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

Side by Side Diff: gclient.py

Issue 7918027: Add a --unmanaged flag to gclient config to allow the main solution to be unmanaged by the scm. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: '' Created 9 years, 3 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | gclient_scm.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/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
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
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
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
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
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
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
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:
OLDNEW
« no previous file with comments | « no previous file | gclient_scm.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698