Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Unified Diff: gclient.py

Issue 1640001: Add -j option to gclient to run parallel updates (Closed)
Patch Set: update to tot Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « gclient ('k') | gclient_utils.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: gclient.py
diff --git a/gclient.py b/gclient.py
index bc912f312f7f2866e15808d7b51650ed46247331..bee5a28fbd354bb25c500533ae130a81bf999605 100644
--- a/gclient.py
+++ b/gclient.py
@@ -51,6 +51,7 @@ Hooks
__version__ = "0.4.1"
+import copy
import errno
import logging
import optparse
@@ -66,7 +67,6 @@ import breakpad
import gclient_scm
import gclient_utils
-from third_party.repo.progress import Progress
def attr(attr, data):
@@ -568,6 +568,20 @@ solutions = [
index += 1
return revision_overrides
+ def GetSCMCommandClosure(self, path, url, revision, command, args, file_list,
+ continuation=None):
+ """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)
+ if continuation:
+ continuation()
+ return _Closure
+
def RunOnDeps(self, command, args):
"""Runs a command on each dependency in a client and its dependencies.
@@ -585,70 +599,129 @@ solutions = [
run_scm = not (command == 'runhooks' and self._options.force)
entries = {}
- file_list = []
- # Run on the base solutions first.
- for solution in self.dependencies:
- name = solution.name
- 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
- # Process the dependencies next (sort alphanumerically to ensure that
- # containing directories get populated first and for readability)
- deps = self._ParseAllDeps(entries)
- 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:
- 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):
- if command in (None, 'cleanup', 'diff', 'pack', 'status'):
- continue
- file_dep = deps[d]
- self._options.revision = file_dep.GetRevision()
- if run_scm:
- scm = gclient_scm.SVNWrapper(file_dep.GetPath(), self.root_dir(), d)
- scm.RunCommand('updatesingle', self._options,
- args + [file_dep.GetFilename()], file_list)
-
- if command == 'update' and not self._options.verbose:
- pm.end()
+ thread_pool = gclient_utils.ThreadPool(self._options.jobs)
+ thread_pool.Start()
- # Second pass for inherited deps (via the From keyword)
- for d in deps_to_process:
- if isinstance(deps[d], self.FromImpl):
- # 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]
- sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
- ).ParseDepsFile(False)
- 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
+ show_progress = command == 'update' and not self._options.verbose
+ pm_update = None
+
+ try:
+ # Run on the base solutions first.
+ if show_progress:
+ pm = gclient_utils.ThreadSafeProgress('Syncing base solutions',
+ len(self.dependencies))
+ pm_update = lambda: pm.update()
+
+ for solution in self.dependencies:
+ name = solution.name
+ 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, pm_update))
+
+ thread_pool.WaitJobs()
+
+ if show_progress:
+ pm.end()
+
+ for solution in self.dependencies:
+ name = solution.name
+ 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
+ try:
+ deps_content = gclient_utils.FileRead(
+ os.path.join(self.root_dir(), name, solution.deps_file))
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ deps_content = ""
+
+ # 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)
+ deps_to_process = deps.keys()
+ deps_to_process.sort()
+
+ # First pass for direct dependencies.
+ if show_progress:
+ pm = gclient_utils.ThreadSafeProgress('Syncing projects',
+ len(deps_to_process))
+ pm_update = lambda: pm.update()
+ for d in deps_to_process:
+ 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, pm_update))
+ elif isinstance(deps[d], self.FileImpl):
+ if command in (None, 'cleanup', 'diff', 'pack', 'status'):
+ continue
+ file_dep = deps[d]
+ revision = file_dep.GetRevision()
+ if run_scm:
+ thread_pool.AddJob(self.GetSCMCommandClosure(
+ d, file_dep.GetPath(), revision, "updatesingle",
+ args + [file_dep.GetFilename()], file_list))
+
+ thread_pool.WaitJobs()
+
+ if show_progress:
+ pm.end()
+
+ # Second pass for inherited deps (via the From keyword)
+ for d in deps_to_process:
+ if isinstance(deps[d], self.FromImpl):
+ # 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]
+ sub_deps = Dependency(self, deps[d].module_name,
+ sub_deps_base_url).ParseDepsFile(False)
+ 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)):
@@ -1159,6 +1232,10 @@ def Main(argv):
parser.add_option('--gclientfile', dest='config_filename',
default=os.environ.get('GCLIENT_FILE', '.gclient'),
help='Specify an alternate %default file')
+ parser.add_option("-j", "--jobs", default=0, type="int",
+ help=("specify how many SCM commands can run in "
+ "parallel"))
+
# Integrate standard options processing.
old_parser = parser.parse_args
def Parse(args):
« no previous file with comments | « gclient ('k') | gclient_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698