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

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') | gclient_scm.py » ('J')
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 self.parent = parent 149 self.parent = parent
150 self.url = url 150 self.url = url
151 self.parsed_url = None 151 self.parsed_url = None
152 # These 2 are only set in .gclient and not in DEPS files. 152 # These 2 are only set in .gclient and not in DEPS files.
153 self.safesync_url = safesync_url 153 self.safesync_url = safesync_url
154 self.managed = managed
154 self.custom_vars = custom_vars or {} 155 self.custom_vars = custom_vars or {}
155 self.custom_deps = custom_deps or {} 156 self.custom_deps = custom_deps or {}
156 self.deps_hooks = [] 157 self.deps_hooks = []
157 self.dependencies = [] 158 self.dependencies = []
158 self.deps_file = deps_file 159 self.deps_file = deps_file
159 # A cache of the files affected by the current operation, necessary for 160 # A cache of the files affected by the current operation, necessary for
160 # hooks. 161 # hooks.
161 self._file_list = [] 162 self._file_list = []
162 # If it is not set to True, the dependency wasn't processed for its child 163 # If it is not set to True, the dependency wasn't processed for its child
163 # dependency, i.e. its DEPS wasn't read. 164 # dependency, i.e. its DEPS wasn't read.
164 self.deps_parsed = False 165 self.deps_parsed = False
165 # This dependency should be processed, i.e. checked out 166 # This dependency should be processed, i.e. checked out
166 self.should_process = should_process 167 self.should_process = should_process
M-A Ruel 2011/09/20 17:39:34 It's sad that 'managed' has a meaning so near to '
cmp 2011/09/20 18:52:13 Done.
167 # This dependency has been processed, i.e. checked out 168 # This dependency has been processed, i.e. checked out
168 self.processed = False 169 self.processed = False
169 # This dependency had its hook run 170 # This dependency had its hook run
170 self.hooks_ran = False 171 self.hooks_ran = False
171 172
172 # Post process the url to remove trailing slashes. 173 # Post process the url to remove trailing slashes.
173 if isinstance(self.url, basestring): 174 if isinstance(self.url, basestring):
174 # urls are sometime incorrectly written as proto://host/path/@rev. Replace 175 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
175 # it to proto://host/path@rev. 176 # it to proto://host/path@rev.
176 if self.url.count('@') > 1: 177 if self.url.count('@') > 1:
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 logging.info('Won\'t process duplicate dependency %s' % tree[name]) 388 logging.info('Won\'t process duplicate dependency %s' % tree[name])
388 # In theory we could keep it as a shadow of the other one. In 389 # In theory we could keep it as a shadow of the other one. In
389 # practice, simply ignore it. 390 # practice, simply ignore it.
390 #should_process = False 391 #should_process = False
391 continue 392 continue
392 else: 393 else:
393 raise gclient_utils.Error( 394 raise gclient_utils.Error(
394 'Dependency %s specified more than once:\n %s\nvs\n %s' % 395 'Dependency %s specified more than once:\n %s\nvs\n %s' %
395 (name, tree[name].hierarchy(), self.hierarchy())) 396 (name, tree[name].hierarchy(), self.hierarchy()))
396 self.dependencies.append(Dependency(self, name, url, None, None, None, 397 self.dependencies.append(Dependency(self, name, url, None, None, None,
397 self.deps_file, should_process)) 398 None, self.deps_file, should_process))
398 logging.debug('Loaded: %s' % str(self)) 399 logging.debug('Loaded: %s' % str(self))
399 400
400 # Arguments number differs from overridden method 401 # Arguments number differs from overridden method
401 # pylint: disable=W0221 402 # pylint: disable=W0221
402 def run(self, revision_overrides, command, args, work_queue, options): 403 def run(self, revision_overrides, command, args, work_queue, options):
403 """Runs 'command' before parsing the DEPS in case it's a initial checkout 404 """Runs 'command' before parsing the DEPS in case it's a initial checkout
404 or a revert.""" 405 or a revert."""
405 406
406 def maybeGetParentRevision(options): 407 def maybeGetParentRevision(options):
407 """If we are performing an update and --transitive is set, set the 408 """If we are performing an update and --transitive is set, set the
(...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after
637 "linux": "unix", 638 "linux": "unix",
638 "linux2": "unix", 639 "linux2": "unix",
639 "linux3": "unix", 640 "linux3": "unix",
640 } 641 }
641 642
642 DEFAULT_CLIENT_FILE_TEXT = ("""\ 643 DEFAULT_CLIENT_FILE_TEXT = ("""\
643 solutions = [ 644 solutions = [
644 { "name" : "%(solution_name)s", 645 { "name" : "%(solution_name)s",
645 "url" : "%(solution_url)s", 646 "url" : "%(solution_url)s",
646 "deps_file" : "%(deps_file)s", 647 "deps_file" : "%(deps_file)s",
648 "managed" : %(managed)s,
647 "custom_deps" : { 649 "custom_deps" : {
648 }, 650 },
649 "safesync_url": "%(safesync_url)s", 651 "safesync_url": "%(safesync_url)s",
650 }, 652 },
651 ] 653 ]
652 """) 654 """)
653 655
654 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ 656 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
655 { "name" : "%(solution_name)s", 657 { "name" : "%(solution_name)s",
656 "url" : "%(solution_url)s", 658 "url" : "%(solution_url)s",
657 "deps_file" : "%(deps_file)s", 659 "deps_file" : "%(deps_file)s",
660 "managed" : %(managed)s,
658 "custom_deps" : { 661 "custom_deps" : {
659 %(solution_deps)s }, 662 %(solution_deps)s },
660 "safesync_url": "%(safesync_url)s", 663 "safesync_url": "%(safesync_url)s",
661 }, 664 },
662 """) 665 """)
663 666
664 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ 667 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
665 # Snapshot generated with gclient revinfo --snapshot 668 # Snapshot generated with gclient revinfo --snapshot
666 solutions = [ 669 solutions = [
667 %(solution_list)s] 670 %(solution_list)s]
668 """) 671 """)
669 672
670 def __init__(self, root_dir, options): 673 def __init__(self, root_dir, options):
671 # Do not change previous behavior. Only solution level and immediate DEPS 674 # Do not change previous behavior. Only solution level and immediate DEPS
672 # are processed. 675 # are processed.
673 self._recursion_limit = 2 676 self._recursion_limit = 2
674 Dependency.__init__(self, None, None, None, None, None, None, 'unused', 677 Dependency.__init__(self, None, None, None, None, None, None, None,
M-A Ruel 2011/09/20 17:39:34 True instead of None?
cmp 2011/09/20 18:52:13 Done.
675 True) 678 'unused', True)
676 self._options = options 679 self._options = options
677 if options.deps_os: 680 if options.deps_os:
678 enforced_os = options.deps_os.split(',') 681 enforced_os = options.deps_os.split(',')
679 else: 682 else:
680 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] 683 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
681 if 'all' in enforced_os: 684 if 'all' in enforced_os:
682 enforced_os = self.DEPS_OS_CHOICES.itervalues() 685 enforced_os = self.DEPS_OS_CHOICES.itervalues()
683 self._enforced_os = list(set(enforced_os)) 686 self._enforced_os = list(set(enforced_os))
684 self._root_dir = root_dir 687 self._root_dir = root_dir
685 self.config_content = None 688 self.config_content = None
686 689
687 def SetConfig(self, content): 690 def SetConfig(self, content):
688 assert self.dependencies == [] 691 assert self.dependencies == []
689 config_dict = {} 692 config_dict = {}
690 self.config_content = content 693 self.config_content = content
691 try: 694 try:
692 exec(content, config_dict) 695 exec(content, config_dict)
693 except SyntaxError, e: 696 except SyntaxError, e:
694 gclient_utils.SyntaxErrorToError('.gclient', e) 697 gclient_utils.SyntaxErrorToError('.gclient', e)
695 for s in config_dict.get('solutions', []): 698 for s in config_dict.get('solutions', []):
696 try: 699 try:
697 tree = dict((d.name, d) for d in self.tree(False)) 700 tree = dict((d.name, d) for d in self.tree(False))
698 if s['name'] in tree: 701 if s['name'] in tree:
699 raise gclient_utils.Error( 702 raise gclient_utils.Error(
700 'Dependency %s specified more than once in .gclient' % s['name']) 703 'Dependency %s specified more than once in .gclient' % s['name'])
701 self.dependencies.append(Dependency( 704 self.dependencies.append(Dependency(
702 self, s['name'], s['url'], 705 self, s['name'], s['url'],
703 s.get('safesync_url', None), 706 s.get('safesync_url', None),
707 s.get('managed', True),
704 s.get('custom_deps', {}), 708 s.get('custom_deps', {}),
705 s.get('custom_vars', {}), 709 s.get('custom_vars', {}),
706 s.get('deps_file', 'DEPS'), 710 s.get('deps_file', 'DEPS'),
707 True)) 711 True))
708 except KeyError: 712 except KeyError:
709 raise gclient_utils.Error('Invalid .gclient file. Solution is ' 713 raise gclient_utils.Error('Invalid .gclient file. Solution is '
710 'incomplete: %s' % s) 714 'incomplete: %s' % s)
711 # .gclient can have hooks. 715 # .gclient can have hooks.
712 self.deps_hooks = config_dict.get('hooks', []) 716 self.deps_hooks = config_dict.get('hooks', [])
713 self.deps_parsed = True 717 self.deps_parsed = True
714 718
715 def SaveConfig(self): 719 def SaveConfig(self):
716 gclient_utils.FileWrite(os.path.join(self.root_dir(), 720 gclient_utils.FileWrite(os.path.join(self.root_dir(),
717 self._options.config_filename), 721 self._options.config_filename),
718 self.config_content) 722 self.config_content)
719 723
720 @staticmethod 724 @staticmethod
721 def LoadCurrentConfig(options): 725 def LoadCurrentConfig(options):
722 """Searches for and loads a .gclient file relative to the current working 726 """Searches for and loads a .gclient file relative to the current working
723 dir. Returns a GClient object.""" 727 dir. Returns a GClient object."""
724 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) 728 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
725 if not path: 729 if not path:
726 return None 730 return None
727 client = GClient(path, options) 731 client = GClient(path, options)
728 client.SetConfig(gclient_utils.FileRead( 732 client.SetConfig(gclient_utils.FileRead(
729 os.path.join(path, options.config_filename))) 733 os.path.join(path, options.config_filename)))
730 return client 734 return client
731 735
732 def SetDefaultConfig(self, solution_name, deps_file, solution_url, 736 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
733 safesync_url): 737 safesync_url, managed=True):
734 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { 738 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
735 'solution_name': solution_name, 739 'solution_name': solution_name,
736 'solution_url': solution_url, 740 'solution_url': solution_url,
737 'deps_file': deps_file, 741 'deps_file': deps_file,
738 'safesync_url' : safesync_url, 742 'safesync_url' : safesync_url,
743 'managed': managed,
739 }) 744 })
740 745
741 def _SaveEntries(self): 746 def _SaveEntries(self):
742 """Creates a .gclient_entries file to record the list of unique checkouts. 747 """Creates a .gclient_entries file to record the list of unique checkouts.
743 748
744 The .gclient_entries file lives in the same directory as .gclient. 749 The .gclient_entries file lives in the same directory as .gclient.
745 """ 750 """
746 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It 751 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
747 # makes testing a bit too fun. 752 # makes testing a bit too fun.
748 result = 'entries = {\n' 753 result = 'entries = {\n'
(...skipping 25 matching lines...) Expand all
774 return scope['entries'] 779 return scope['entries']
775 780
776 def _EnforceRevisions(self): 781 def _EnforceRevisions(self):
777 """Checks for revision overrides.""" 782 """Checks for revision overrides."""
778 revision_overrides = {} 783 revision_overrides = {}
779 if self._options.head: 784 if self._options.head:
780 return revision_overrides 785 return revision_overrides
781 # Do not check safesync_url if one or more --revision flag is specified. 786 # Do not check safesync_url if one or more --revision flag is specified.
782 if not self._options.revisions: 787 if not self._options.revisions:
783 for s in self.dependencies: 788 for s in self.dependencies:
784 if not s.safesync_url: 789 if not s.managed:
785 continue 790 self._options.revisions.append('%s@unmanaged' % s.name)
786 handle = urllib.urlopen(s.safesync_url) 791 elif s.safesync_url:
787 rev = handle.read().strip() 792 handle = urllib.urlopen(s.safesync_url)
788 handle.close() 793 rev = handle.read().strip()
789 if len(rev): 794 handle.close()
790 self._options.revisions.append('%s@%s' % (s.name, rev)) 795 if len(rev):
796 self._options.revisions.append('%s@%s' % (s.name, rev))
791 if not self._options.revisions: 797 if not self._options.revisions:
792 return revision_overrides 798 return revision_overrides
793 solutions_names = [s.name for s in self.dependencies] 799 solutions_names = [s.name for s in self.dependencies]
794 index = 0 800 index = 0
795 for revision in self._options.revisions: 801 for revision in self._options.revisions:
796 if not '@' in revision: 802 if not '@' in revision:
797 # Support for --revision 123 803 # Support for --revision 123
798 revision = '%s@%s' % (solutions_names[index], revision) 804 revision = '%s@%s' % (solutions_names[index], revision)
799 sol, rev = revision.split('@', 1) 805 sol, rev = revision.split('@', 1)
800 if not sol in solutions_names: 806 if not sol in solutions_names:
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
904 if entries[k]: 910 if entries[k]:
905 # Quotes aren't escaped... 911 # Quotes aren't escaped...
906 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k])) 912 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
907 else: 913 else:
908 custom_deps.append(' \"%s\": None,\n' % k) 914 custom_deps.append(' \"%s\": None,\n' % k)
909 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % { 915 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
910 'solution_name': d.name, 916 'solution_name': d.name,
911 'solution_url': d.url, 917 'solution_url': d.url,
912 'deps_file': d.deps_file, 918 'deps_file': d.deps_file,
913 'safesync_url' : d.safesync_url or '', 919 'safesync_url' : d.safesync_url or '',
920 'managed': d.managed,
914 'solution_deps': ''.join(custom_deps), 921 'solution_deps': ''.join(custom_deps),
915 } 922 }
916 # Print the snapshot configuration file 923 # Print the snapshot configuration file
917 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}) 924 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
918 else: 925 else:
919 entries = {} 926 entries = {}
920 for d in self.tree(False): 927 for d in self.tree(False):
921 if self._options.actual: 928 if self._options.actual:
922 entries[d.name] = GetURLAndRev(d) 929 entries[d.name] = GetURLAndRev(d)
923 else: 930 else:
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
1024 """ 1031 """
1025 parser.add_option('--spec', 1032 parser.add_option('--spec',
1026 help='create a gclient file containing the provided ' 1033 help='create a gclient file containing the provided '
1027 'string. Due to Cygwin/Python brokenness, it ' 1034 'string. Due to Cygwin/Python brokenness, it '
1028 'probably can\'t contain any newlines.') 1035 'probably can\'t contain any newlines.')
1029 parser.add_option('--name', 1036 parser.add_option('--name',
1030 help='overrides the default name for the solution') 1037 help='overrides the default name for the solution')
1031 parser.add_option('--deps-file', default='DEPS', 1038 parser.add_option('--deps-file', default='DEPS',
1032 help='overrides the default name for the DEPS file for the' 1039 help='overrides the default name for the DEPS file for the'
1033 'main solutions and all sub-dependencies') 1040 'main solutions and all sub-dependencies')
1041 parser.add_option('--unmanaged', action='store_true', default=False,
1042 help='overrides the default behavior to make it possible '
1043 'to have the main solution untouched by gclient '
1044 '(gclient will check out unmanaged dependencies but '
1045 'will never sync them)')
1034 parser.add_option('--git-deps', action='store_true', 1046 parser.add_option('--git-deps', action='store_true',
1035 help='sets the deps file to ".DEPS.git" instead of "DEPS"') 1047 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
1036 (options, args) = parser.parse_args(args) 1048 (options, args) = parser.parse_args(args)
1037 if ((options.spec and args) or len(args) > 2 or 1049 if ((options.spec and args) or len(args) > 2 or
1038 (not options.spec and not args)): 1050 (not options.spec and not args)):
1039 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') 1051 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1040 1052
1041 client = GClient('.', options) 1053 client = GClient('.', options)
1042 if options.spec: 1054 if options.spec:
1043 client.SetConfig(options.spec) 1055 client.SetConfig(options.spec)
1044 else: 1056 else:
1045 base_url = args[0].rstrip('/') 1057 base_url = args[0].rstrip('/')
1046 if not options.name: 1058 if not options.name:
1047 name = base_url.split('/')[-1] 1059 name = base_url.split('/')[-1]
1048 if name.endswith('.git'): 1060 if name.endswith('.git'):
1049 name = name[:-4] 1061 name = name[:-4]
1050 else: 1062 else:
1051 # specify an alternate relpath for the given URL. 1063 # specify an alternate relpath for the given URL.
1052 name = options.name 1064 name = options.name
1053 deps_file = options.deps_file 1065 deps_file = options.deps_file
1054 if options.git_deps: 1066 if options.git_deps:
1055 deps_file = '.DEPS.git' 1067 deps_file = '.DEPS.git'
1056 safesync_url = '' 1068 safesync_url = ''
1057 if len(args) > 1: 1069 if len(args) > 1:
1058 safesync_url = args[1] 1070 safesync_url = args[1]
1059 client.SetDefaultConfig(name, deps_file, base_url, safesync_url) 1071 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1072 managed=not options.unmanaged)
1060 client.SaveConfig() 1073 client.SaveConfig()
1061 return 0 1074 return 0
1062 1075
1063 1076
1064 @attr('epilog', """Example: 1077 @attr('epilog', """Example:
1065 gclient pack > patch.txt 1078 gclient pack > patch.txt
1066 generate simple patch for configured client and dependences 1079 generate simple patch for configured client and dependences
1067 """) 1080 """)
1068 def CMDpack(parser, args): 1081 def CMDpack(parser, args):
1069 """Generate a patch which can be applied at the root of the tree. 1082 """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
1365 except (gclient_utils.Error, subprocess2.CalledProcessError), e: 1378 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
1366 print >> sys.stderr, 'Error: %s' % str(e) 1379 print >> sys.stderr, 'Error: %s' % str(e)
1367 return 1 1380 return 1
1368 1381
1369 1382
1370 if '__main__' == __name__: 1383 if '__main__' == __name__:
1371 fix_encoding.fix_encoding() 1384 fix_encoding.fix_encoding()
1372 sys.exit(Main(sys.argv[1:])) 1385 sys.exit(Main(sys.argv[1:]))
1373 1386
1374 # vim: ts=2:sw=2:tw=80:et: 1387 # vim: ts=2:sw=2:tw=80:et:
OLDNEW
« no previous file with comments | « no previous file | gclient_scm.py » ('j') | gclient_scm.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698