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

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) 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
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
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
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
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
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
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:
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