OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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: |
OLD | NEW |