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, name) | 148 gclient_utils.WorkItem.__init__(self, name) |
149 | 149 |
150 # These are not mutable: | 150 # These are not mutable: |
151 self._parent = parent | 151 self._parent = parent |
152 self._safesync_url = safesync_url | 152 self._safesync_url = safesync_url |
153 self._deps_file = deps_file | 153 self._deps_file = deps_file |
154 self._should_process = should_process | 154 self._should_process = should_process |
155 | 155 |
156 # This is in both .gclient and DEPS files: | 156 # This is in both .gclient and DEPS files: |
157 self.url = url | 157 self.url = url |
158 | 158 |
159 # These are only set in .gclient and not in DEPS files. | 159 # These are only set in .gclient and not in DEPS files. |
| 160 # 'managed' determines whether or not this dependency is synced/updated by |
| 161 # gclient after gclient checks it out initially. The difference between |
| 162 # 'managed' and 'should_process' (defined below) is that the user specifies |
| 163 # 'managed' via the --unmanaged command-line flag or a .gclient config, |
| 164 # where 'should_process' is dynamically set by gclient if it goes over its |
| 165 # recursion limit and controls gclient's behavior so it does not misbehave. |
| 166 self.managed = managed |
160 self.custom_vars = custom_vars or {} | 167 self.custom_vars = custom_vars or {} |
161 self.custom_deps = custom_deps or {} | 168 self.custom_deps = custom_deps or {} |
162 self.deps_hooks = [] | 169 self.deps_hooks = [] |
163 | 170 |
164 # Calculates properties: | 171 # Calculates properties: |
165 self.parsed_url = None | 172 self.parsed_url = None |
166 self.dependencies = [] | 173 self.dependencies = [] |
167 # A cache of the files affected by the current operation, necessary for | 174 # A cache of the files affected by the current operation, necessary for |
168 # hooks. | 175 # hooks. |
169 self._file_list = [] | 176 self._file_list = [] |
(...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 logging.info('Won\'t process duplicate dependency %s' % tree[name]) | 402 logging.info('Won\'t process duplicate dependency %s' % tree[name]) |
396 # In theory we could keep it as a shadow of the other one. In | 403 # In theory we could keep it as a shadow of the other one. In |
397 # practice, simply ignore it. | 404 # practice, simply ignore it. |
398 #should_process = False | 405 #should_process = False |
399 continue | 406 continue |
400 else: | 407 else: |
401 raise gclient_utils.Error( | 408 raise gclient_utils.Error( |
402 'Dependency %s specified more than once:\n %s\nvs\n %s' % | 409 'Dependency %s specified more than once:\n %s\nvs\n %s' % |
403 (name, tree[name].hierarchy(), self.hierarchy())) | 410 (name, tree[name].hierarchy(), self.hierarchy())) |
404 self.dependencies.append(Dependency(self, name, url, None, None, None, | 411 self.dependencies.append(Dependency(self, name, url, None, None, None, |
405 self.deps_file, should_process)) | 412 None, self.deps_file, should_process)) |
406 logging.debug('Loaded: %s' % str(self)) | 413 logging.debug('Loaded: %s' % str(self)) |
407 | 414 |
408 # Arguments number differs from overridden method | 415 # Arguments number differs from overridden method |
409 # pylint: disable=W0221 | 416 # pylint: disable=W0221 |
410 def run(self, revision_overrides, command, args, work_queue, options): | 417 def run(self, revision_overrides, command, args, work_queue, options): |
411 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 418 """Runs 'command' before parsing the DEPS in case it's a initial checkout |
412 or a revert.""" | 419 or a revert.""" |
413 | 420 |
414 def maybeGetParentRevision(options): | 421 def maybeGetParentRevision(options): |
415 """If we are performing an update and --transitive is set, set the | 422 """If we are performing an update and --transitive is set, set the |
(...skipping 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
655 "linux": "unix", | 662 "linux": "unix", |
656 "linux2": "unix", | 663 "linux2": "unix", |
657 "linux3": "unix", | 664 "linux3": "unix", |
658 } | 665 } |
659 | 666 |
660 DEFAULT_CLIENT_FILE_TEXT = ("""\ | 667 DEFAULT_CLIENT_FILE_TEXT = ("""\ |
661 solutions = [ | 668 solutions = [ |
662 { "name" : "%(solution_name)s", | 669 { "name" : "%(solution_name)s", |
663 "url" : "%(solution_url)s", | 670 "url" : "%(solution_url)s", |
664 "deps_file" : "%(deps_file)s", | 671 "deps_file" : "%(deps_file)s", |
| 672 "managed" : %(managed)s, |
665 "custom_deps" : { | 673 "custom_deps" : { |
666 }, | 674 }, |
667 "safesync_url": "%(safesync_url)s", | 675 "safesync_url": "%(safesync_url)s", |
668 }, | 676 }, |
669 ] | 677 ] |
670 """) | 678 """) |
671 | 679 |
672 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ | 680 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ |
673 { "name" : "%(solution_name)s", | 681 { "name" : "%(solution_name)s", |
674 "url" : "%(solution_url)s", | 682 "url" : "%(solution_url)s", |
675 "deps_file" : "%(deps_file)s", | 683 "deps_file" : "%(deps_file)s", |
| 684 "managed" : %(managed)s, |
676 "custom_deps" : { | 685 "custom_deps" : { |
677 %(solution_deps)s }, | 686 %(solution_deps)s }, |
678 "safesync_url": "%(safesync_url)s", | 687 "safesync_url": "%(safesync_url)s", |
679 }, | 688 }, |
680 """) | 689 """) |
681 | 690 |
682 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ | 691 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ |
683 # Snapshot generated with gclient revinfo --snapshot | 692 # Snapshot generated with gclient revinfo --snapshot |
684 solutions = [ | 693 solutions = [ |
685 %(solution_list)s] | 694 %(solution_list)s] |
686 """) | 695 """) |
687 | 696 |
688 def __init__(self, root_dir, options): | 697 def __init__(self, root_dir, options): |
689 # Do not change previous behavior. Only solution level and immediate DEPS | 698 # Do not change previous behavior. Only solution level and immediate DEPS |
690 # are processed. | 699 # are processed. |
691 self._recursion_limit = 2 | 700 self._recursion_limit = 2 |
692 Dependency.__init__(self, None, None, None, None, None, None, 'unused', | 701 Dependency.__init__(self, None, None, None, None, True, None, None, |
693 True) | 702 'unused', True) |
694 self._options = options | 703 self._options = options |
695 if options.deps_os: | 704 if options.deps_os: |
696 enforced_os = options.deps_os.split(',') | 705 enforced_os = options.deps_os.split(',') |
697 else: | 706 else: |
698 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] | 707 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] |
699 if 'all' in enforced_os: | 708 if 'all' in enforced_os: |
700 enforced_os = self.DEPS_OS_CHOICES.itervalues() | 709 enforced_os = self.DEPS_OS_CHOICES.itervalues() |
701 self._enforced_os = tuple(set(enforced_os)) | 710 self._enforced_os = tuple(set(enforced_os)) |
702 self._root_dir = root_dir | 711 self._root_dir = root_dir |
703 self.config_content = None | 712 self.config_content = None |
704 | 713 |
705 def SetConfig(self, content): | 714 def SetConfig(self, content): |
706 assert self.dependencies == [] | 715 assert self.dependencies == [] |
707 config_dict = {} | 716 config_dict = {} |
708 self.config_content = content | 717 self.config_content = content |
709 try: | 718 try: |
710 exec(content, config_dict) | 719 exec(content, config_dict) |
711 except SyntaxError, e: | 720 except SyntaxError, e: |
712 gclient_utils.SyntaxErrorToError('.gclient', e) | 721 gclient_utils.SyntaxErrorToError('.gclient', e) |
713 for s in config_dict.get('solutions', []): | 722 for s in config_dict.get('solutions', []): |
714 try: | 723 try: |
715 tree = dict((d.name, d) for d in self.root.subtree(False)) | 724 tree = dict((d.name, d) for d in self.root.subtree(False)) |
716 if s['name'] in tree: | 725 if s['name'] in tree: |
717 raise gclient_utils.Error( | 726 raise gclient_utils.Error( |
718 'Dependency %s specified more than once in .gclient' % s['name']) | 727 'Dependency %s specified more than once in .gclient' % s['name']) |
719 self.dependencies.append(Dependency( | 728 self.dependencies.append(Dependency( |
720 self, s['name'], s['url'], | 729 self, s['name'], s['url'], |
721 s.get('safesync_url', None), | 730 s.get('safesync_url', None), |
| 731 s.get('managed', True), |
722 s.get('custom_deps', {}), | 732 s.get('custom_deps', {}), |
723 s.get('custom_vars', {}), | 733 s.get('custom_vars', {}), |
724 s.get('deps_file', 'DEPS'), | 734 s.get('deps_file', 'DEPS'), |
725 True)) | 735 True)) |
726 except KeyError: | 736 except KeyError: |
727 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 737 raise gclient_utils.Error('Invalid .gclient file. Solution is ' |
728 'incomplete: %s' % s) | 738 'incomplete: %s' % s) |
729 # .gclient can have hooks. | 739 # .gclient can have hooks. |
730 self.deps_hooks = config_dict.get('hooks', []) | 740 self.deps_hooks = config_dict.get('hooks', []) |
731 self.deps_parsed = True | 741 self.deps_parsed = True |
732 | 742 |
733 def SaveConfig(self): | 743 def SaveConfig(self): |
734 gclient_utils.FileWrite(os.path.join(self.root_dir, | 744 gclient_utils.FileWrite(os.path.join(self.root_dir, |
735 self._options.config_filename), | 745 self._options.config_filename), |
736 self.config_content) | 746 self.config_content) |
737 | 747 |
738 @staticmethod | 748 @staticmethod |
739 def LoadCurrentConfig(options): | 749 def LoadCurrentConfig(options): |
740 """Searches for and loads a .gclient file relative to the current working | 750 """Searches for and loads a .gclient file relative to the current working |
741 dir. Returns a GClient object.""" | 751 dir. Returns a GClient object.""" |
742 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) | 752 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) |
743 if not path: | 753 if not path: |
744 return None | 754 return None |
745 client = GClient(path, options) | 755 client = GClient(path, options) |
746 client.SetConfig(gclient_utils.FileRead( | 756 client.SetConfig(gclient_utils.FileRead( |
747 os.path.join(path, options.config_filename))) | 757 os.path.join(path, options.config_filename))) |
748 return client | 758 return client |
749 | 759 |
750 def SetDefaultConfig(self, solution_name, deps_file, solution_url, | 760 def SetDefaultConfig(self, solution_name, deps_file, solution_url, |
751 safesync_url): | 761 safesync_url, managed=True): |
752 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { | 762 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { |
753 'solution_name': solution_name, | 763 'solution_name': solution_name, |
754 'solution_url': solution_url, | 764 'solution_url': solution_url, |
755 'deps_file': deps_file, | 765 'deps_file': deps_file, |
756 'safesync_url' : safesync_url, | 766 'safesync_url' : safesync_url, |
| 767 'managed': managed, |
757 }) | 768 }) |
758 | 769 |
759 def _SaveEntries(self): | 770 def _SaveEntries(self): |
760 """Creates a .gclient_entries file to record the list of unique checkouts. | 771 """Creates a .gclient_entries file to record the list of unique checkouts. |
761 | 772 |
762 The .gclient_entries file lives in the same directory as .gclient. | 773 The .gclient_entries file lives in the same directory as .gclient. |
763 """ | 774 """ |
764 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It | 775 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It |
765 # makes testing a bit too fun. | 776 # makes testing a bit too fun. |
766 result = 'entries = {\n' | 777 result = 'entries = {\n' |
(...skipping 25 matching lines...) Expand all Loading... |
792 return scope['entries'] | 803 return scope['entries'] |
793 | 804 |
794 def _EnforceRevisions(self): | 805 def _EnforceRevisions(self): |
795 """Checks for revision overrides.""" | 806 """Checks for revision overrides.""" |
796 revision_overrides = {} | 807 revision_overrides = {} |
797 if self._options.head: | 808 if self._options.head: |
798 return revision_overrides | 809 return revision_overrides |
799 # Do not check safesync_url if one or more --revision flag is specified. | 810 # Do not check safesync_url if one or more --revision flag is specified. |
800 if not self._options.revisions: | 811 if not self._options.revisions: |
801 for s in self.dependencies: | 812 for s in self.dependencies: |
802 if not s.safesync_url: | 813 if not s.managed: |
803 continue | 814 self._options.revisions.append('%s@unmanaged' % s.name) |
804 handle = urllib.urlopen(s.safesync_url) | 815 elif s.safesync_url: |
805 rev = handle.read().strip() | 816 handle = urllib.urlopen(s.safesync_url) |
806 handle.close() | 817 rev = handle.read().strip() |
807 if len(rev): | 818 handle.close() |
808 self._options.revisions.append('%s@%s' % (s.name, rev)) | 819 if len(rev): |
| 820 self._options.revisions.append('%s@%s' % (s.name, rev)) |
809 if not self._options.revisions: | 821 if not self._options.revisions: |
810 return revision_overrides | 822 return revision_overrides |
811 solutions_names = [s.name for s in self.dependencies] | 823 solutions_names = [s.name for s in self.dependencies] |
812 index = 0 | 824 index = 0 |
813 for revision in self._options.revisions: | 825 for revision in self._options.revisions: |
814 if not '@' in revision: | 826 if not '@' in revision: |
815 # Support for --revision 123 | 827 # Support for --revision 123 |
816 revision = '%s@%s' % (solutions_names[index], revision) | 828 revision = '%s@%s' % (solutions_names[index], revision) |
817 sol, rev = revision.split('@', 1) | 829 sol, rev = revision.split('@', 1) |
818 if not sol in solutions_names: | 830 if not sol in solutions_names: |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
922 if entries[k]: | 934 if entries[k]: |
923 # Quotes aren't escaped... | 935 # Quotes aren't escaped... |
924 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k])) | 936 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k])) |
925 else: | 937 else: |
926 custom_deps.append(' \"%s\": None,\n' % k) | 938 custom_deps.append(' \"%s\": None,\n' % k) |
927 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % { | 939 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % { |
928 'solution_name': d.name, | 940 'solution_name': d.name, |
929 'solution_url': d.url, | 941 'solution_url': d.url, |
930 'deps_file': d.deps_file, | 942 'deps_file': d.deps_file, |
931 'safesync_url' : d.safesync_url or '', | 943 'safesync_url' : d.safesync_url or '', |
| 944 'managed': d.managed, |
932 'solution_deps': ''.join(custom_deps), | 945 'solution_deps': ''.join(custom_deps), |
933 } | 946 } |
934 # Print the snapshot configuration file | 947 # Print the snapshot configuration file |
935 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) | 948 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) |
936 else: | 949 else: |
937 entries = {} | 950 entries = {} |
938 for d in self.root.subtree(False): | 951 for d in self.root.subtree(False): |
939 if self._options.actual: | 952 if self._options.actual: |
940 entries[d.name] = GetURLAndRev(d) | 953 entries[d.name] = GetURLAndRev(d) |
941 else: | 954 else: |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1041 """ | 1054 """ |
1042 parser.add_option('--spec', | 1055 parser.add_option('--spec', |
1043 help='create a gclient file containing the provided ' | 1056 help='create a gclient file containing the provided ' |
1044 'string. Due to Cygwin/Python brokenness, it ' | 1057 'string. Due to Cygwin/Python brokenness, it ' |
1045 'probably can\'t contain any newlines.') | 1058 'probably can\'t contain any newlines.') |
1046 parser.add_option('--name', | 1059 parser.add_option('--name', |
1047 help='overrides the default name for the solution') | 1060 help='overrides the default name for the solution') |
1048 parser.add_option('--deps-file', default='DEPS', | 1061 parser.add_option('--deps-file', default='DEPS', |
1049 help='overrides the default name for the DEPS file for the' | 1062 help='overrides the default name for the DEPS file for the' |
1050 'main solutions and all sub-dependencies') | 1063 'main solutions and all sub-dependencies') |
| 1064 parser.add_option('--unmanaged', action='store_true', default=False, |
| 1065 help='overrides the default behavior to make it possible ' |
| 1066 'to have the main solution untouched by gclient ' |
| 1067 '(gclient will check out unmanaged dependencies but ' |
| 1068 'will never sync them)') |
1051 parser.add_option('--git-deps', action='store_true', | 1069 parser.add_option('--git-deps', action='store_true', |
1052 help='sets the deps file to ".DEPS.git" instead of "DEPS"') | 1070 help='sets the deps file to ".DEPS.git" instead of "DEPS"') |
1053 (options, args) = parser.parse_args(args) | 1071 (options, args) = parser.parse_args(args) |
1054 if ((options.spec and args) or len(args) > 2 or | 1072 if ((options.spec and args) or len(args) > 2 or |
1055 (not options.spec and not args)): | 1073 (not options.spec and not args)): |
1056 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') | 1074 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') |
1057 | 1075 |
1058 client = GClient('.', options) | 1076 client = GClient('.', options) |
1059 if options.spec: | 1077 if options.spec: |
1060 client.SetConfig(options.spec) | 1078 client.SetConfig(options.spec) |
1061 else: | 1079 else: |
1062 base_url = args[0].rstrip('/') | 1080 base_url = args[0].rstrip('/') |
1063 if not options.name: | 1081 if not options.name: |
1064 name = base_url.split('/')[-1] | 1082 name = base_url.split('/')[-1] |
1065 if name.endswith('.git'): | 1083 if name.endswith('.git'): |
1066 name = name[:-4] | 1084 name = name[:-4] |
1067 else: | 1085 else: |
1068 # specify an alternate relpath for the given URL. | 1086 # specify an alternate relpath for the given URL. |
1069 name = options.name | 1087 name = options.name |
1070 deps_file = options.deps_file | 1088 deps_file = options.deps_file |
1071 if options.git_deps: | 1089 if options.git_deps: |
1072 deps_file = '.DEPS.git' | 1090 deps_file = '.DEPS.git' |
1073 safesync_url = '' | 1091 safesync_url = '' |
1074 if len(args) > 1: | 1092 if len(args) > 1: |
1075 safesync_url = args[1] | 1093 safesync_url = args[1] |
1076 client.SetDefaultConfig(name, deps_file, base_url, safesync_url) | 1094 client.SetDefaultConfig(name, deps_file, base_url, safesync_url, |
| 1095 managed=not options.unmanaged) |
1077 client.SaveConfig() | 1096 client.SaveConfig() |
1078 return 0 | 1097 return 0 |
1079 | 1098 |
1080 | 1099 |
1081 @attr('epilog', """Example: | 1100 @attr('epilog', """Example: |
1082 gclient pack > patch.txt | 1101 gclient pack > patch.txt |
1083 generate simple patch for configured client and dependences | 1102 generate simple patch for configured client and dependences |
1084 """) | 1103 """) |
1085 def CMDpack(parser, args): | 1104 def CMDpack(parser, args): |
1086 """Generate a patch which can be applied at the root of the tree. | 1105 """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... |
1382 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1401 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
1383 print >> sys.stderr, 'Error: %s' % str(e) | 1402 print >> sys.stderr, 'Error: %s' % str(e) |
1384 return 1 | 1403 return 1 |
1385 | 1404 |
1386 | 1405 |
1387 if '__main__' == __name__: | 1406 if '__main__' == __name__: |
1388 fix_encoding.fix_encoding() | 1407 fix_encoding.fix_encoding() |
1389 sys.exit(Main(sys.argv[1:])) | 1408 sys.exit(Main(sys.argv[1:])) |
1390 | 1409 |
1391 # vim: ts=2:sw=2:tw=80:et: | 1410 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |