$ cat .gclient solutions = [ { "name" : "my_project", "url" : "ssh://example.com/repos/my_project.git", "deps_file" : ".DEPS.git", "managed" : True, "custom_deps" : { }, "safesync_url": "", }, ] cache_dir = None $ cat my_project/.DEPS.git vars = { # Common settings. "base_url" : "ssh://example.com/repos", "project_directory" : "my_project", # Specify dependency package |package| as package_destination, package_url, # and package_revision tuples. Then, ensure to add the dependency in deps # using the variables. # Google test "googletest_destination" : "third_party/googletest", "googletest_url" : "/external/googletest.git", "googletest_revision" : "2a2740e0ce24acaae88fb1c7b1edf5a2289d3b1c", } deps = { # Google test Var("project_directory") + "/" + Var("googletest_destination") : Var("base_url") + Var("googletest_url") + "@" + Var("googletest_revision") }
Now, in the last post, I noted that the initial checkout seems to happen in a detached state. That is, git status prints out something along the lines of the following:
HEAD detached at origin/master
I would like my initial checkouts to always go to the master branch, since I know I will forget to switch to the master branch when doing changes. This seems like a good application of a hook.
As you recall, .DEPS.git from Chromium had a section called hooks. Great! I would imagine that's exactly what we want.
The structure seems to be as follows:
hooks = [ { "name" : "hook_name", "pattern" : "hook_pattern", "action" : ["hook_action", "hook_action_parameter1"] } ]
Name and action are fairly self explanatory, but I'm not too clear about what the pattern is supposed to represent. Run this hook only if something matches this pattern? That would make sense. However, most of the hooks in Chromium .DEPS.git have pattern as ".", so let's stick with that for now.
Let's just jump in and add a simple hook into our .DEPS.git:
... hooks = [ { "name" : "hello", "pattern" : ".", "action" : ["echo", "hello world!"] } ]
This should do something:
$ gclient runhooks ________ running 'echo hello world!' in '/tmp/learning_gclient' hello world! $ gclient sync Syncing projects: 100% (2/2), done. ________ running 'echo hello world!' in '/tmp/learning_gclient' hello world!
Perfect! We can run hooks separately, and the hooks are run when we sync. That's exactly what we want. One observation I have is that it's probably a good idea to stick to some sort of a cross platform scripting language when writing hooks, since 'echo' on my machine might not exist on some other machine. Since gclient itself is written in python, it's a good bet that python is installed. As such, let's stick with python as the hooks language.
Also note that we're running this hook in the same directory as the .gclient file (/tmp/learning_gclient in my case). Switching into my_project and running hooks again confirms that we're always running it from the same directory as the .gclient file.
Alright, let's just jump in and write a hook that checkout out master if the current state is "HEAD detached at origin/master". Learning python is out of the scope of this post, but after some googling on how to do things in it I came up with this:
$ cat my_project/hooks/checkout_master.py import os from subprocess import Popen, PIPE def main(): os.chdir("my_project") p = Popen(['git', 'status'], stdout=PIPE) output, err = p.communicate() line = output.splitlines()[0].strip() if line != 'HEAD detached at origin/master': print 'not an initial checkout, skip checkout master' return print 'checking out master branch' p = Popen(['git', 'checkout', 'master'], stdout=PIPE, stderr=PIPE) output, err = p.communicate() if __name__ == "__main__": main()
Basically, we switch to my_project (I don't like hardcoding the project name here, but it will do for now), get the output of "git status", and if the first line of that is 'HEAD detached at origin/master', we run "git checkout master". Now, let's add this hook into our .DEPS.git, replacing the hello world one:
... hooks = [ { "name" : "checkout_master", "pattern" : ".", "action" : ["python", Var("project_directory") + "/hooks/checkout_master.py"] } ]
Let's see if that works as expected:
$ gclient runhooks ________ running '/usr/bin/python my_project/hooks/checkout_master.py' in '/tmp/learning_gclient' not an initial checkout, skip checkout master
Right, that's because during my testing, I already switched to the master branch. Let's just delete the whole project and sync again. However, remember to commit/push your changes. During my first attempt, I removed the directory and lost all of my changes (ie, I had to write the script and the hooks again).
$ rm -rf my_project $ gclient sync Syncing projects: 100% (2/2), done. ________ running '/usr/bin/python my_project/hooks/checkout_master.py' in '/tmp/learning_gclient' checking out master branch $ cd my_project/ $ git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean
Excellent! Everything seems to be working as intended. I think I'm happy enough with this basic gclient setup to move on to the actual build system using either GYP or GN.
- vmpstr
P.S. To eliminate the project name from checkout_master.py (thus making it a generic hook for any project), we should just move the project name to be a parameter. Here's a diff that makes it so:
diff --git a/.DEPS.git b/.DEPS.git index 482ee29..56555cd 100644 --- a/.DEPS.git +++ b/.DEPS.git @@ -23,6 +23,10 @@ hooks = [ { "name" : "checkout_master", "pattern" : ".", - "action" : ["python", Var("project_directory") + "/hooks/checkout_master.py"] + "action" : [ + "python", + Var("project_directory") + "/hooks/checkout_master.py", + Var("project_directory") + ] } ] diff --git a/hooks/checkout_master.py b/hooks/checkout_master.py index f41eefa..b5cf30a 100644 --- a/hooks/checkout_master.py +++ b/hooks/checkout_master.py @@ -1,8 +1,10 @@ import os from subprocess import Popen, PIPE +import sys def main(): - os.chdir("my_project") + if len(sys.argv) == 2: + os.chdir(sys.argv[1]) p = Popen(['git', 'status'], stdout=PIPE) output, err = p.communicate()