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

Side by Side Diff: gclient.py

Issue 503563002: Auto-update .gclient files for git. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 6 years, 4 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 | no next file » | 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) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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 # Files 7 # Files
8 # .gclient : Current client configuration, written by 'config' command. 8 # .gclient : Current client configuration, written by 'config' command.
9 # Format is a Python script defining 'solutions', a list whose 9 # Format is a Python script defining 'solutions', a list whose
10 # entries each are maps binding the strings "name" and "url" 10 # entries each are maps binding the strings "name" and "url"
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 # 71 #
72 # If the "target_os_only" key is also present and true, then *only* the 72 # If the "target_os_only" key is also present and true, then *only* the
73 # operating systems listed in "target_os" will be used. 73 # operating systems listed in "target_os" will be used.
74 # 74 #
75 # Example: 75 # Example:
76 # target_os = [ "ios" ] 76 # target_os = [ "ios" ]
77 # target_os_only = True 77 # target_os_only = True
78 78
79 __version__ = '0.7' 79 __version__ = '0.7'
80 80
81 import ast
81 import copy 82 import copy
82 import json 83 import json
83 import logging 84 import logging
84 import optparse 85 import optparse
85 import os 86 import os
86 import platform 87 import platform
87 import posixpath 88 import posixpath
88 import pprint 89 import pprint
89 import re 90 import re
90 import sys 91 import sys
91 import time 92 import time
92 import urllib 93 import urllib
93 import urlparse 94 import urlparse
94 95
95 import breakpad # pylint: disable=W0611 96 import breakpad # pylint: disable=W0611
96 97
97 import fix_encoding 98 import fix_encoding
98 import gclient_scm 99 import gclient_scm
99 import gclient_utils 100 import gclient_utils
100 import git_cache 101 import git_cache
101 from third_party.repo.progress import Progress 102 from third_party.repo.progress import Progress
102 import subcommand 103 import subcommand
103 import subprocess2 104 import subprocess2
104 from third_party import colorama 105 from third_party import colorama
105 106
107 CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
108
109
110 def ast_dict_index(dnode, key):
111 """Search an ast.Dict for the argument key, and return its index."""
112 idx = [i for i in range(len(dnode.keys)) if (
113 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
114 if not idx:
115 return -1
116 elif len(idx) > 1:
117 raise gclient_utils.Error('Multiple dict entries with same key in AST')
118 return idx[-1]
119
120 def ast2str(node, indent=0):
121 """Return a pretty-printed rendition of an ast.Node."""
122 t = type(node)
123 if t is ast.Module:
124 return '\n'.join([ast2str(x, indent) for x in node.body])
125 elif t is ast.Assign:
126 return ((' ' * indent) +
127 ' = '.join([ast2str(x) for x in node.targets] +
128 [ast2str(node.value, indent)]) + '\n')
129 elif t is ast.Name:
130 return node.id
131 elif t is ast.List:
132 if not node.elts:
133 return '[]'
134 elif len(node.elts) == 1:
135 return '[' + ast2str(node.elts[0], indent) + ']'
136 return ('[\n' + (' ' * (indent + 1)) +
137 (',\n' + (' ' * (indent + 1))).join(
138 [ast2str(x, indent + 1) for x in node.elts]) +
139 '\n' + (' ' * indent) + ']')
140 elif t is ast.Dict:
141 if not node.keys:
142 return '{}'
143 elif len(node.keys) == 1:
144 return '{%s: %s}' % (ast2str(node.keys[0]),
145 ast2str(node.values[0], indent + 1))
146 return ('{\n' + (' ' * (indent + 1)) +
147 (',\n' + (' ' * (indent + 1))).join(
148 ['%s: %s' % (ast2str(node.keys[i]),
149 ast2str(node.values[i], indent + 1))
150 for i in range(len(node.keys))]) +
151 '\n' + (' ' * indent) + '}')
152 elif t is ast.Str:
153 return "'%s'" % node.s
154 else:
155 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
156 % (node.lineno, node.col_offset, t))
157
106 158
107 class GClientKeywords(object): 159 class GClientKeywords(object):
108 class FromImpl(object): 160 class FromImpl(object):
109 """Used to implement the From() syntax.""" 161 """Used to implement the From() syntax."""
110 162
111 def __init__(self, module_name, sub_target_name=None): 163 def __init__(self, module_name, sub_target_name=None):
112 """module_name is the dep module we want to include from. It can also be 164 """module_name is the dep module we want to include from. It can also be
113 the name of a subdirectory to include from. 165 the name of a subdirectory to include from.
114 166
115 sub_target_name is an optional parameter if the module name in the other 167 sub_target_name is an optional parameter if the module name in the other
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 class DependencySettings(GClientKeywords): 213 class DependencySettings(GClientKeywords):
162 """Immutable configuration settings.""" 214 """Immutable configuration settings."""
163 def __init__( 215 def __init__(
164 self, parent, url, safesync_url, managed, custom_deps, custom_vars, 216 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
165 custom_hooks, deps_file, should_process): 217 custom_hooks, deps_file, should_process):
166 GClientKeywords.__init__(self) 218 GClientKeywords.__init__(self)
167 219
168 # These are not mutable: 220 # These are not mutable:
169 self._parent = parent 221 self._parent = parent
170 self._safesync_url = safesync_url 222 self._safesync_url = safesync_url
171 self._deps_file = deps_file 223 if url == CHROMIUM_SRC_URL:
224 self._deps_file = 'DEPS'
225 else:
226 self._deps_file = deps_file
172 self._url = url 227 self._url = url
173 # 'managed' determines whether or not this dependency is synced/updated by 228 # 'managed' determines whether or not this dependency is synced/updated by
174 # gclient after gclient checks it out initially. The difference between 229 # gclient after gclient checks it out initially. The difference between
175 # 'managed' and 'should_process' is that the user specifies 'managed' via 230 # 'managed' and 'should_process' is that the user specifies 'managed' via
176 # the --unmanaged command-line flag or a .gclient config, where 231 # the --unmanaged command-line flag or a .gclient config, where
177 # 'should_process' is dynamically set by gclient if it goes over its 232 # 'should_process' is dynamically set by gclient if it goes over its
178 # recursion limit and controls gclient's behavior so it does not misbehave. 233 # recursion limit and controls gclient's behavior so it does not misbehave.
179 self._managed = managed 234 self._managed = managed
180 self._should_process = should_process 235 self._should_process = should_process
181 # This is a mutable value which has the list of 'target_os' OSes listed in 236 # This is a mutable value which has the list of 'target_os' OSes listed in
(...skipping 1007 matching lines...) Expand 10 before | Expand all | Expand 10 after
1189 raise gclient_utils.Error('Invalid .gclient file. Solution is ' 1244 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1190 'incomplete: %s' % s) 1245 'incomplete: %s' % s)
1191 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', [])) 1246 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1192 logging.info('SetConfig() done') 1247 logging.info('SetConfig() done')
1193 1248
1194 def SaveConfig(self): 1249 def SaveConfig(self):
1195 gclient_utils.FileWrite(os.path.join(self.root_dir, 1250 gclient_utils.FileWrite(os.path.join(self.root_dir,
1196 self._options.config_filename), 1251 self._options.config_filename),
1197 self.config_content) 1252 self.config_content)
1198 1253
1254 def MigrateConfigToGit(self, path, options):
1255 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1256 'svn://svn\.chromium\.org/chrome)/'
1257 '(trunk|branches/[^/]+)/src')
1258 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1259 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1260 'gerrit\.chromium\.org(:2941[89])?)/'
1261 'chromium/src\.git')
1262 # Scan existing .gclient file for obsolete settings. It would be simpler
1263 # to traverse self.dependencies, but working with the AST allows the code to
1264 # dump an updated .gclient file that preserves the ordering of the original.
1265 a = ast.parse(self.config_content, options.config_filename, 'exec')
1266 modified = False
1267 solutions = [elem for elem in a.body if 'solutions' in
1268 [target.id for target in elem.targets]]
1269 if not solutions:
1270 return self
1271 solutions = solutions[-1]
1272 for solution in solutions.value.elts:
1273 # Check for obsolete URL's
1274 url_idx = ast_dict_index(solution, 'url')
1275 if url_idx == -1:
1276 continue
1277 url_val = solution.values[url_idx]
1278 if type(url_val) is not ast.Str:
1279 continue
1280 if (svn_url_re.match(url_val.s.strip())):
1281 raise gclient_utils.Error(
1282 """
1283 The chromium code repository has migrated completely to git.
1284 Your SVN-based checkout is now obsolete; you need to create a brand-new
1285 git checkout by following these instructions:
1286
1287 http://www.chromium.org/developers/how-tos/get-the-code
1288 """)
1289 if (old_git_re.match(url_val.s.strip())):
1290 url_val.s = CHROMIUM_SRC_URL
1291 modified = True
1292
1293 # Check for obsolete deps_file
1294 if url_val.s == CHROMIUM_SRC_URL:
1295 deps_file_idx = ast_dict_index(solution, 'deps_file')
1296 if deps_file_idx == -1:
1297 continue
1298 deps_file_val = solution.values[deps_file_idx]
1299 if type(deps_file_val) is not ast.Str:
1300 continue
1301 if deps_file_val.s == '.DEPS.git':
1302 solution.keys[deps_file_idx:deps_file_idx + 1] = []
1303 solution.values[deps_file_idx:deps_file_idx + 1] = []
1304 modified = True
1305
1306 if not modified:
1307 return self
1308
1309 print(
1310 """
1311 WARNING: gclient detected an obsolete setting in your %s file. The file has
1312 been automagically updated. The previous version is available at %s.old.
1313 """ % (options.config_filename, options.config_filename))
1314
1315 # Replace existing .gclient with the updated version.
1316 # Return a new GClient instance based on the new content.
1317 new_content = ast2str(a)
1318 dot_gclient_fn = os.path.join(path, options.config_filename)
1319 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1320 fh = open(dot_gclient_fn, 'w')
1321 fh.write(new_content)
1322 fh.close()
1323 client = GClient(path, options)
1324 client.SetConfig(new_content)
1325 return client
1326
1199 @staticmethod 1327 @staticmethod
1200 def LoadCurrentConfig(options): 1328 def LoadCurrentConfig(options):
1201 """Searches for and loads a .gclient file relative to the current working 1329 """Searches for and loads a .gclient file relative to the current working
1202 dir. Returns a GClient object.""" 1330 dir. Returns a GClient object."""
1203 if options.spec: 1331 if options.spec:
1204 client = GClient('.', options) 1332 client = GClient('.', options)
1205 client.SetConfig(options.spec) 1333 client.SetConfig(options.spec)
1206 else: 1334 else:
1207 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) 1335 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1208 if not path: 1336 if not path:
1209 return None 1337 return None
1210 client = GClient(path, options) 1338 client = GClient(path, options)
1211 client.SetConfig(gclient_utils.FileRead( 1339 client.SetConfig(gclient_utils.FileRead(
1212 os.path.join(path, options.config_filename))) 1340 os.path.join(path, options.config_filename)))
1341 client = client.MigrateConfigToGit(path, options)
1213 1342
1214 if (options.revisions and 1343 if (options.revisions and
1215 len(client.dependencies) > 1 and 1344 len(client.dependencies) > 1 and
1216 any('@' not in r for r in options.revisions)): 1345 any('@' not in r for r in options.revisions)):
1217 print >> sys.stderr, ( 1346 print >> sys.stderr, (
1218 'You must specify the full solution name like --revision %s@%s\n' 1347 'You must specify the full solution name like --revision %s@%s\n'
1219 'when you have multiple solutions setup in your .gclient file.\n' 1348 'when you have multiple solutions setup in your .gclient file.\n'
1220 'Other solutions present are: %s.') % ( 1349 'Other solutions present are: %s.') % (
1221 client.dependencies[0].name, 1350 client.dependencies[0].name,
1222 options.revisions[0], 1351 options.revisions[0],
(...skipping 416 matching lines...) Expand 10 before | Expand all | Expand 10 after
1639 parser.add_option('--name', 1768 parser.add_option('--name',
1640 help='overrides the default name for the solution') 1769 help='overrides the default name for the solution')
1641 parser.add_option('--deps-file', default='DEPS', 1770 parser.add_option('--deps-file', default='DEPS',
1642 help='overrides the default name for the DEPS file for the' 1771 help='overrides the default name for the DEPS file for the'
1643 'main solutions and all sub-dependencies') 1772 'main solutions and all sub-dependencies')
1644 parser.add_option('--unmanaged', action='store_true', default=False, 1773 parser.add_option('--unmanaged', action='store_true', default=False,
1645 help='overrides the default behavior to make it possible ' 1774 help='overrides the default behavior to make it possible '
1646 'to have the main solution untouched by gclient ' 1775 'to have the main solution untouched by gclient '
1647 '(gclient will check out unmanaged dependencies but ' 1776 '(gclient will check out unmanaged dependencies but '
1648 'will never sync them)') 1777 'will never sync them)')
1649 parser.add_option('--git-deps', action='store_true',
1650 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
1651 parser.add_option('--cache-dir', 1778 parser.add_option('--cache-dir',
1652 help='(git only) Cache all git repos into this dir and do ' 1779 help='(git only) Cache all git repos into this dir and do '
1653 'shared clones from the cache, instead of cloning ' 1780 'shared clones from the cache, instead of cloning '
1654 'directly from the remote. (experimental)') 1781 'directly from the remote. (experimental)')
1655 parser.set_defaults(config_filename=None) 1782 parser.set_defaults(config_filename=None)
1656 (options, args) = parser.parse_args(args) 1783 (options, args) = parser.parse_args(args)
1657 if options.output_config_file: 1784 if options.output_config_file:
1658 setattr(options, 'config_filename', getattr(options, 'output_config_file')) 1785 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
1659 if ((options.spec and args) or len(args) > 2 or 1786 if ((options.spec and args) or len(args) > 2 or
1660 (not options.spec and not args)): 1787 (not options.spec and not args)):
1661 parser.error('Inconsistent arguments. Use either --spec or one or 2 args') 1788 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1662 1789
1663 client = GClient('.', options) 1790 client = GClient('.', options)
1664 if options.spec: 1791 if options.spec:
1665 client.SetConfig(options.spec) 1792 client.SetConfig(options.spec)
1666 else: 1793 else:
1667 base_url = args[0].rstrip('/') 1794 base_url = args[0].rstrip('/')
1668 if not options.name: 1795 if not options.name:
1669 name = base_url.split('/')[-1] 1796 name = base_url.split('/')[-1]
1670 if name.endswith('.git'): 1797 if name.endswith('.git'):
1671 name = name[:-4] 1798 name = name[:-4]
1672 else: 1799 else:
1673 # specify an alternate relpath for the given URL. 1800 # specify an alternate relpath for the given URL.
1674 name = options.name 1801 name = options.name
1675 deps_file = options.deps_file 1802 deps_file = options.deps_file
1676 if options.git_deps:
1677 deps_file = '.DEPS.git'
1678 safesync_url = '' 1803 safesync_url = ''
1679 if len(args) > 1: 1804 if len(args) > 1:
1680 safesync_url = args[1] 1805 safesync_url = args[1]
1681 client.SetDefaultConfig(name, deps_file, base_url, safesync_url, 1806 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1682 managed=not options.unmanaged, 1807 managed=not options.unmanaged,
1683 cache_dir=options.cache_dir) 1808 cache_dir=options.cache_dir)
1684 client.SaveConfig() 1809 client.SaveConfig()
1685 return 0 1810 return 0
1686 1811
1687 1812
(...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after
2065 print >> sys.stderr, 'Error: %s' % str(e) 2190 print >> sys.stderr, 'Error: %s' % str(e)
2066 return 1 2191 return 1
2067 finally: 2192 finally:
2068 gclient_utils.PrintWarnings() 2193 gclient_utils.PrintWarnings()
2069 2194
2070 2195
2071 if '__main__' == __name__: 2196 if '__main__' == __name__:
2072 sys.exit(Main(sys.argv[1:])) 2197 sys.exit(Main(sys.argv[1:]))
2073 2198
2074 # vim: ts=2:sw=2:tw=80:et: 2199 # vim: ts=2:sw=2:tw=80:et:
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698