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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « gclient ('k') | gclient_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Meta checkout manager supporting both Subversion and GIT. 6 """Meta checkout manager supporting both Subversion and GIT.
7 7
8 Files 8 Files
9 .gclient : Current client configuration, written by 'config' command. 9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose 10 Format is a Python script defining 'solutions', a list whose
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
44 44
45 Example: 45 Example:
46 hooks = [ 46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", 47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]}, 48 "action": ["python", "image_indexer.py", "--all"]},
49 ] 49 ]
50 """ 50 """
51 51
52 __version__ = "0.4.1" 52 __version__ = "0.4.1"
53 53
54 import copy
54 import errno 55 import errno
55 import logging 56 import logging
56 import optparse 57 import optparse
57 import os 58 import os
58 import pprint 59 import pprint
59 import re 60 import re
60 import subprocess 61 import subprocess
61 import sys 62 import sys
62 import urlparse 63 import urlparse
63 import urllib 64 import urllib
64 65
65 import breakpad 66 import breakpad
66 67
67 import gclient_scm 68 import gclient_scm
68 import gclient_utils 69 import gclient_utils
69 from third_party.repo.progress import Progress
70 70
71 71
72 def attr(attr, data): 72 def attr(attr, data):
73 """Sets an attribute on a function.""" 73 """Sets an attribute on a function."""
74 def hook(fn): 74 def hook(fn):
75 setattr(fn, attr, data) 75 setattr(fn, attr, data)
76 return fn 76 return fn
77 return hook 77 return hook
78 78
79 79
(...skipping 481 matching lines...) Expand 10 before | Expand all | Expand 10 after
561 sol, rev = revision.split('@', 1) 561 sol, rev = revision.split('@', 1)
562 if not sol in solutions_names: 562 if not sol in solutions_names:
563 #raise gclient_utils.Error('%s is not a valid solution.' % sol) 563 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
564 print >> sys.stderr, ('Please fix your script, having invalid ' 564 print >> sys.stderr, ('Please fix your script, having invalid '
565 '--revision flags will soon considered an error.') 565 '--revision flags will soon considered an error.')
566 else: 566 else:
567 revision_overrides[sol] = rev 567 revision_overrides[sol] = rev
568 index += 1 568 index += 1
569 return revision_overrides 569 return revision_overrides
570 570
571 def GetSCMCommandClosure(self, path, url, revision, command, args, file_list,
572 continuation=None):
573 """Gets a closure that runs a SCM command on a particular dependency."""
574 def _Closure():
575 logging.debug("Running %s in %s to %s %s" % (command, path, url,
576 revision))
577 options = copy.copy(self._options)
578 options.revision = revision
579 scm = gclient_scm.CreateSCM(url, self.root_dir(), path)
580 scm.RunCommand(command, options, args, file_list)
581 if continuation:
582 continuation()
583 return _Closure
584
571 def RunOnDeps(self, command, args): 585 def RunOnDeps(self, command, args):
572 """Runs a command on each dependency in a client and its dependencies. 586 """Runs a command on each dependency in a client and its dependencies.
573 587
574 Args: 588 Args:
575 command: The command to use (e.g., 'status' or 'diff') 589 command: The command to use (e.g., 'status' or 'diff')
576 args: list of str - extra arguments to add to the command line. 590 args: list of str - extra arguments to add to the command line.
577 """ 591 """
578 if not self.dependencies: 592 if not self.dependencies:
579 raise gclient_utils.Error('No solution specified') 593 raise gclient_utils.Error('No solution specified')
580 revision_overrides = self._EnforceRevisions() 594 revision_overrides = self._EnforceRevisions()
581 595
582 # When running runhooks --force, there's no need to consult the SCM. 596 # When running runhooks --force, there's no need to consult the SCM.
583 # All known hooks are expected to run unconditionally regardless of working 597 # All known hooks are expected to run unconditionally regardless of working
584 # copy state, so skip the SCM status check. 598 # copy state, so skip the SCM status check.
585 run_scm = not (command == 'runhooks' and self._options.force) 599 run_scm = not (command == 'runhooks' and self._options.force)
586 600
587 entries = {} 601 entries = {}
588 file_list = []
589 # Run on the base solutions first.
590 for solution in self.dependencies:
591 name = solution.name
592 if name in entries:
593 raise gclient_utils.Error("solution %s specified more than once" % name)
594 url = solution.url
595 entries[name] = url
596 if run_scm and url:
597 self._options.revision = revision_overrides.get(name)
598 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
599 scm.RunCommand(command, self._options, args, file_list)
600 file_list = [os.path.join(name, f.strip()) for f in file_list]
601 self._options.revision = None
602 602
603 # Process the dependencies next (sort alphanumerically to ensure that 603 # To avoid threading issues, all file lists get constructed separately then
604 # containing directories get populated first and for readability) 604 # gathered in a flattened list at the end.
605 deps = self._ParseAllDeps(entries) 605 file_list_list = []
606 deps_to_process = deps.keys() 606 file_list_dict = {}
607 deps_to_process.sort()
608 607
609 # First pass for direct dependencies. 608 thread_pool = gclient_utils.ThreadPool(self._options.jobs)
610 if command == 'update' and not self._options.verbose: 609 thread_pool.Start()
611 pm = Progress('Syncing projects', len(deps_to_process))
612 for d in deps_to_process:
613 if command == 'update' and not self._options.verbose:
614 pm.update()
615 if type(deps[d]) == str:
616 url = deps[d]
617 entries[d] = url
618 if run_scm:
619 self._options.revision = revision_overrides.get(d)
620 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
621 scm.RunCommand(command, self._options, args, file_list)
622 self._options.revision = None
623 elif isinstance(deps[d], self.FileImpl):
624 if command in (None, 'cleanup', 'diff', 'pack', 'status'):
625 continue
626 file_dep = deps[d]
627 self._options.revision = file_dep.GetRevision()
628 if run_scm:
629 scm = gclient_scm.SVNWrapper(file_dep.GetPath(), self.root_dir(), d)
630 scm.RunCommand('updatesingle', self._options,
631 args + [file_dep.GetFilename()], file_list)
632 610
633 if command == 'update' and not self._options.verbose: 611 show_progress = command == 'update' and not self._options.verbose
634 pm.end() 612 pm_update = None
635 613
636 # Second pass for inherited deps (via the From keyword) 614 try:
637 for d in deps_to_process: 615 # Run on the base solutions first.
638 if isinstance(deps[d], self.FromImpl): 616 if show_progress:
639 # Getting the URL from the sub_deps file can involve having to resolve 617 pm = gclient_utils.ThreadSafeProgress('Syncing base solutions',
640 # a File() or having to resolve a relative URL. To resolve relative 618 len(self.dependencies))
641 # URLs, we need to pass in the orignal sub deps URL. 619 pm_update = lambda: pm.update()
642 sub_deps_base_url = deps[deps[d].module_name] 620
643 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url 621 for solution in self.dependencies:
644 ).ParseDepsFile(False) 622 name = solution.name
645 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps) 623 if name in entries:
646 entries[d] = url 624 raise gclient_utils.Error("solution %s specified more than once"
647 if run_scm: 625 % name)
648 self._options.revision = revision_overrides.get(d) 626 url = solution.url
649 scm = gclient_scm.CreateSCM(url, self.root_dir(), d) 627 entries[name] = url
650 scm.RunCommand(command, self._options, args, file_list) 628 if run_scm and url:
651 self._options.revision = None 629 revision = revision_overrides.get(name)
630 file_list = []
631 file_list_dict[name] = file_list
632 thread_pool.AddJob(self.GetSCMCommandClosure(
633 name, url, revision, command, args, file_list, pm_update))
634
635 thread_pool.WaitJobs()
636
637 if show_progress:
638 pm.end()
639
640 for solution in self.dependencies:
641 name = solution.name
642 try:
643 file_list_list.append([os.path.join(name, f.strip())
644 for f in file_list_dict[name]])
645 except KeyError:
646 # We may not have added the file list to the dict, see tests above.
647 # Instead of duplicating the tests, it's less fragile to just ignore
648 # the exception.
649 pass
650 try:
651 deps_content = gclient_utils.FileRead(
652 os.path.join(self.root_dir(), name, solution.deps_file))
653 except IOError, e:
654 if e.errno != errno.ENOENT:
655 raise
656 deps_content = ""
657
658 # Process the dependencies next (sort alphanumerically to ensure that
659 # containing directories get populated first and for readability)
660 # TODO(piman): when using multiple threads, the ordering is not ensured.
661 # In many cases (e.g. updates to an existing checkout where DEPS don't
662 # move between directories), it'll still be correct but for completeness
663 # this should be fixed.
664 deps = self._ParseAllDeps(entries)
665 deps_to_process = deps.keys()
666 deps_to_process.sort()
667
668 # First pass for direct dependencies.
669 if show_progress:
670 pm = gclient_utils.ThreadSafeProgress('Syncing projects',
671 len(deps_to_process))
672 pm_update = lambda: pm.update()
673 for d in deps_to_process:
674 file_list = []
675 file_list_list.append(file_list)
676
677 if type(deps[d]) == str:
678 url = deps[d]
679 entries[d] = url
680 if run_scm:
681 revision = revision_overrides.get(d)
682 thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision,
683 command, args,
684 file_list, pm_update))
685 elif isinstance(deps[d], self.FileImpl):
686 if command in (None, 'cleanup', 'diff', 'pack', 'status'):
687 continue
688 file_dep = deps[d]
689 revision = file_dep.GetRevision()
690 if run_scm:
691 thread_pool.AddJob(self.GetSCMCommandClosure(
692 d, file_dep.GetPath(), revision, "updatesingle",
693 args + [file_dep.GetFilename()], file_list))
694
695 thread_pool.WaitJobs()
696
697 if show_progress:
698 pm.end()
699
700 # Second pass for inherited deps (via the From keyword)
701 for d in deps_to_process:
702 if isinstance(deps[d], self.FromImpl):
703 # Getting the URL from the sub_deps file can involve having to resolve
704 # a File() or having to resolve a relative URL. To resolve relative
705 # URLs, we need to pass in the orignal sub deps URL.
706 sub_deps_base_url = deps[deps[d].module_name]
707 sub_deps = Dependency(self, deps[d].module_name,
708 sub_deps_base_url).ParseDepsFile(False)
709 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
710 entries[d] = url
711 if run_scm:
712 revision = revision_overrides.get(d)
713 file_list = []
714 file_list_list.append(file_list)
715 thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision,
716 command, args,
717 file_list))
718
719 thread_pool.WaitJobs()
720
721 finally:
722 thread_pool.Stop()
723
724 file_list = sum(file_list_list, [])
652 725
653 # Convert all absolute paths to relative. 726 # Convert all absolute paths to relative.
654 for i in range(len(file_list)): 727 for i in range(len(file_list)):
655 # TODO(phajdan.jr): We should know exactly when the paths are absolute. 728 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
656 # It depends on the command being executed (like runhooks vs sync). 729 # It depends on the command being executed (like runhooks vs sync).
657 if not os.path.isabs(file_list[i]): 730 if not os.path.isabs(file_list[i]):
658 continue 731 continue
659 732
660 prefix = os.path.commonprefix([self.root_dir().lower(), 733 prefix = os.path.commonprefix([self.root_dir().lower(),
661 file_list[i].lower()]) 734 file_list[i].lower()])
(...skipping 490 matching lines...) Expand 10 before | Expand all | Expand 10 after
1152 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([ 1225 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1153 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip()) 1226 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1154 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')])) 1227 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1155 parser = optparse.OptionParser(version='%prog ' + __version__) 1228 parser = optparse.OptionParser(version='%prog ' + __version__)
1156 parser.add_option('-v', '--verbose', action='count', default=0, 1229 parser.add_option('-v', '--verbose', action='count', default=0,
1157 help='Produces additional output for diagnostics. Can be ' 1230 help='Produces additional output for diagnostics. Can be '
1158 'used up to three times for more logging info.') 1231 'used up to three times for more logging info.')
1159 parser.add_option('--gclientfile', dest='config_filename', 1232 parser.add_option('--gclientfile', dest='config_filename',
1160 default=os.environ.get('GCLIENT_FILE', '.gclient'), 1233 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1161 help='Specify an alternate %default file') 1234 help='Specify an alternate %default file')
1235 parser.add_option("-j", "--jobs", default=0, type="int",
1236 help=("specify how many SCM commands can run in "
1237 "parallel"))
1238
1162 # Integrate standard options processing. 1239 # Integrate standard options processing.
1163 old_parser = parser.parse_args 1240 old_parser = parser.parse_args
1164 def Parse(args): 1241 def Parse(args):
1165 (options, args) = old_parser(args) 1242 (options, args) = old_parser(args)
1166 level = None 1243 level = None
1167 if options.verbose == 2: 1244 if options.verbose == 2:
1168 level = logging.INFO 1245 level = logging.INFO
1169 elif options.verbose > 2: 1246 elif options.verbose > 2:
1170 level = logging.DEBUG 1247 level = logging.DEBUG
1171 logging.basicConfig(level=level, 1248 logging.basicConfig(level=level,
(...skipping 29 matching lines...) Expand all
1201 return CMDhelp(parser, argv) 1278 return CMDhelp(parser, argv)
1202 except gclient_utils.Error, e: 1279 except gclient_utils.Error, e:
1203 print >> sys.stderr, 'Error: %s' % str(e) 1280 print >> sys.stderr, 'Error: %s' % str(e)
1204 return 1 1281 return 1
1205 1282
1206 1283
1207 if '__main__' == __name__: 1284 if '__main__' == __name__:
1208 sys.exit(Main(sys.argv[1:])) 1285 sys.exit(Main(sys.argv[1:]))
1209 1286
1210 # vim: ts=2:sw=2:tw=80:et: 1287 # vim: ts=2:sw=2:tw=80:et:
OLDNEW
« 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