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): |