Index: gclient.py |
diff --git a/gclient.py b/gclient.py |
index a92b8e110e70f34f7b57097326047c48a25a0699..407b9ad4e99f1efd3f31270f3695e18f458b1fcb 100755 |
--- a/gclient.py |
+++ b/gclient.py |
@@ -3,74 +3,74 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-"""Meta checkout manager supporting both Subversion and GIT. |
- |
-Files |
- .gclient : Current client configuration, written by 'config' command. |
- Format is a Python script defining 'solutions', a list whose |
- entries each are maps binding the strings "name" and "url" |
- to strings specifying the name and location of the client |
- module, as well as "custom_deps" to a map similar to the deps |
- section of the DEPS file below, as well as "custom_hooks" to |
- a list similar to the hooks sections of the DEPS file below. |
- .gclient_entries : A cache constructed by 'update' command. Format is a |
- Python script defining 'entries', a list of the names |
- of all modules in the client |
- <module>/DEPS : Python script defining var 'deps' as a map from each requisite |
- submodule name to a URL where it can be found (via one SCM) |
- |
-Hooks |
- .gclient and DEPS files may optionally contain a list named "hooks" to |
- allow custom actions to be performed based on files that have changed in the |
- working copy as a result of a "sync"/"update" or "revert" operation. This |
- can be prevented by using --nohooks (hooks run by default). Hooks can also |
- be forced to run with the "runhooks" operation. If "sync" is run with |
- --force, all known but not suppressed hooks will run regardless of the state |
- of the working copy. |
- |
- Each item in a "hooks" list is a dict, containing these two keys: |
- "pattern" The associated value is a string containing a regular |
- expression. When a file whose pathname matches the expression |
- is checked out, updated, or reverted, the hook's "action" will |
- run. |
- "action" A list describing a command to run along with its arguments, if |
- any. An action command will run at most one time per gclient |
- invocation, regardless of how many files matched the pattern. |
- The action is executed in the same directory as the .gclient |
- file. If the first item in the list is the string "python", |
- the current Python interpreter (sys.executable) will be used |
- to run the command. If the list contains string "$matching_files" |
- it will be removed from the list and the list will be extended |
- by the list of matching files. |
- "name" An optional string specifying the group to which a hook belongs |
- for overriding and organizing. |
- |
- Example: |
- hooks = [ |
- { "pattern": "\\.(gif|jpe?g|pr0n|png)$", |
- "action": ["python", "image_indexer.py", "--all"]}, |
- { "pattern": ".", |
- "name": "gyp", |
- "action": ["python", "src/build/gyp_chromium"]}, |
- ] |
- |
-Specifying a target OS |
- An optional key named "target_os" may be added to a gclient file to specify |
- one or more additional operating systems that should be considered when |
- processing the deps_os dict of a DEPS file. |
- |
- Example: |
- target_os = [ "android" ] |
- |
- If the "target_os_only" key is also present and true, then *only* the |
- operating systems listed in "target_os" will be used. |
- |
- Example: |
- target_os = [ "ios" ] |
- target_os_only = True |
-""" |
- |
-__version__ = "0.6.4" |
+"""Meta checkout manager supporting both Subversion and GIT.""" |
+# Files |
+# .gclient : Current client configuration, written by 'config' command. |
+# Format is a Python script defining 'solutions', a list whose |
+# entries each are maps binding the strings "name" and "url" |
+# to strings specifying the name and location of the client |
+# module, as well as "custom_deps" to a map similar to the |
+# deps section of the DEPS file below, as well as |
+# "custom_hooks" to a list similar to the hooks sections of |
+# the DEPS file below. |
+# .gclient_entries : A cache constructed by 'update' command. Format is a |
+# Python script defining 'entries', a list of the names |
+# of all modules in the client |
+# <module>/DEPS : Python script defining var 'deps' as a map from each |
+# requisite submodule name to a URL where it can be found (via |
+# one SCM) |
+# |
+# Hooks |
+# .gclient and DEPS files may optionally contain a list named "hooks" to |
+# allow custom actions to be performed based on files that have changed in the |
+# working copy as a result of a "sync"/"update" or "revert" operation. This |
+# can be prevented by using --nohooks (hooks run by default). Hooks can also |
+# be forced to run with the "runhooks" operation. If "sync" is run with |
+# --force, all known but not suppressed hooks will run regardless of the state |
+# of the working copy. |
+# |
+# Each item in a "hooks" list is a dict, containing these two keys: |
+# "pattern" The associated value is a string containing a regular |
+# expression. When a file whose pathname matches the expression |
+# is checked out, updated, or reverted, the hook's "action" will |
+# run. |
+# "action" A list describing a command to run along with its arguments, if |
+# any. An action command will run at most one time per gclient |
+# invocation, regardless of how many files matched the pattern. |
+# The action is executed in the same directory as the .gclient |
+# file. If the first item in the list is the string "python", |
+# the current Python interpreter (sys.executable) will be used |
+# to run the command. If the list contains string |
+# "$matching_files" it will be removed from the list and the list |
+# will be extended by the list of matching files. |
+# "name" An optional string specifying the group to which a hook belongs |
+# for overriding and organizing. |
+# |
+# Example: |
+# hooks = [ |
+# { "pattern": "\\.(gif|jpe?g|pr0n|png)$", |
+# "action": ["python", "image_indexer.py", "--all"]}, |
+# { "pattern": ".", |
+# "name": "gyp", |
+# "action": ["python", "src/build/gyp_chromium"]}, |
+# ] |
+# |
+# Specifying a target OS |
+# An optional key named "target_os" may be added to a gclient file to specify |
+# one or more additional operating systems that should be considered when |
+# processing the deps_os dict of a DEPS file. |
+# |
+# Example: |
+# target_os = [ "android" ] |
+# |
+# If the "target_os_only" key is also present and true, then *only* the |
+# operating systems listed in "target_os" will be used. |
+# |
+# Example: |
+# target_os = [ "ios" ] |
+# target_os_only = True |
+ |
+__version__ = '0.7' |
import copy |
import logging |
@@ -91,21 +91,9 @@ import fix_encoding |
import gclient_scm |
import gclient_utils |
from third_party.repo.progress import Progress |
+import subcommand |
import subprocess2 |
from third_party import colorama |
-# Import shortcut. |
-from third_party.colorama import Fore |
- |
- |
-def attr(attribute, data): |
- """Sets an attribute on a function.""" |
- def hook(fn): |
- setattr(fn, attribute, data) |
- return fn |
- return hook |
- |
- |
-## GClient implementation. |
class GClientKeywords(object): |
@@ -1314,8 +1302,8 @@ solutions = [ |
def CMDcleanup(parser, args): |
"""Cleans up all working copies. |
-Mostly svn-specific. Simply runs 'svn cleanup' for each module. |
-""" |
+ Mostly svn-specific. Simply runs 'svn cleanup' for each module. |
+ """ |
parser.add_option('--deps', dest='deps_os', metavar='OS_LIST', |
help='override deps for the specified (comma-separated) ' |
'platform(s); \'all\' will process all deps_os ' |
@@ -1331,9 +1319,9 @@ Mostly svn-specific. Simply runs 'svn cleanup' for each module. |
return client.RunOnDeps('cleanup', args) |
-@attr('usage', '[command] [args ...]') |
+@subcommand.usage('[command] [args ...]') |
def CMDrecurse(parser, args): |
- """Operates on all the entries. |
+ """Operates [command args ...] on all the dependencies. |
Runs a shell command on all entries. |
Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to |
@@ -1372,12 +1360,12 @@ def CMDrecurse(parser, args): |
progress=not options.no_progress) |
-@attr('usage', '[args ...]') |
+@subcommand.usage('[args ...]') |
def CMDfetch(parser, args): |
"""Fetches upstream commits for all modules. |
-Completely git-specific. Simply runs 'git fetch [args ...]' for each module. |
-""" |
+ Completely git-specific. Simply runs 'git fetch [args ...]' for each module. |
+ """ |
(options, args) = parser.parse_args(args) |
return CMDrecurse(OptionParser(), [ |
'--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args) |
@@ -1386,9 +1374,8 @@ Completely git-specific. Simply runs 'git fetch [args ...]' for each module. |
def CMDgrep(parser, args): |
"""Greps through git repos managed by gclient. |
-Runs 'git grep [args...]' for each module. |
-""" |
- |
+ Runs 'git grep [args...]' for each module. |
+ """ |
# We can't use optparse because it will try to parse arguments sent |
# to git grep and throw an error. :-( |
if not args or re.match('(-h|--help)$', args[0]): |
@@ -1413,17 +1400,16 @@ Runs 'git grep [args...]' for each module. |
'git', 'grep', '--null', '--color=Always'] + args) |
-@attr('usage', '[url] [safesync url]') |
+@subcommand.usage('[url] [safesync url]') |
def CMDconfig(parser, args): |
- """Create a .gclient file in the current directory. |
- |
-This specifies the configuration for further commands. After update/sync, |
-top-level DEPS files in each module are read to determine dependent |
-modules to operate on as well. If optional [url] parameter is |
-provided, then configuration is read from a specified Subversion server |
-URL. |
-""" |
+ """Creates a .gclient file in the current directory. |
+ This specifies the configuration for further commands. After update/sync, |
+ top-level DEPS files in each module are read to determine dependent |
+ modules to operate on as well. If optional [url] parameter is |
+ provided, then configuration is read from a specified Subversion server |
+ URL. |
+ """ |
# We do a little dance with the --gclientfile option. 'gclient config' is the |
# only command where it's acceptable to have both '--gclientfile' and '--spec' |
# arguments. So, we temporarily stash any --gclientfile parameter into |
@@ -1481,18 +1467,18 @@ URL. |
return 0 |
-@attr('epilog', """Example: |
+@subcommand.epilog("""Example: |
gclient pack > patch.txt |
generate simple patch for configured client and dependences |
""") |
def CMDpack(parser, args): |
- """Generate a patch which can be applied at the root of the tree. |
+ """Generates a patch which can be applied at the root of the tree. |
-Internally, runs 'svn diff'/'git diff' on each checked out module and |
-dependencies, and performs minimal postprocessing of the output. The |
-resulting patch is printed to stdout and can be applied to a freshly |
-checked out tree via 'patch -p0 < patchfile'. |
-""" |
+ Internally, runs 'svn diff'/'git diff' on each checked out module and |
+ dependencies, and performs minimal postprocessing of the output. The |
+ resulting patch is printed to stdout and can be applied to a freshly |
+ checked out tree via 'patch -p0 < patchfile'. |
+ """ |
parser.add_option('--deps', dest='deps_os', metavar='OS_LIST', |
help='override deps for the specified (comma-separated) ' |
'platform(s); \'all\' will process all deps_os ' |
@@ -1512,7 +1498,7 @@ checked out tree via 'patch -p0 < patchfile'. |
def CMDstatus(parser, args): |
- """Show modification status for every dependencies.""" |
+ """Shows modification status for every dependencies.""" |
parser.add_option('--deps', dest='deps_os', metavar='OS_LIST', |
help='override deps for the specified (comma-separated) ' |
'platform(s); \'all\' will process all deps_os ' |
@@ -1528,7 +1514,7 @@ def CMDstatus(parser, args): |
return client.RunOnDeps('status', args) |
-@attr('epilog', """Examples: |
+@subcommand.epilog("""Examples: |
gclient sync |
update files from SCM according to current configuration, |
*for modules which have changed since last update or sync* |
@@ -1604,9 +1590,8 @@ def CMDsync(parser, args): |
return client.RunOnDeps('update', args) |
-def CMDupdate(parser, args): |
- """Alias for the sync command. Deprecated.""" |
- return CMDsync(parser, args) |
+CMDupdate = CMDsync |
+ |
def CMDdiff(parser, args): |
"""Displays local diff for every dependencies.""" |
@@ -1626,7 +1611,7 @@ def CMDdiff(parser, args): |
def CMDrevert(parser, args): |
- """Revert all modifications in every dependencies. |
+ """Reverts all modifications in every dependencies. |
That's the nuclear option to get back to a 'clean' state. It removes anything |
that shows up in svn status.""" |
@@ -1671,7 +1656,7 @@ def CMDrunhooks(parser, args): |
def CMDrevinfo(parser, args): |
- """Output revision info mapping for the client and its dependencies. |
+ """Outputs revision info mapping for the client and its dependencies. |
This allows the capture of an overall 'revision' for the source tree that |
can be used to reproduce the same tree in the future. It is only useful for |
@@ -1699,7 +1684,7 @@ def CMDrevinfo(parser, args): |
def CMDhookinfo(parser, args): |
- """Output the hooks that would be run by `gclient runhooks`""" |
+ """Outputs the hooks that would be run by `gclient runhooks`.""" |
(options, args) = parser.parse_args(args) |
options.force = True |
client = GClient.LoadCurrentConfig(options) |
@@ -1710,31 +1695,6 @@ def CMDhookinfo(parser, args): |
return 0 |
-def Command(name): |
- return getattr(sys.modules[__name__], 'CMD' + name, None) |
- |
- |
-def CMDhelp(parser, args): |
- """Prints list of commands or help for a specific command.""" |
- (_, args) = parser.parse_args(args) |
- if len(args) == 1: |
- return Main(args + ['--help']) |
- parser.print_help() |
- return 0 |
- |
- |
-def GenUsage(parser, command): |
- """Modify an OptParse object with the function's documentation.""" |
- obj = Command(command) |
- if command == 'help': |
- command = '<command>' |
- # OptParser.description prefer nicely non-formatted strings. |
- parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__) |
- usage = getattr(obj, 'usage', '') |
- parser.set_usage('%%prog %s [options] %s' % (command, usage)) |
- parser.epilog = getattr(obj, 'epilog', None) |
- |
- |
class OptionParser(optparse.OptionParser): |
gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient') |
@@ -1805,9 +1765,13 @@ class OptionParser(optparse.OptionParser): |
gclient_scm.SCMWrapper.nag_max = None |
return (options, args) |
- def format_epilog(self, _): |
- """Disables wordwrapping in epilog (usually examples).""" |
- return self.epilog or '' |
+ |
+def disable_buffering(): |
+ # Make stdout auto-flush so buildbot doesn't kill us during lengthy |
+ # operations. Python as a strong tendency to buffer sys.stdout. |
+ sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout) |
+ # Make stdout annotated with the thread ids. |
+ sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout) |
def Main(argv): |
@@ -1822,34 +1786,12 @@ def Main(argv): |
print >> sys.stderr, ( |
'\nPython cannot find the location of it\'s own executable.\n') |
return 2 |
+ fix_encoding.fix_encoding() |
+ disable_buffering() |
colorama.init() |
+ dispatcher = subcommand.CommandDispatcher(__name__) |
try: |
- # Make stdout auto-flush so buildbot doesn't kill us during lengthy |
- # operations. Python as a strong tendency to buffer sys.stdout. |
- sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout) |
- # Make stdout annotated with the thread ids. |
- sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout) |
- # Do it late so all commands are listed. |
- # Unused variable 'usage' |
- # pylint: disable=W0612 |
- def to_str(fn): |
- return ( |
- ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) + |
- ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip()) |
- cmds = ( |
- to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD') |
- ) |
- CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds) |
- parser = OptionParser() |
- if argv: |
- command = Command(argv[0]) |
- if command: |
- # 'fix' the usage and the description now that we know the subcommand. |
- GenUsage(parser, argv[0]) |
- return command(parser, argv[1:]) |
- # Not a known command. Default to help. |
- GenUsage(parser, 'help') |
- return CMDhelp(parser, argv) |
+ return dispatcher.execute(OptionParser(), argv) |
except KeyboardInterrupt: |
gclient_utils.GClientChildren.KillAllRemainingChildren() |
raise |
@@ -1859,7 +1801,6 @@ def Main(argv): |
if '__main__' == __name__: |
- fix_encoding.fix_encoding() |
sys.exit(Main(sys.argv[1:])) |
# vim: ts=2:sw=2:tw=80:et: |