chromium的部署工具depot_tools和gclient

备注:本文摘自https://blog.csdn.net/hjx5200/article/details/42706761,非常感谢博主的分享,本次转载仅用于学习记录,如有侵权请及时告知,本人会及时删除。

depot_tools是个工具包,里面包含gclient、gcl、gn和ninja等工具。其中gclient是代码获取工具,它其实是利用了svn和git。主要涉及的depot_tools文件夹下的文件有:gclient、gclient.py、subcommand.py、gclient_utils.py。

gclient文件是个bash脚本:

[plain]  view plain  copy
  1. #########glcient###########  
  2. /usr/bin/bash   
  3. base_dir=$(dirname "$0")  
  4.    
  5.  if [[ "#grep#fetch#cleanup#diff#" != *"#$1#"* ]]; then  
  6.    "$base_dir"/update_depot_tools  
  7.  fi  
  8.    
  9.  PYTHONDONTWRITEBYTECODE=1 exec python "$base_dir/gclient.py" "$@"  


首先,获取脚本的目录并赋值给base_dir,然后判断命令参数1是否为grep|fetch|cleanup|diff,如不是则执行base_dir下的updat_depot_tools脚本,该脚本更新git、svn工具。最后调用当前脚本目录(depot_tools)下的python脚本gclient.py,并把参数都传递给该脚本。

[plain]  view plain  copy
  1. ###############glcient.py#######  
  2. # 文件最后  
  3.   
  4. def Main(argv):  
  5.       .....  
  6.    dispatcher = subcommand.CommandDispatcher(__name__)  
  7.    try:  
  8.      return dispatcher.execute(OptionParser(), argv)  
  9.     ......  
  10. if '__main__' == __name__:  
  11.    sys.exit(Main(sys.argv[1:]))  

在gclient.py脚本开始,调用函数Main,参数为bash脚本传递来的。Main主要执行两个过程,一是建立dispatcher对象,参数为当前模块;然后调用dispatcher的execute方法,参数一是OptionParser对象,参数二为传递给Main的参数。

下面进入subcommand模块下的类CommandDispatcher,分析execute方法。

[plain]  view plain  copy
  1. ################# subcommand.py ###########  
  2. class CommandDispatcher(object):  
  3.   def __init__(self, module):  
  4.     """module is the name of the main python module where to look for commands.  
  5.   
  6.     The python builtin variable __name__ MUST be used for |module|. If the  
  7.     script is executed in the form 'python script.py', __name__ == '__main__'  
  8.     and sys.modules['script'] doesn't exist. On the other hand if it is unit  
  9.     tested, __main__ will be the unit test's module so it has to reference to  
  10.     itself with 'script'. __name__ always match the right value.  
  11.     """  
  12.     self.module = sys.modules[module]  
  13.   
  14.   def enumerate_commands(self):  
  15.     """Returns a dict of command and their handling function.  
  16.   
  17.     The commands must be in the '__main__' modules. To import a command from a  
  18.     submodule, use:  
  19.       from mysubcommand import CMDfoo  
  20.   
  21.     Automatically adds 'help' if not already defined.  
  22.   
  23.     A command can be effectively disabled by defining a global variable to None,  
  24.     e.g.:  
  25.       CMDhelp = None  
  26.     """  
  27.     cmds = dict(  
  28.         (fn[3:], getattr(self.module, fn))  
  29.         for fn in dir(self.module) if fn.startswith('CMD'))  
  30.     cmds.setdefault('help', CMDhelp)  
  31.     return cmds  
  32.   
  33.   def find_nearest_command(self, name):  
  34.     """Retrieves the function to handle a command.  
  35.   
  36.     It automatically tries to guess the intended command by handling typos or  
  37.     incomplete names.  
  38.     """  
  39.     # Implicitly replace foo-bar to foo_bar since foo-bar is not a valid python  
  40.     # symbol but it's faster to type.  
  41.     name = name.replace('-', '_')  
  42.     commands = self.enumerate_commands()  
  43.     if name in commands:  
  44.       return commands[name]  
  45.   
  46.     # An exact match was not found. Try to be smart and look if there's  
  47.     # something similar.  
  48.     commands_with_prefix = [c for c in commands if c.startswith(name)]  
  49.     if len(commands_with_prefix) == 1:  
  50.       return commands[commands_with_prefix[0]]  
  51.   
  52.     # A #closeenough approximation of levenshtein distance.  
  53.     def close_enough(a, b):  
  54.       return difflib.SequenceMatcher(a=a, b=b).ratio()  
  55.   
  56.     hamming_commands = sorted(  
  57.         ((close_enough(c, name), c) for c in commands),  
  58.         reverse=True)  
  59.     if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3:  
  60.       # Too ambiguous.  
  61.       return  
  62.   
  63.     if hamming_commands[0][0] < 0.8:  
  64.       # Not similar enough. Don't be a fool and run a random command.  
  65.       return  
  66.   
  67.     return commands[hamming_commands[0][1]]  
  68.   
  69.   def _gen_commands_list(self):  
  70.     """Generates the short list of supported commands."""  
  71.     commands = self.enumerate_commands()  
  72.     docs = sorted(  
  73.         (name, self._create_command_summary(name, handler))  
  74.         for name, handler in commands.iteritems())  
  75.     # Skip commands without a docstring.  
  76.     docs = [i for i in docs if i[1]]  
  77.     # Then calculate maximum length for alignment:  
  78.     length = max(len(c) for c in commands)  
  79.   
  80.     # Look if color is supported.  
  81.     colors = _get_color_module()  
  82.     green = reset = ''  
  83.     if colors:  
  84.       green = colors.Fore.GREEN  
  85.       reset = colors.Fore.RESET  
  86.     return (  
  87.         'Commands are:\n' +  
  88.         ''.join(  
  89.             '  %s%-*s%s %s\n' % (green, length, name, reset, doc)  
  90.             for name, doc in docs))  
  91.   
  92.   def _add_command_usage(self, parser, command):  
  93.     """Modifies an OptionParser object with the function's documentation."""  
  94.     name = command.__name__[3:]  
  95.     if name == 'help':  
  96.       name = '<command>'  
  97.       # Use the module's docstring as the description for the 'help' command if  
  98.       # available.  
  99.       parser.description = (self.module.__doc__ or '').rstrip()  
  100.       if parser.description:  
  101.         parser.description += '\n\n'  
  102.       parser.description += self._gen_commands_list()  
  103.       # Do not touch epilog.  
  104.     else:  
  105.       # Use the command's docstring if available. For commands, unlike module  
  106.       # docstring, realign.  
  107.       lines = (command.__doc__ or '').rstrip().splitlines()  
  108.       if lines[:1]:  
  109.         rest = textwrap.dedent('\n'.join(lines[1:]))  
  110.         parser.description = '\n'.join((lines[0], rest))  
  111.       else:  
  112.         parser.description = lines[0]  
  113.       if parser.description:  
  114.         parser.description += '\n'  
  115.       parser.epilog = getattr(command, 'epilog', None)  
  116.       if parser.epilog:  
  117.         parser.epilog = '\n' + parser.epilog.strip() + '\n'  
  118.   
  119.     more = getattr(command, 'usage_more', '')  
  120.     parser.set_usage(  
  121.         'usage: %%prog %s [options]%s' % (name, '' if not more else ' ' + more))  
  122.   
  123.   @staticmethod  
  124.   def _create_command_summary(name, command):  
  125.     """Creates a oneline summary from the command's docstring."""  
  126.     if name != command.__name__[3:]:  
  127.       # Skip aliases.  
  128.       return ''  
  129.     doc = command.__doc__ or ''  
  130.     line = doc.split('\n', 1)[0].rstrip('.')  
  131.     if not line:  
  132.       return line  
  133.     return (line[0].lower() + line[1:]).strip()  
  134.   
  135.   def execute(self, parser, args):  
  136.     """Dispatches execution to the right command.  
  137.   
  138.     Fallbacks to 'help' if not disabled.  
  139.     """  
  140.     # Unconditionally disable format_description() and format_epilog().  
  141.     # Technically, a formatter should be used but it's not worth (yet) the  
  142.     # trouble.  
  143.     parser.format_description = lambda _: parser.description or ''  
  144.     parser.format_epilog = lambda _: parser.epilog or ''  
  145.   
  146.     if args:  
  147.       if args[0] in ('-h', '--help') and len(args) > 1:  
  148.         # Inverse the argument order so 'tool --help cmd' is rewritten to  
  149.         # 'tool cmd --help'.  
  150.         args = [args[1], args[0]] + args[2:]  
  151.       command = self.find_nearest_command(args[0])  
  152.       if command:  
  153.         if command.__name__ == 'CMDhelp' and len(args) > 1:  
  154.           # Inverse the arguments order so 'tool help cmd' is rewritten to  
  155.           # 'tool cmd --help'. Do it here since we want 'tool hel cmd' to work  
  156.           # too.  
  157.           args = [args[1], '--help'] + args[2:]  
  158.           command = self.find_nearest_command(args[0]) or command  
  159.   
  160.         # "fix" the usage and the description now that we know the subcommand.  
  161.         self._add_command_usage(parser, command)  
  162.         return command(parser, args[1:])  
  163.   
  164.     cmdhelp = self.enumerate_commands().get('help')  
  165.     if cmdhelp:  
  166.       # Not a known command. Default to help.  
  167.       self._add_command_usage(parser, cmdhelp)  
  168.       return cmdhelp(parser, args)  
  169.   
  170.     # Nothing can be done.  
  171.     return 2  


在gcient.py中,dispatcher = subcomamnd.CommandDispatcher(__name__),可以看出传入的参数是gclient.py这个模块。在CommandDispatcher的__init__中,self.module就是gclent。了解这点对理解后面的enumrate_commands函数有用。从它的名字就可以看出是枚举所有支持的命令,返回的结果是dict,键是命令名称,值为对应的处理函数,如{"sync" : CMDsync}。其他函数如_add_command_usage, _create_command_summary, _gen_commands_list是些辅助函数,功能也比较明确。比较复杂的是find_nearest_command,它是从enumerate_commands生成的dict字典里查找命令,如果没有精确匹配的,就通过模糊查找,并返回对应命令的处理函数。

[plain]  view plain  copy
  1. ############# gclient.py ##################  
  2. class OptionParser(optparse.OptionParser):  
  3.   gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')  
  4.   
  5.   def __init__(self, **kwargs):  
  6.     optparse.OptionParser.__init__(  
  7.         self, version='%prog ' + __version__, **kwargs)  
  8.   
  9.     # Some arm boards have issues with parallel sync.  
  10.     if platform.machine().startswith('arm'):  
  11.       jobs = 1  
  12.     else:  
  13.       jobs = max(8, gclient_utils.NumLocalCpus())  
  14.     # cmp: 2013/06/19  
  15.     # Temporary workaround to lower bot-load on SVN server.  
  16.     # Bypassed if a bot_update flag is detected.  
  17.     if (os.environ.get('CHROME_HEADLESS') == '1' and  
  18.         not os.path.exists('update.flag')):  
  19.       jobs = 1  
  20.   
  21.     self.add_option(  
  22.         '-j', '--jobs', default=jobs, type='int',  
  23.         help='Specify how many SCM commands can run in parallel; defaults to '  
  24.              '%default on this machine')  
  25.     self.add_option(  
  26.         '-v', '--verbose', action='count', default=0,  
  27.         help='Produces additional output for diagnostics. Can be used up to '  
  28.              'three times for more logging info.')  
  29.     self.add_option(  
  30.         '--gclientfile', dest='config_filename',  
  31.         help='Specify an alternate %s file' % self.gclientfile_default)  
  32.     self.add_option(  
  33.         '--spec',  
  34.         help='create a gclient file containing the provided string. Due to '  
  35.             'Cygwin/Python brokenness, it can\'t contain any newlines.')  
  36.     self.add_option(  
  37.         '--no-nag-max', default=False, action='store_true',  
  38.         help='Ignored for backwards compatibility.')  

参数OptionParser是gclient.py中的一个类,它继承子模块的optparser.OptionParser。主要功能是解析参数,并返回tuple(options, args),关于OptionParser这个例子说明的很清楚。返回值就是输入参数被格式化分析后的结果。这个函数处理分两个层面,一是如果是帮助查询,一个是执行命令。在帮助查询时,用户输入的命令如:gclient --help sync等。其实,传到execute的时候,参数变成--help sync,那么首先是调换参数位置,使得结果为sync --help,也就是args[0]为"sync", args[1]为--help,然后调用self.fine_nearest_command函数,查找精确命令或者最相似的模糊匹配的命令,并把结果(命令处理函数以CMD开头)赋值给command变量。


进入到execute(),解析参数,find_nearest_commands()得到命令后,判断是不是帮助命令,如果不是就直接调用对应的处理函数return command(parser, args[1:])。用上例来说就是调用CMDsync函数,参数是parser 和sync后的参数。

看看CMDsync是如何处理输入参数的?

[plain]  view plain  copy
  1. #############gclient.py#############  
  2. def CMDsync(parser, args):  
  3.   """Checkout/update all modules."""  
  4.   parser.add_option('-f', '--force', action='store_true',  
  5.                     help='force update even for unchanged modules')  
  6.   parser.add_option('-n', '--nohooks', action='store_true',  
  7.                     help='don\'t run hooks after the update is complete')  
  8.   parser.add_option('-p', '--noprehooks', action='store_true',  
  9.                     help='don\'t run pre-DEPS hooks', default=False)  
  10.   parser.add_option('-r', '--revision', action='append',  
  11.                     dest='revisions', metavar='REV', default=[],  
  12.                     help='Enforces revision/hash for the solutions with the '  
  13.                          'format src@rev. The src@ part is optional and can be '  
  14.                          'skipped. -r can be used multiple times when .gclient '  
  15.                          'has multiple solutions configured and will work even '  
  16.                          'if the src@ part is skipped. Note that specifying '  
  17.                          '--revision means your safesync_url gets ignored.')  
  18.   parser.add_option('--with_branch_heads', action='store_true',  
  19.                     help='Clone git "branch_heads" refspecs in addition to '  
  20.                          'the default refspecs. This adds about 1/2GB to a '  
  21.                          'full checkout. (git only)')  
  22.   parser.add_option('-t', '--transitive', action='store_true',  
  23.                     help='When a revision is specified (in the DEPS file or '  
  24.                           'with the command-line flag), transitively update '  
  25.                           'the dependencies to the date of the given revision. '  
  26.                           'Only supported for SVN repositories.')  
  27.   parser.add_option('-H', '--head', action='store_true',  
  28.                     help='skips any safesync_urls specified in '  
  29.                          'configured solutions and sync to head instead')  
  30.   parser.add_option('-D', '--delete_unversioned_trees', action='store_true',  
  31.                     help='Deletes from the working copy any dependencies that '  
  32.                          'have been removed since the last sync, as long as '  
  33.                          'there are no local modifications. When used with '  
  34.                          '--force, such dependencies are removed even if they '  
  35.                          'have local modifications. When used with --reset, '  
  36.                          'all untracked directories are removed from the '  
  37.                          'working copy, excluding those which are explicitly '  
  38.                          'ignored in the repository.')  
  39.   parser.add_option('-R', '--reset', action='store_true',  
  40.                     help='resets any local changes before updating (git only)')  
  41.   parser.add_option('-M', '--merge', action='store_true',  
  42.                     help='merge upstream changes instead of trying to '  
  43.                          'fast-forward or rebase')  
  44.   parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',  
  45.                     help='override deps for the specified (comma-separated) '  
  46.                          'platform(s); \'all\' will process all deps_os '  
  47.                          'references')  
  48.   parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',  
  49.                     help='Skip svn up whenever possible by requesting '  
  50.                          'actual HEAD revision from the repository')  
  51.   parser.add_option('--upstream', action='store_true',  
  52.                     help='Make repo state match upstream branch.')  
  53.   parser.add_option('--output-json',  
  54.                     help='Output a json document to this path containing '  
  55.                          'summary information about the sync.')  
  56.   (options, args) = parser.parse_args(args)  
  57.   client = GClient.LoadCurrentConfig(options)  
  58.   
  59.   if not client:  
  60.     raise gclient_utils.Error('client not configured; see \'gclient config\'')  
  61.   
  62.   if options.revisions and options.head:  
  63.     # TODO(maruel): Make it a parser.error if it doesn't break any builder.  
  64.     print('Warning: you cannot use both --head and --revision')  
  65.   
  66.   if options.verbose:  
  67.     # Print out the .gclient file.  This is longer than if we just printed the  
  68.     # client dict, but more legible, and it might contain helpful comments.  
  69.     print(client.config_content)  
  70.   ret = client.RunOnDeps('update', args)  
  71.   if options.output_json:  
  72.     slns = {}  
  73.     for d in client.subtree(True):  
  74.       normed = d.name.replace('\\', '/').rstrip('/') + '/'  
  75.       slns[normed] = {  
  76.           'revision': d.got_revision,  
  77.           'scm': d.used_scm.name if d.used_scm else None,  
  78.       }  
  79.     with open(options.output_json, 'wb') as f:  
  80.       json.dump({'solutions': slns}, f)  
  81.   return ret  
  82.   
  83.   
  84. CMDupdate = CMDsync  
通过options,args = parser.parse_args(args),得到对CMDsync命令参数的解析,然后调用Gclient.LoadCurrentConfig(options),为简单起见,我们输入的是gclient sync,那么options和args为空。

[plain]  view plain  copy
  1. ############glcient.py#############  
  2. def LoadCurrentConfig(options):  
  3.     """Searches for and loads a .gclient file relative to the current working  
  4.     dir. Returns a GClient object."""  
  5.     if options.spec:  
  6.       client = GClient('.', options)  
  7.       client.SetConfig(options.spec)  
  8.     else:  
  9.       path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)  
  10.       if not path:  
  11.         return None  
  12.       client = GClient(path, options)  
  13.       client.SetConfig(gclient_utils.FileRead(  
  14.           os.path.join(path, options.config_filename)))  
  15.   
  16.     if (options.revisions and  
  17.         len(client.dependencies) > 1 and  
  18.         any('@' not in r for r in options.revisions)):  
  19.       print >> sys.stderr, (  
  20.           'You must specify the full solution name like --revision %s@%s\n'  
  21.           'when you have multiple solutions setup in your .gclient file.\n'  
  22.           'Other solutions present are: %s.') % (  
  23.               client.dependencies[0].name,  
  24.               options.revisions[0],  
  25.               ', '.join(s.name for s in client.dependencies[1:]))  
  26.     return client  

该函数查询当前目录下.glcient文件,最终返回client对象。该函数会调用到GClient(path, options):

[plain]  view plain  copy
  1. ############## gclient.py ##################  
  2. class GClient(Dependency):  
  3.   """Object that represent a gclient checkout. A tree of Dependency(), one per  
  4.   solution or DEPS entry."""  
  5.   
  6.   DEPS_OS_CHOICES = {  
  7.     "win32": "win",  
  8.     "win": "win",  
  9.     "cygwin": "win",  
  10.     "darwin": "mac",  
  11.     "mac": "mac",  
  12.     "unix": "unix",  
  13.     "linux": "unix",  
  14.     "linux2": "unix",  
  15.     "linux3": "unix",  
  16.     "android": "android",  
  17.   }  
  18.   
  19.   DEFAULT_CLIENT_FILE_TEXT = ("""\  
  20. solutions = [  
  21.   { "name"        : "%(solution_name)s",  
  22.     "url"         : "%(solution_url)s",  
  23.     "deps_file"   : "%(deps_file)s",  
  24.     "managed"     : %(managed)s,  
  25.     "custom_deps" : {  
  26.     },  
  27.     "safesync_url": "%(safesync_url)s",  
  28.   },  
  29. ]  
  30. cache_dir = %(cache_dir)r  
  31. """)  
  32.   
  33.   DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\  
  34.   { "name"        : "%(solution_name)s",  
  35.     "url"         : "%(solution_url)s",  
  36.     "deps_file"   : "%(deps_file)s",  
  37.     "managed"     : %(managed)s,  
  38.     "custom_deps" : {  
  39. %(solution_deps)s    },  
  40.     "safesync_url": "%(safesync_url)s",  
  41.   },  
  42. """)  
  43.   
  44.   DEFAULT_SNAPSHOT_FILE_TEXT = ("""\  
  45. # Snapshot generated with gclient revinfo --snapshot  
  46. solutions = [  
  47. %(solution_list)s]  
  48. """)  
  49.   
  50.   def __init__(self, root_dir, options):  
  51.     # Do not change previous behavior. Only solution level and immediate DEPS  
  52.     # are processed.  
  53.     self._recursion_limit = 2  
  54.     Dependency.__init__(self, None, None, None, None, True, None, None, None,  
  55.                         'unused', True)  
  56.     self._options = options  
  57.     if options.deps_os:  
  58.       enforced_os = options.deps_os.split(',')  
  59.     else:  
  60.       enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]  
  61.     if 'all' in enforced_os:  
  62.       enforced_os = self.DEPS_OS_CHOICES.itervalues()  
  63.     self._enforced_os = tuple(set(enforced_os))  
  64.     self._root_dir = root_dir  
  65.     self.config_content = None  
  66.   
  67.   def SetConfig(self, content):  
  68.     assert not self.dependencies  
  69.     config_dict = {}  
  70.     self.config_content = content  
  71.     try:  
  72.       exec(content, config_dict)  
  73.     except SyntaxError, e:  
  74.       gclient_utils.SyntaxErrorToError('.gclient', e)  
  75.   
  76.     # Append any target OS that is not already being enforced to the tuple.  
  77.     target_os = config_dict.get('target_os', [])  
  78.     if config_dict.get('target_os_only', False):  
  79.       self._enforced_os = tuple(set(target_os))  
  80.     else:  
  81.       self._enforced_os = tuple(set(self._enforced_os).union(target_os))  
  82.   
  83.     gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')  
  84.   
  85.     if not target_os and config_dict.get('target_os_only', False):  
  86.       raise gclient_utils.Error('Can\'t use target_os_only if target_os is '  
  87.                                 'not specified')  
  88.   
  89.     deps_to_add = []  
  90.     for s in config_dict.get('solutions', []):  
  91.       try:  
  92.         deps_to_add.append(Dependency(  
  93.             self, s['name'], s['url'],  
  94.             s.get('safesync_url', None),  
  95.             s.get('managed', True),  
  96.             s.get('custom_deps', {}),  
  97.             s.get('custom_vars', {}),  
  98.             s.get('custom_hooks', []),  
  99.             s.get('deps_file', 'DEPS'),  
  100.             True))  
  101.       except KeyError:  
  102.         raise gclient_utils.Error('Invalid .gclient file. Solution is '  
  103.                                   'incomplete: %s' % s)  
  104.     self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))  
  105.     logging.info('SetConfig() done')  
  106.   
  107.   def SaveConfig(self):  
  108.     gclient_utils.FileWrite(os.path.join(self.root_dir,  
  109.                                          self._options.config_filename),  
  110.                             self.config_content)  
  111.   
  112.         
  113.   
  114.   def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):  
  115.     """Runs a command on each dependency in a client and its dependencies.  
  116.   
  117.     Args:  
  118.       command: The command to use (e.g., 'status' or 'diff')  
  119.       args: list of str - extra arguments to add to the command line.  
  120.     """  
  121.     if not self.dependencies:  
  122.       raise gclient_utils.Error('No solution specified')  
  123.     revision_overrides = {}  
  124.     # It's unnecessary to check for revision overrides for 'recurse'.  
  125.     # Save a few seconds by not calling _EnforceRevisions() in that case.  
  126.     if command not in ('diff', 'recurse', 'runhooks', 'status'):  
  127.       revision_overrides = self._EnforceRevisions()  
  128.     pm = None  
  129.     # Disable progress for non-tty stdout.  
  130.     if (sys.stdout.isatty() and not self._options.verbose and progress):  
  131.       if command in ('update', 'revert'):  
  132.         pm = Progress('Syncing projects', 1)  
  133.       elif command == 'recurse':  
  134.         pm = Progress(' '.join(args), 1)  
  135.     work_queue = gclient_utils.ExecutionQueue(  
  136.         self._options.jobs, pm, ignore_requirements=ignore_requirements)  
  137.     for s in self.dependencies:  
  138.       work_queue.enqueue(s)  
  139.     work_queue.flush(revision_overrides, command, args, options=self._options)  
  140.   
  141.     # Once all the dependencies have been processed, it's now safe to run the  
  142.     # hooks.  
  143.     if not self._options.nohooks:  
  144.       self.RunHooksRecursively(self._options)  
  145.   
  146.     if command == 'update':  
  147.       # Notify the user if there is an orphaned entry in their working copy.  
  148.       # Only delete the directory if there are no changes in it, and  
  149.       # delete_unversioned_trees is set to true.  
  150.       entries = [i.name for i in self.root.subtree(False) if i.url]  
  151.       full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))  
  152.                       for e in entries]  
  153.   
  154.       for entry, prev_url in self._ReadEntries().iteritems():  
  155.         if not prev_url:  
  156.           # entry must have been overridden via .gclient custom_deps  
  157.           continue  
  158.         # Fix path separator on Windows.  
  159.         entry_fixed = entry.replace('/', os.path.sep)  
  160.         e_dir = os.path.join(self.root_dir, entry_fixed)  
  161.   
  162.         def _IsParentOfAny(parent, path_list):  
  163.           parent_plus_slash = parent + '/'  
  164.           return any(  
  165.               path[:len(parent_plus_slash)] == parent_plus_slash  
  166.               for path in path_list)  
  167.   
  168.         # Use entry and not entry_fixed there.  
  169.         if (entry not in entries and  
  170.             (not any(path.startswith(entry + '/') for path in entries)) and  
  171.             os.path.exists(e_dir)):  
  172.           scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)  
  173.   
  174.           # Check to see if this directory is now part of a higher-up checkout.  
  175.           if scm.GetCheckoutRoot() in full_entries:  
  176.             logging.info('%s is part of a higher level checkout, not '  
  177.                          'removing.', scm.GetCheckoutRoot())  
  178.             continue  
  179.   
  180.           file_list = []  
  181.           scm.status(self._options, [], file_list)  
  182.           modified_files = file_list != []  
  183.           if (not self._options.delete_unversioned_trees or  
  184.               (modified_files and not self._options.force)):  
  185.             # There are modified files in this entry. Keep warning until  
  186.             # removed.  
  187.             print(('\nWARNING: \'%s\' is no longer part of this client.  '  
  188.                    'It is recommended that you manually remove it.\n') %  
  189.                       entry_fixed)  
  190.           else:  
  191.             # Delete the entry  
  192.             print('\n________ deleting \'%s\' in \'%s\'' % (  
  193.                 entry_fixed, self.root_dir))  
  194.             gclient_utils.rmtree(e_dir)  
  195.       # record the current list of entries for next time  
  196.       self._SaveEntries()  
  197.     return 0   

client.SetConfig()读取配置文件里的solutions,构建dependencies的list变量deps_to_add,然后调用.add_dependencies_and_close,参数就是包含deps_to_add。在add_dependencies_and_close函数里验证deps,如果是有效的,就加入到依赖树中。

在CMDsync函数里,执行client.RunOnDeps()。它将dependengcies的每个任务加入到gclient_tuils.ExecutionQueue队列并执行。完成所有任务后执行runhook。

[plain]  view plain  copy
  1. ######## src/DEPS #############  
  2.   
  3. hooks = [  
  4.   {  
  5.     # A change to a .gyp, .gypi, or to GYP itself should run the generator.  
  6.     "pattern": ".",  
  7.     "action": ["python", "src/build/gyp_chromium"],  
  8.   },  
  9. ]  

上面是src下的DEPS关于hooks的部分,也就是执行完sync命令后,文件更新就执行src/build/gyp_chromium文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值