patch from issue 1640001
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@45652 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gclient.py b/gclient.py
index 48a940f..519019e 100644
--- a/gclient.py
+++ b/gclient.py
@@ -58,6 +58,7 @@
__author__ = "[email protected] (Darin Fisher)"
__version__ = "0.3.4"
+import copy
import errno
import logging
import optparse
@@ -698,6 +699,17 @@
if matching_file_list:
self._RunHookAction(hook_dict, matching_file_list)
+ def GetSCMCommandClosure(self, path, url, revision, command, args, file_list):
+ """Gets a closure that runs a SCM command on a particular dependency."""
+ def _Closure():
+ logging.debug("Running %s in %s to %s %s" % (command, path, url,
+ revision))
+ options = copy.copy(self._options)
+ options.revision = revision
+ scm = gclient_scm.CreateSCM(url, self._root_dir, path)
+ scm.RunCommand(command, options, args, file_list)
+ return _Closure
+
def RunOnDeps(self, command, args):
"""Runs a command on each dependency in a client and its dependencies.
@@ -738,84 +750,125 @@
entries = {}
entries_deps_content = {}
- file_list = []
- # Run on the base solutions first.
- for solution in solutions:
- name = solution["name"]
- deps_file = solution.get("deps_file", self._options.deps_file)
- if '/' in deps_file or '\\' in deps_file:
- raise gclient_utils.Error('deps_file name must not be a path, just a '
- 'filename.')
- if name in entries:
- raise gclient_utils.Error("solution %s specified more than once" % name)
- url = solution["url"]
- entries[name] = url
- if run_scm and url:
- self._options.revision = revision_overrides.get(name)
- scm = gclient_scm.CreateSCM(url, self._root_dir, name)
- scm.RunCommand(command, self._options, args, file_list)
- file_list = [os.path.join(name, f.strip()) for f in file_list]
- self._options.revision = None
- try:
- deps_content = gclient_utils.FileRead(
- os.path.join(self._root_dir, name, deps_file))
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise
- deps_content = ""
- entries_deps_content[name] = deps_content
- # Process the dependencies next (sort alphanumerically to ensure that
- # containing directories get populated first and for readability)
- deps = self._ParseAllDeps(entries, entries_deps_content)
- deps_to_process = deps.keys()
- deps_to_process.sort()
+ # To avoid threading issues, all file lists get constructed separately then
+ # gathered in a flattened list at the end.
+ file_list_list = []
+ file_list_dict = {}
- # First pass for direct dependencies.
- if command == 'update' and not self._options.verbose:
- pm = Progress('Syncing projects', len(deps_to_process))
- for d in deps_to_process:
+ thread_pool = gclient_utils.ThreadPool(self._options.jobs)
+ thread_pool.Start()
+
+ try:
+ # Run on the base solutions first.
+ for solution in solutions:
+ name = solution["name"]
+ deps_file = solution.get("deps_file", self._options.deps_file)
+ if '/' in deps_file or '\\' in deps_file:
+ raise gclient_utils.Error('deps_file name must not be a path, just a '
+ 'filename.')
+ if name in entries:
+ raise gclient_utils.Error(
+ "solution %s specified more than once" % name)
+ url = solution["url"]
+ entries[name] = url
+ if run_scm and url:
+ revision = revision_overrides.get(name)
+ file_list = []
+ file_list_dict[name] = file_list
+ thread_pool.AddJob(self.GetSCMCommandClosure(
+ name, url, revision, command, args, file_list))
+
+ thread_pool.WaitJobs()
+
+ for solution in solutions:
+ name = solution["name"]
+ deps_file = solution.get("deps_file", self._options.deps_file)
+ try:
+ deps_content = gclient_utils.FileRead(
+ os.path.join(self._root_dir, name, deps_file))
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ deps_content = ""
+ entries_deps_content[name] = deps_content
+ try:
+ file_list_list.append([os.path.join(name, f.strip())
+ for f in file_list_dict[name]])
+ except KeyError:
+ # We may not have added the file list to the dict, see tests above.
+ # Instead of duplicating the tests, it's less fragile to just ignore
+ # the exception.
+ pass
+
+ # Process the dependencies next (sort alphanumerically to ensure that
+ # containing directories get populated first and for readability)
+ # TODO(piman): when using multiple threads, the ordering is not ensured.
+ # In many cases (e.g. updates to an existing checkout where DEPS don't
+ # move between directories), it'll still be correct but for completeness
+ # this should be fixed.
+ deps = self._ParseAllDeps(entries, entries_deps_content)
+ deps_to_process = deps.keys()
+ deps_to_process.sort()
+
+ # First pass for direct dependencies.
if command == 'update' and not self._options.verbose:
- pm.update()
- if type(deps[d]) == str:
- url = deps[d]
- entries[d] = url
- if run_scm:
- self._options.revision = revision_overrides.get(d)
- scm = gclient_scm.CreateSCM(url, self._root_dir, d)
- scm.RunCommand(command, self._options, args, file_list)
- self._options.revision = None
- elif isinstance(deps[d], self.FileImpl):
- file = deps[d]
- self._options.revision = file.GetRevision()
- if run_scm:
- scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d)
- scm.RunCommand("updatesingle", self._options,
- args + [file.GetFilename()], file_list)
-
- if command == 'update' and not self._options.verbose:
- pm.end()
+ pm = Progress('Syncing projects', len(deps_to_process))
- # Second pass for inherited deps (via the From keyword)
- for d in deps_to_process:
- if isinstance(deps[d], self.FromImpl):
- filename = os.path.join(self._root_dir,
- deps[d].module_name,
- self._options.deps_file)
- content = gclient_utils.FileRead(filename)
- sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
- False)
- # Getting the URL from the sub_deps file can involve having to resolve
- # a File() or having to resolve a relative URL. To resolve relative
- # URLs, we need to pass in the orignal sub deps URL.
- sub_deps_base_url = deps[deps[d].module_name]
- url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
- entries[d] = url
- if run_scm:
- self._options.revision = revision_overrides.get(d)
- scm = gclient_scm.CreateSCM(url, self._root_dir, d)
- scm.RunCommand(command, self._options, args, file_list)
- self._options.revision = None
+ for d in deps_to_process:
+ if command == 'update' and not self._options.verbose:
+ pm.update()
+ file_list = []
+ file_list_list.append(file_list)
+ if type(deps[d]) == str:
+ url = deps[d]
+ entries[d] = url
+ if run_scm:
+ revision = revision_overrides.get(d)
+ thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision,
+ command, args,
+ file_list))
+ elif isinstance(deps[d], self.FileImpl):
+ file = deps[d]
+ if run_scm:
+ revision = file.GetRevision()
+ thread_pool.AddJob(self.GetSCMCommandClosure(
+ d, url, revision, "updatesingle", args + [file.GetFilename()],
+ file_list))
+
+ thread_pool.WaitJobs()
+
+ if command == 'update' and not self._options.verbose:
+ pm.end()
+
+ # Second pass for inherited deps (via the From keyword)
+ for d in deps_to_process:
+ if isinstance(deps[d], self.FromImpl):
+ filename = os.path.join(self._root_dir,
+ deps[d].module_name,
+ self._options.deps_file)
+ content = gclient_utils.FileRead(filename)
+ sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
+ False)
+ # Getting the URL from the sub_deps file can involve having to resolve
+ # a File() or having to resolve a relative URL. To resolve relative
+ # URLs, we need to pass in the orignal sub deps URL.
+ sub_deps_base_url = deps[deps[d].module_name]
+ url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
+ entries[d] = url
+ if run_scm:
+ revision = revision_overrides.get(d)
+ file_list = []
+ file_list_list.append(file_list)
+ thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision,
+ command, args,
+ file_list))
+
+ thread_pool.WaitJobs()
+ finally:
+ thread_pool.Stop()
+
+ file_list = sum(file_list_list, [])
# Convert all absolute paths to relative.
for i in range(len(file_list)):
@@ -1284,6 +1337,9 @@
option_parser.add_option("", "--gclientfile", default=None,
metavar="FILENAME",
help=("specify an alternate .gclient file"))
+ option_parser.add_option("-j", "--jobs", default=1, type="int",
+ help=("specify how many SCM commands can run in "
+ "parallel"))
if len(argv) < 2:
# Users don't need to be told to use the 'help' command.