OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
132 if var_name in self._custom_vars: | 132 if var_name in self._custom_vars: |
133 return self._custom_vars[var_name] | 133 return self._custom_vars[var_name] |
134 elif var_name in self._local_scope.get("vars", {}): | 134 elif var_name in self._local_scope.get("vars", {}): |
135 return self._local_scope["vars"][var_name] | 135 return self._local_scope["vars"][var_name] |
136 raise gclient_utils.Error("Var is not defined: %s" % var_name) | 136 raise gclient_utils.Error("Var is not defined: %s" % var_name) |
137 | 137 |
138 | 138 |
139 class Dependency(GClientKeywords, gclient_utils.WorkItem): | 139 class Dependency(GClientKeywords, gclient_utils.WorkItem): |
140 """Object that represents a dependency checkout.""" | 140 """Object that represents a dependency checkout.""" |
141 | 141 |
142 def __init__(self, parent, name, url, safesync_url, custom_deps, | 142 def __init__(self, parent, name, url, safesync_url, managed, custom_deps, |
143 custom_vars, deps_file, should_process): | 143 custom_vars, deps_file, should_process): |
144 # Warning: this function can be called from any thread. Both | 144 # Warning: this function can be called from any thread. Both |
145 # self.dependencies and self.requirements are read and modified from | 145 # self.dependencies and self.requirements are read and modified from |
146 # multiple threads at the same time. Sad. | 146 # multiple threads at the same time. Sad. |
147 GClientKeywords.__init__(self) | 147 GClientKeywords.__init__(self) |
148 gclient_utils.WorkItem.__init__(self) | 148 gclient_utils.WorkItem.__init__(self) |
149 self.parent = parent | 149 self.parent = parent |
150 self.name = name | 150 self.name = name |
151 self.url = url | 151 self.url = url |
152 self.parsed_url = None | 152 self.parsed_url = None |
153 # These 2 are only set in .gclient and not in DEPS files. | 153 # These 2 are only set in .gclient and not in DEPS files. |
154 self.safesync_url = safesync_url | 154 self.safesync_url = safesync_url |
155 self.managed = managed | |
155 self.custom_vars = custom_vars or {} | 156 self.custom_vars = custom_vars or {} |
156 self.custom_deps = custom_deps or {} | 157 self.custom_deps = custom_deps or {} |
157 self.deps_hooks = [] | 158 self.deps_hooks = [] |
158 self.dependencies = [] | 159 self.dependencies = [] |
159 self.deps_file = deps_file | 160 self.deps_file = deps_file |
160 # A cache of the files affected by the current operation, necessary for | 161 # A cache of the files affected by the current operation, necessary for |
161 # hooks. | 162 # hooks. |
162 self._file_list = [] | 163 self._file_list = [] |
163 # If it is not set to True, the dependency wasn't processed for its child | 164 # If it is not set to True, the dependency wasn't processed for its child |
164 # dependency, i.e. its DEPS wasn't read. | 165 # dependency, i.e. its DEPS wasn't read. |
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
384 logging.info('Won\'t process duplicate dependency %s' % tree[name]) | 385 logging.info('Won\'t process duplicate dependency %s' % tree[name]) |
385 # In theory we could keep it as a shadow of the other one. In | 386 # In theory we could keep it as a shadow of the other one. In |
386 # practice, simply ignore it. | 387 # practice, simply ignore it. |
387 #should_process = False | 388 #should_process = False |
388 continue | 389 continue |
389 else: | 390 else: |
390 raise gclient_utils.Error( | 391 raise gclient_utils.Error( |
391 'Dependency %s specified more than once:\n %s\nvs\n %s' % | 392 'Dependency %s specified more than once:\n %s\nvs\n %s' % |
392 (name, tree[name].hierarchy(), self.hierarchy())) | 393 (name, tree[name].hierarchy(), self.hierarchy())) |
393 self.dependencies.append(Dependency(self, name, url, None, None, None, | 394 self.dependencies.append(Dependency(self, name, url, None, None, None, |
394 self.deps_file, should_process)) | 395 None, self.deps_file, should_process)) |
Dirk Pranke
2011/09/16 19:06:35
style nit ... at some point w/ all these None argu
cmp
2011/09/20 01:12:28
I agree, yet I don't want to make that change here
| |
395 logging.debug('Loaded: %s' % str(self)) | 396 logging.debug('Loaded: %s' % str(self)) |
396 | 397 |
397 # Arguments number differs from overridden method | 398 # Arguments number differs from overridden method |
398 # pylint: disable=W0221 | 399 # pylint: disable=W0221 |
399 def run(self, revision_overrides, command, args, work_queue, options): | 400 def run(self, revision_overrides, command, args, work_queue, options): |
400 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 401 """Runs 'command' before parsing the DEPS in case it's a initial checkout |
401 or a revert.""" | 402 or a revert.""" |
402 | 403 |
403 def maybeGetParentRevision(options): | 404 def maybeGetParentRevision(options): |
404 """If we are performing an update and --transitive is set, set the | 405 """If we are performing an update and --transitive is set, set the |
(...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
634 "linux": "unix", | 635 "linux": "unix", |
635 "linux2": "unix", | 636 "linux2": "unix", |
636 "linux3": "unix", | 637 "linux3": "unix", |
637 } | 638 } |
638 | 639 |
639 DEFAULT_CLIENT_FILE_TEXT = ("""\ | 640 DEFAULT_CLIENT_FILE_TEXT = ("""\ |
640 solutions = [ | 641 solutions = [ |
641 { "name" : "%(solution_name)s", | 642 { "name" : "%(solution_name)s", |
642 "url" : "%(solution_url)s", | 643 "url" : "%(solution_url)s", |
643 "deps_file" : "%(deps_file)s", | 644 "deps_file" : "%(deps_file)s", |
645 "managed" : %(managed)s, | |
644 "custom_deps" : { | 646 "custom_deps" : { |
645 }, | 647 }, |
646 "safesync_url": "%(safesync_url)s", | 648 "safesync_url": "%(safesync_url)s", |
647 }, | 649 }, |
648 ] | 650 ] |
649 """) | 651 """) |
650 | 652 |
651 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ | 653 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ |
652 { "name" : "%(solution_name)s", | 654 { "name" : "%(solution_name)s", |
653 "url" : "%(solution_url)s", | 655 "url" : "%(solution_url)s", |
654 "deps_file" : "%(deps_file)s", | 656 "deps_file" : "%(deps_file)s", |
657 "managed" : %(managed)s, | |
655 "custom_deps" : { | 658 "custom_deps" : { |
656 %(solution_deps)s }, | 659 %(solution_deps)s }, |
657 "safesync_url": "%(safesync_url)s", | 660 "safesync_url": "%(safesync_url)s", |
658 }, | 661 }, |
659 """) | 662 """) |
660 | 663 |
661 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ | 664 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ |
662 # Snapshot generated with gclient revinfo --snapshot | 665 # Snapshot generated with gclient revinfo --snapshot |
663 solutions = [ | 666 solutions = [ |
664 %(solution_list)s] | 667 %(solution_list)s] |
665 """) | 668 """) |
666 | 669 |
667 def __init__(self, root_dir, options): | 670 def __init__(self, root_dir, options): |
668 # Do not change previous behavior. Only solution level and immediate DEPS | 671 # Do not change previous behavior. Only solution level and immediate DEPS |
669 # are processed. | 672 # are processed. |
670 self._recursion_limit = 2 | 673 self._recursion_limit = 2 |
671 Dependency.__init__(self, None, None, None, None, None, None, 'unused', | 674 Dependency.__init__(self, None, None, None, None, None, None, None, |
672 True) | 675 'unused', True) |
673 self._options = options | 676 self._options = options |
674 if options.deps_os: | 677 if options.deps_os: |
675 enforced_os = options.deps_os.split(',') | 678 enforced_os = options.deps_os.split(',') |
676 else: | 679 else: |
677 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] | 680 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] |
678 if 'all' in enforced_os: | 681 if 'all' in enforced_os: |
679 enforced_os = self.DEPS_OS_CHOICES.itervalues() | 682 enforced_os = self.DEPS_OS_CHOICES.itervalues() |
680 self._enforced_os = list(set(enforced_os)) | 683 self._enforced_os = list(set(enforced_os)) |
681 self._root_dir = root_dir | 684 self._root_dir = root_dir |
682 self.config_content = None | 685 self.config_content = None |
683 | 686 |
684 def SetConfig(self, content): | 687 def SetConfig(self, content): |
685 assert self.dependencies == [] | 688 assert self.dependencies == [] |
686 config_dict = {} | 689 config_dict = {} |
687 self.config_content = content | 690 self.config_content = content |
688 try: | 691 try: |
689 exec(content, config_dict) | 692 exec(content, config_dict) |
690 except SyntaxError, e: | 693 except SyntaxError, e: |
691 gclient_utils.SyntaxErrorToError('.gclient', e) | 694 gclient_utils.SyntaxErrorToError('.gclient', e) |
692 for s in config_dict.get('solutions', []): | 695 for s in config_dict.get('solutions', []): |
693 try: | 696 try: |
694 tree = dict((d.name, d) for d in self.tree(False)) | 697 tree = dict((d.name, d) for d in self.tree(False)) |
695 if s['name'] in tree: | 698 if s['name'] in tree: |
696 raise gclient_utils.Error( | 699 raise gclient_utils.Error( |
697 'Dependency %s specified more than once in .gclient' % s['name']) | 700 'Dependency %s specified more than once in .gclient' % s['name']) |
698 self.dependencies.append(Dependency( | 701 self.dependencies.append(Dependency( |
699 self, s['name'], s['url'], | 702 self, s['name'], s['url'], |
700 s.get('safesync_url', None), | 703 s.get('safesync_url', None), |
704 s.get('managed', True), | |
701 s.get('custom_deps', {}), | 705 s.get('custom_deps', {}), |
702 s.get('custom_vars', {}), | 706 s.get('custom_vars', {}), |
703 s.get('deps_file', 'DEPS'), | 707 s.get('deps_file', 'DEPS'), |
704 True)) | 708 True)) |
705 except KeyError: | 709 except KeyError: |
706 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 710 raise gclient_utils.Error('Invalid .gclient file. Solution is ' |
707 'incomplete: %s' % s) | 711 'incomplete: %s' % s) |
708 # .gclient can have hooks. | 712 # .gclient can have hooks. |
709 self.deps_hooks = config_dict.get('hooks', []) | 713 self.deps_hooks = config_dict.get('hooks', []) |
710 self.deps_parsed = True | 714 self.deps_parsed = True |
711 | 715 |
712 def SaveConfig(self): | 716 def SaveConfig(self): |
713 gclient_utils.FileWrite(os.path.join(self.root_dir(), | 717 gclient_utils.FileWrite(os.path.join(self.root_dir(), |
714 self._options.config_filename), | 718 self._options.config_filename), |
715 self.config_content) | 719 self.config_content) |
716 | 720 |
717 @staticmethod | 721 @staticmethod |
718 def LoadCurrentConfig(options): | 722 def LoadCurrentConfig(options): |
719 """Searches for and loads a .gclient file relative to the current working | 723 """Searches for and loads a .gclient file relative to the current working |
720 dir. Returns a GClient object.""" | 724 dir. Returns a GClient object.""" |
721 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) | 725 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) |
722 if not path: | 726 if not path: |
723 return None | 727 return None |
724 client = GClient(path, options) | 728 client = GClient(path, options) |
725 client.SetConfig(gclient_utils.FileRead( | 729 client.SetConfig(gclient_utils.FileRead( |
726 os.path.join(path, options.config_filename))) | 730 os.path.join(path, options.config_filename))) |
727 return client | 731 return client |
728 | 732 |
729 def SetDefaultConfig(self, solution_name, deps_file, solution_url, | 733 def SetDefaultConfig(self, solution_name, deps_file, solution_url, |
730 safesync_url): | 734 safesync_url, managed): |
731 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { | 735 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { |
732 'solution_name': solution_name, | 736 'solution_name': solution_name, |
733 'solution_url': solution_url, | 737 'solution_url': solution_url, |
734 'deps_file': deps_file, | 738 'deps_file': deps_file, |
735 'safesync_url' : safesync_url, | 739 'safesync_url' : safesync_url, |
740 'managed': managed, | |
736 }) | 741 }) |
737 | 742 |
738 def _SaveEntries(self): | 743 def _SaveEntries(self): |
739 """Creates a .gclient_entries file to record the list of unique checkouts. | 744 """Creates a .gclient_entries file to record the list of unique checkouts. |
740 | 745 |
741 The .gclient_entries file lives in the same directory as .gclient. | 746 The .gclient_entries file lives in the same directory as .gclient. |
742 """ | 747 """ |
743 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It | 748 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It |
744 # makes testing a bit too fun. | 749 # makes testing a bit too fun. |
745 result = 'entries = {\n' | 750 result = 'entries = {\n' |
(...skipping 25 matching lines...) Expand all Loading... | |
771 return scope['entries'] | 776 return scope['entries'] |
772 | 777 |
773 def _EnforceRevisions(self): | 778 def _EnforceRevisions(self): |
774 """Checks for revision overrides.""" | 779 """Checks for revision overrides.""" |
775 revision_overrides = {} | 780 revision_overrides = {} |
776 if self._options.head: | 781 if self._options.head: |
777 return revision_overrides | 782 return revision_overrides |
778 # Do not check safesync_url if one or more --revision flag is specified. | 783 # Do not check safesync_url if one or more --revision flag is specified. |
779 if not self._options.revisions: | 784 if not self._options.revisions: |
780 for s in self.dependencies: | 785 for s in self.dependencies: |
781 if not s.safesync_url: | 786 if not s.managed: |
782 continue | 787 self._options.revisions.append('%s@unmanaged' % s.name) |
783 handle = urllib.urlopen(s.safesync_url) | 788 elif s.safesync_url: |
784 rev = handle.read().strip() | 789 handle = urllib.urlopen(s.safesync_url) |
785 handle.close() | 790 rev = handle.read().strip() |
786 if len(rev): | 791 handle.close() |
787 self._options.revisions.append('%s@%s' % (s.name, rev)) | 792 if len(rev): |
793 self._options.revisions.append('%s@%s' % (s.name, rev)) | |
788 if not self._options.revisions: | 794 if not self._options.revisions: |
789 return revision_overrides | 795 return revision_overrides |
790 solutions_names = [s.name for s in self.dependencies] | 796 solutions_names = [s.name for s in self.dependencies] |
791 index = 0 | 797 index = 0 |
792 for revision in self._options.revisions: | 798 for revision in self._options.revisions: |
793 if not '@' in revision: | 799 if not '@' in revision: |
794 # Support for --revision 123 | 800 # Support for --revision 123 |
795 revision = '%s@%s' % (solutions_names[index], revision) | 801 revision = '%s@%s' % (solutions_names[index], revision) |
796 sol, rev = revision.split('@', 1) | 802 sol, rev = revision.split('@', 1) |
797 if not sol in solutions_names: | 803 if not sol in solutions_names: |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
901 if entries[k]: | 907 if entries[k]: |
902 # Quotes aren't escaped... | 908 # Quotes aren't escaped... |
903 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k])) | 909 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k])) |
904 else: | 910 else: |
905 custom_deps.append(' \"%s\": None,\n' % k) | 911 custom_deps.append(' \"%s\": None,\n' % k) |
906 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % { | 912 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % { |
907 'solution_name': d.name, | 913 'solution_name': d.name, |
908 'solution_url': d.url, | 914 'solution_url': d.url, |
909 'deps_file': d.deps_file, | 915 'deps_file': d.deps_file, |
910 'safesync_url' : d.safesync_url or '', | 916 'safesync_url' : d.safesync_url or '', |
917 'managed': d.managed, | |
911 'solution_deps': ''.join(custom_deps), | 918 'solution_deps': ''.join(custom_deps), |
912 } | 919 } |
913 # Print the snapshot configuration file | 920 # Print the snapshot configuration file |
914 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) | 921 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) |
915 else: | 922 else: |
916 entries = {} | 923 entries = {} |
917 for d in self.tree(False): | 924 for d in self.tree(False): |
918 if self._options.actual: | 925 if self._options.actual: |
919 entries[d.name] = GetURLAndRev(d) | 926 entries[d.name] = GetURLAndRev(d) |
920 else: | 927 else: |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1021 """ | 1028 """ |
1022 parser.add_option('--spec', | 1029 parser.add_option('--spec', |
1023 help='create a gclient file containing the provided ' | 1030 help='create a gclient file containing the provided ' |
1024 'string. Due to Cygwin/Python brokenness, it ' | 1031 'string. Due to Cygwin/Python brokenness, it ' |
1025 'probably can\'t contain any newlines.') | 1032 'probably can\'t contain any newlines.') |
1026 parser.add_option('--name', | 1033 parser.add_option('--name', |
1027 help='overrides the default name for the solution') | 1034 help='overrides the default name for the solution') |
1028 parser.add_option('--deps-file', default='DEPS', | 1035 parser.add_option('--deps-file', default='DEPS', |
1029 help='overrides the default name for the DEPS file for the' | 1036 help='overrides the default name for the DEPS file for the' |
1030 'main solutions and all sub-dependencies') | 1037 'main solutions and all sub-dependencies') |
1038 parser.add_option('--unmanaged', action='store_true', | |
1039 help='overrides the default behavior to make it possible to' | |
1040 'have the main solution untouched by gclient') | |
1031 parser.add_option('--git-deps', action='store_true', | 1041 parser.add_option('--git-deps', action='store_true', |
1032 help='sets the deps file to ".DEPS.git" instead of "DEPS"') | 1042 help='sets the deps file to ".DEPS.git" instead of "DEPS"') |
1033 (options, args) = parser.parse_args(args) | 1043 (options, args) = parser.parse_args(args) |
1034 if ((options.spec and args) or len(args) > 2 or | 1044 if ((options.spec and args) or len(args) > 2 or |
1035 (not options.spec and not args)): | 1045 (not options.spec and not args)): |
1036 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') | 1046 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') |
1037 | 1047 |
1038 client = GClient('.', options) | 1048 client = GClient('.', options) |
1039 if options.spec: | 1049 if options.spec: |
1040 client.SetConfig(options.spec) | 1050 client.SetConfig(options.spec) |
1041 else: | 1051 else: |
1042 base_url = args[0].rstrip('/') | 1052 base_url = args[0].rstrip('/') |
1043 if not options.name: | 1053 if not options.name: |
1044 name = base_url.split('/')[-1] | 1054 name = base_url.split('/')[-1] |
1045 if name.endswith('.git'): | 1055 if name.endswith('.git'): |
1046 name = name[:-4] | 1056 name = name[:-4] |
1047 else: | 1057 else: |
1048 # specify an alternate relpath for the given URL. | 1058 # specify an alternate relpath for the given URL. |
1049 name = options.name | 1059 name = options.name |
1050 deps_file = options.deps_file | 1060 deps_file = options.deps_file |
1051 if options.git_deps: | 1061 if options.git_deps: |
1052 deps_file = '.DEPS.git' | 1062 deps_file = '.DEPS.git' |
1063 managed = True | |
1064 if options.unmanaged: | |
1065 managed = False | |
Dirk Pranke
2011/09/16 19:06:35
Why not just use default=False above when you defi
cmp
2011/09/20 01:12:28
Good idea, done.
| |
1053 safesync_url = '' | 1066 safesync_url = '' |
1054 if len(args) > 1: | 1067 if len(args) > 1: |
1055 safesync_url = args[1] | 1068 safesync_url = args[1] |
1056 client.SetDefaultConfig(name, deps_file, base_url, safesync_url) | 1069 client.SetDefaultConfig(name, deps_file, base_url, safesync_url, managed) |
M-A Ruel
2011/09/16 18:58:23
s/managed/not options.unmanaged/ and replace lines
Dirk Pranke
2011/09/16 19:06:35
Actually, I would specify default=False and just d
cmp
2011/09/20 01:12:28
I implemented Dirk's suggestion (which is like thi
cmp
2011/09/20 01:12:28
Done.
| |
1057 client.SaveConfig() | 1070 client.SaveConfig() |
1058 return 0 | 1071 return 0 |
1059 | 1072 |
1060 | 1073 |
1061 @attr('epilog', """Example: | 1074 @attr('epilog', """Example: |
1062 gclient pack > patch.txt | 1075 gclient pack > patch.txt |
1063 generate simple patch for configured client and dependences | 1076 generate simple patch for configured client and dependences |
1064 """) | 1077 """) |
1065 def CMDpack(parser, args): | 1078 def CMDpack(parser, args): |
1066 """Generate a patch which can be applied at the root of the tree. | 1079 """Generate a patch which can be applied at the root of the tree. |
(...skipping 295 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1362 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1375 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
1363 print >> sys.stderr, 'Error: %s' % str(e) | 1376 print >> sys.stderr, 'Error: %s' % str(e) |
1364 return 1 | 1377 return 1 |
1365 | 1378 |
1366 | 1379 |
1367 if '__main__' == __name__: | 1380 if '__main__' == __name__: |
1368 fix_encoding.fix_encoding() | 1381 fix_encoding.fix_encoding() |
1369 sys.exit(Main(sys.argv[1:])) | 1382 sys.exit(Main(sys.argv[1:])) |
1370 | 1383 |
1371 # vim: ts=2:sw=2:tw=80:et: | 1384 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |