| OLD | NEW |
| 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 """A wrapper script to manage a set of client modules in different SCM. | 6 """A wrapper script to manage a set of client modules in different SCM. |
| 7 | 7 |
| 8 This script is intended to be used to help basic management of client | 8 This script is intended to be used to help basic management of client |
| 9 program sources residing in one or more Subversion modules and Git | 9 program sources residing in one or more Subversion modules and Git |
| 10 repositories, along with other modules it depends on, also in Subversion or Git, | 10 repositories, along with other modules it depends on, also in Subversion or Git, |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 Example: | 51 Example: |
| 52 hooks = [ | 52 hooks = [ |
| 53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", | 53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", |
| 54 "action": ["python", "image_indexer.py", "--all"]}, | 54 "action": ["python", "image_indexer.py", "--all"]}, |
| 55 ] | 55 ] |
| 56 """ | 56 """ |
| 57 | 57 |
| 58 __author__ = "darinf@gmail.com (Darin Fisher)" | 58 __author__ = "darinf@gmail.com (Darin Fisher)" |
| 59 __version__ = "0.3.4" | 59 __version__ = "0.3.4" |
| 60 | 60 |
| 61 import copy | |
| 62 import errno | 61 import errno |
| 63 import logging | 62 import logging |
| 64 import optparse | 63 import optparse |
| 65 import os | 64 import os |
| 66 import pprint | 65 import pprint |
| 67 import re | 66 import re |
| 68 import sys | 67 import sys |
| 69 import urlparse | 68 import urlparse |
| 70 import urllib | 69 import urllib |
| 71 | 70 |
| (...skipping 620 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 692 return | 691 return |
| 693 | 692 |
| 694 # Run hooks on the basis of whether the files from the gclient operation | 693 # Run hooks on the basis of whether the files from the gclient operation |
| 695 # match each hook's pattern. | 694 # match each hook's pattern. |
| 696 for hook_dict in hooks: | 695 for hook_dict in hooks: |
| 697 pattern = re.compile(hook_dict['pattern']) | 696 pattern = re.compile(hook_dict['pattern']) |
| 698 matching_file_list = [f for f in file_list if pattern.search(f)] | 697 matching_file_list = [f for f in file_list if pattern.search(f)] |
| 699 if matching_file_list: | 698 if matching_file_list: |
| 700 self._RunHookAction(hook_dict, matching_file_list) | 699 self._RunHookAction(hook_dict, matching_file_list) |
| 701 | 700 |
| 702 def GetSCMCommandClosure(self, path, url, revision, command, args, file_list): | |
| 703 """Gets a closure that runs a SCM command on a particular dependency.""" | |
| 704 def _Closure(): | |
| 705 logging.debug("Running %s in %s to %s %s" % (command, path, url, | |
| 706 revision)) | |
| 707 options = copy.copy(self._options) | |
| 708 options.revision = revision | |
| 709 scm = gclient_scm.CreateSCM(url, self._root_dir, path) | |
| 710 scm.RunCommand(command, options, args, file_list) | |
| 711 return _Closure | |
| 712 | |
| 713 def RunOnDeps(self, command, args): | 701 def RunOnDeps(self, command, args): |
| 714 """Runs a command on each dependency in a client and its dependencies. | 702 """Runs a command on each dependency in a client and its dependencies. |
| 715 | 703 |
| 716 The module's dependencies are specified in its top-level DEPS files. | 704 The module's dependencies are specified in its top-level DEPS files. |
| 717 | 705 |
| 718 Args: | 706 Args: |
| 719 command: The command to use (e.g., 'status' or 'diff') | 707 command: The command to use (e.g., 'status' or 'diff') |
| 720 args: list of str - extra arguments to add to the command line. | 708 args: list of str - extra arguments to add to the command line. |
| 721 | 709 |
| 722 Raises: | 710 Raises: |
| (...skipping 20 matching lines...) Expand all Loading... |
| 743 if not solutions: | 731 if not solutions: |
| 744 raise gclient_utils.Error("No solution specified") | 732 raise gclient_utils.Error("No solution specified") |
| 745 | 733 |
| 746 # When running runhooks --force, there's no need to consult the SCM. | 734 # When running runhooks --force, there's no need to consult the SCM. |
| 747 # All known hooks are expected to run unconditionally regardless of working | 735 # All known hooks are expected to run unconditionally regardless of working |
| 748 # copy state, so skip the SCM status check. | 736 # copy state, so skip the SCM status check. |
| 749 run_scm = not (command == 'runhooks' and self._options.force) | 737 run_scm = not (command == 'runhooks' and self._options.force) |
| 750 | 738 |
| 751 entries = {} | 739 entries = {} |
| 752 entries_deps_content = {} | 740 entries_deps_content = {} |
| 741 file_list = [] |
| 742 # Run on the base solutions first. |
| 743 for solution in solutions: |
| 744 name = solution["name"] |
| 745 deps_file = solution.get("deps_file", self._options.deps_file) |
| 746 if '/' in deps_file or '\\' in deps_file: |
| 747 raise gclient_utils.Error('deps_file name must not be a path, just a ' |
| 748 'filename.') |
| 749 if name in entries: |
| 750 raise gclient_utils.Error("solution %s specified more than once" % name) |
| 751 url = solution["url"] |
| 752 entries[name] = url |
| 753 if run_scm and url: |
| 754 self._options.revision = revision_overrides.get(name) |
| 755 scm = gclient_scm.CreateSCM(url, self._root_dir, name) |
| 756 scm.RunCommand(command, self._options, args, file_list) |
| 757 file_list = [os.path.join(name, f.strip()) for f in file_list] |
| 758 self._options.revision = None |
| 759 try: |
| 760 deps_content = gclient_utils.FileRead( |
| 761 os.path.join(self._root_dir, name, deps_file)) |
| 762 except IOError, e: |
| 763 if e.errno != errno.ENOENT: |
| 764 raise |
| 765 deps_content = "" |
| 766 entries_deps_content[name] = deps_content |
| 753 | 767 |
| 754 # To avoid threading issues, all file lists get constructed separately then | 768 # Process the dependencies next (sort alphanumerically to ensure that |
| 755 # gathered in a flattened list at the end. | 769 # containing directories get populated first and for readability) |
| 756 file_list_list = [] | 770 deps = self._ParseAllDeps(entries, entries_deps_content) |
| 757 file_list_dict = {} | 771 deps_to_process = deps.keys() |
| 772 deps_to_process.sort() |
| 758 | 773 |
| 759 thread_pool = gclient_utils.ThreadPool(self._options.jobs) | 774 # First pass for direct dependencies. |
| 760 thread_pool.Start() | 775 if command == 'update' and not self._options.verbose: |
| 776 pm = Progress('Syncing projects', len(deps_to_process)) |
| 777 for d in deps_to_process: |
| 778 if command == 'update' and not self._options.verbose: |
| 779 pm.update() |
| 780 if type(deps[d]) == str: |
| 781 url = deps[d] |
| 782 entries[d] = url |
| 783 if run_scm: |
| 784 self._options.revision = revision_overrides.get(d) |
| 785 scm = gclient_scm.CreateSCM(url, self._root_dir, d) |
| 786 scm.RunCommand(command, self._options, args, file_list) |
| 787 self._options.revision = None |
| 788 elif isinstance(deps[d], self.FileImpl): |
| 789 file = deps[d] |
| 790 self._options.revision = file.GetRevision() |
| 791 if run_scm: |
| 792 scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d) |
| 793 scm.RunCommand("updatesingle", self._options, |
| 794 args + [file.GetFilename()], file_list) |
| 795 |
| 796 if command == 'update' and not self._options.verbose: |
| 797 pm.end() |
| 761 | 798 |
| 762 try: | 799 # Second pass for inherited deps (via the From keyword) |
| 763 # Run on the base solutions first. | 800 for d in deps_to_process: |
| 764 for solution in solutions: | 801 if isinstance(deps[d], self.FromImpl): |
| 765 name = solution["name"] | 802 filename = os.path.join(self._root_dir, |
| 766 deps_file = solution.get("deps_file", self._options.deps_file) | 803 deps[d].module_name, |
| 767 if '/' in deps_file or '\\' in deps_file: | 804 self._options.deps_file) |
| 768 raise gclient_utils.Error('deps_file name must not be a path, just a ' | 805 content = gclient_utils.FileRead(filename) |
| 769 'filename.') | 806 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {}, |
| 770 if name in entries: | 807 False) |
| 771 raise gclient_utils.Error( | 808 # Getting the URL from the sub_deps file can involve having to resolve |
| 772 "solution %s specified more than once" % name) | 809 # a File() or having to resolve a relative URL. To resolve relative |
| 773 url = solution["url"] | 810 # URLs, we need to pass in the orignal sub deps URL. |
| 774 entries[name] = url | 811 sub_deps_base_url = deps[deps[d].module_name] |
| 775 if run_scm and url: | 812 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps) |
| 776 revision = revision_overrides.get(name) | 813 entries[d] = url |
| 777 file_list = [] | 814 if run_scm: |
| 778 file_list_dict[name] = file_list | 815 self._options.revision = revision_overrides.get(d) |
| 779 thread_pool.AddJob(self.GetSCMCommandClosure( | 816 scm = gclient_scm.CreateSCM(url, self._root_dir, d) |
| 780 name, url, revision, command, args, file_list)) | 817 scm.RunCommand(command, self._options, args, file_list) |
| 781 | 818 self._options.revision = None |
| 782 thread_pool.WaitJobs() | |
| 783 | |
| 784 for solution in solutions: | |
| 785 name = solution["name"] | |
| 786 deps_file = solution.get("deps_file", self._options.deps_file) | |
| 787 try: | |
| 788 deps_content = gclient_utils.FileRead( | |
| 789 os.path.join(self._root_dir, name, deps_file)) | |
| 790 except IOError, e: | |
| 791 if e.errno != errno.ENOENT: | |
| 792 raise | |
| 793 deps_content = "" | |
| 794 entries_deps_content[name] = deps_content | |
| 795 try: | |
| 796 file_list_list.append([os.path.join(name, f.strip()) | |
| 797 for f in file_list_dict[name]]) | |
| 798 except KeyError: | |
| 799 # We may not have added the file list to the dict, see tests above. | |
| 800 # Instead of duplicating the tests, it's less fragile to just ignore | |
| 801 # the exception. | |
| 802 pass | |
| 803 | |
| 804 # Process the dependencies next (sort alphanumerically to ensure that | |
| 805 # containing directories get populated first and for readability) | |
| 806 # TODO(piman): when using multiple threads, the ordering is not ensured. | |
| 807 # In many cases (e.g. updates to an existing checkout where DEPS don't | |
| 808 # move between directories), it'll still be correct but for completeness | |
| 809 # this should be fixed. | |
| 810 deps = self._ParseAllDeps(entries, entries_deps_content) | |
| 811 deps_to_process = deps.keys() | |
| 812 deps_to_process.sort() | |
| 813 | |
| 814 # First pass for direct dependencies. | |
| 815 if command == 'update' and not self._options.verbose: | |
| 816 pm = Progress('Syncing projects', len(deps_to_process)) | |
| 817 | |
| 818 for d in deps_to_process: | |
| 819 if command == 'update' and not self._options.verbose: | |
| 820 pm.update() | |
| 821 file_list = [] | |
| 822 file_list_list.append(file_list) | |
| 823 if type(deps[d]) == str: | |
| 824 url = deps[d] | |
| 825 entries[d] = url | |
| 826 if run_scm: | |
| 827 revision = revision_overrides.get(d) | |
| 828 thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision, | |
| 829 command, args, | |
| 830 file_list)) | |
| 831 elif isinstance(deps[d], self.FileImpl): | |
| 832 file = deps[d] | |
| 833 if run_scm: | |
| 834 revision = file.GetRevision() | |
| 835 thread_pool.AddJob(self.GetSCMCommandClosure( | |
| 836 d, url, revision, "updatesingle", args + [file.GetFilename()], | |
| 837 file_list)) | |
| 838 | |
| 839 thread_pool.WaitJobs() | |
| 840 | |
| 841 if command == 'update' and not self._options.verbose: | |
| 842 pm.end() | |
| 843 | |
| 844 # Second pass for inherited deps (via the From keyword) | |
| 845 for d in deps_to_process: | |
| 846 if isinstance(deps[d], self.FromImpl): | |
| 847 filename = os.path.join(self._root_dir, | |
| 848 deps[d].module_name, | |
| 849 self._options.deps_file) | |
| 850 content = gclient_utils.FileRead(filename) | |
| 851 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {}, | |
| 852 False) | |
| 853 # Getting the URL from the sub_deps file can involve having to resolve | |
| 854 # a File() or having to resolve a relative URL. To resolve relative | |
| 855 # URLs, we need to pass in the orignal sub deps URL. | |
| 856 sub_deps_base_url = deps[deps[d].module_name] | |
| 857 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps) | |
| 858 entries[d] = url | |
| 859 if run_scm: | |
| 860 revision = revision_overrides.get(d) | |
| 861 file_list = [] | |
| 862 file_list_list.append(file_list) | |
| 863 thread_pool.AddJob(self.GetSCMCommandClosure(d, url, revision, | |
| 864 command, args, | |
| 865 file_list)) | |
| 866 | |
| 867 thread_pool.WaitJobs() | |
| 868 finally: | |
| 869 thread_pool.Stop() | |
| 870 | |
| 871 file_list = sum(file_list_list, []) | |
| 872 | 819 |
| 873 # Convert all absolute paths to relative. | 820 # Convert all absolute paths to relative. |
| 874 for i in range(len(file_list)): | 821 for i in range(len(file_list)): |
| 875 # TODO(phajdan.jr): We should know exactly when the paths are absolute. | 822 # TODO(phajdan.jr): We should know exactly when the paths are absolute. |
| 876 # It depends on the command being executed (like runhooks vs sync). | 823 # It depends on the command being executed (like runhooks vs sync). |
| 877 if not os.path.isabs(file_list[i]): | 824 if not os.path.isabs(file_list[i]): |
| 878 continue | 825 continue |
| 879 | 826 |
| 880 prefix = os.path.commonprefix([self._root_dir.lower(), | 827 prefix = os.path.commonprefix([self._root_dir.lower(), |
| 881 file_list[i].lower()]) | 828 file_list[i].lower()]) |
| (...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1330 help=("on update, delete any unexpected " | 1277 help=("on update, delete any unexpected " |
| 1331 "unversioned trees that are in the checkout")) | 1278 "unversioned trees that are in the checkout")) |
| 1332 option_parser.add_option("", "--snapshot", action="store_true", default=False, | 1279 option_parser.add_option("", "--snapshot", action="store_true", default=False, |
| 1333 help=("(revinfo only), create a snapshot file " | 1280 help=("(revinfo only), create a snapshot file " |
| 1334 "of the current version of all repositories")) | 1281 "of the current version of all repositories")) |
| 1335 option_parser.add_option("", "--name", | 1282 option_parser.add_option("", "--name", |
| 1336 help="specify alternate relative solution path") | 1283 help="specify alternate relative solution path") |
| 1337 option_parser.add_option("", "--gclientfile", default=None, | 1284 option_parser.add_option("", "--gclientfile", default=None, |
| 1338 metavar="FILENAME", | 1285 metavar="FILENAME", |
| 1339 help=("specify an alternate .gclient file")) | 1286 help=("specify an alternate .gclient file")) |
| 1340 option_parser.add_option("-j", "--jobs", default=1, type="int", | |
| 1341 help=("specify how many SCM commands can run in " | |
| 1342 "parallel")) | |
| 1343 | 1287 |
| 1344 if len(argv) < 2: | 1288 if len(argv) < 2: |
| 1345 # Users don't need to be told to use the 'help' command. | 1289 # Users don't need to be told to use the 'help' command. |
| 1346 option_parser.print_help() | 1290 option_parser.print_help() |
| 1347 return 1 | 1291 return 1 |
| 1348 # Add manual support for --version as first argument. | 1292 # Add manual support for --version as first argument. |
| 1349 if argv[1] == '--version': | 1293 if argv[1] == '--version': |
| 1350 option_parser.print_version() | 1294 option_parser.print_version() |
| 1351 return 0 | 1295 return 0 |
| 1352 | 1296 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 1377 | 1321 |
| 1378 if "__main__" == __name__: | 1322 if "__main__" == __name__: |
| 1379 try: | 1323 try: |
| 1380 result = Main(sys.argv) | 1324 result = Main(sys.argv) |
| 1381 except gclient_utils.Error, e: | 1325 except gclient_utils.Error, e: |
| 1382 print >> sys.stderr, "Error: %s" % str(e) | 1326 print >> sys.stderr, "Error: %s" % str(e) |
| 1383 result = 1 | 1327 result = 1 |
| 1384 sys.exit(result) | 1328 sys.exit(result) |
| 1385 | 1329 |
| 1386 # vim: ts=2:sw=2:tw=80:et: | 1330 # vim: ts=2:sw=2:tw=80:et: |
| OLD | NEW |