OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 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 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
172 raise gclient_utils.Error('Dependency without name') | 172 raise gclient_utils.Error('Dependency without name') |
173 if not isinstance(self.url, | 173 if not isinstance(self.url, |
174 (basestring, self.FromImpl, self.FileImpl, None.__class__)): | 174 (basestring, self.FromImpl, self.FileImpl, None.__class__)): |
175 raise gclient_utils.Error('dependency url must be either a string, None, ' | 175 raise gclient_utils.Error('dependency url must be either a string, None, ' |
176 'File() or From() instead of %s' % | 176 'File() or From() instead of %s' % |
177 self.url.__class__.__name__) | 177 self.url.__class__.__name__) |
178 if '/' in self.deps_file or '\\' in self.deps_file: | 178 if '/' in self.deps_file or '\\' in self.deps_file: |
179 raise gclient_utils.Error('deps_file name must not be a path, just a ' | 179 raise gclient_utils.Error('deps_file name must not be a path, just a ' |
180 'filename. %s' % self.deps_file) | 180 'filename. %s' % self.deps_file) |
181 | 181 |
182 | |
183 class GClient(Dependency): | |
184 """Main gclient checkout root where .gclient resides.""" | |
185 SUPPORTED_COMMANDS = [ | |
186 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', | |
187 'runhooks' | |
188 ] | |
189 | |
190 DEPS_OS_CHOICES = { | |
191 "win32": "win", | |
192 "win": "win", | |
193 "cygwin": "win", | |
194 "darwin": "mac", | |
195 "mac": "mac", | |
196 "unix": "unix", | |
197 "linux": "unix", | |
198 "linux2": "unix", | |
199 } | |
200 | |
201 DEFAULT_CLIENT_FILE_TEXT = ("""\ | |
202 solutions = [ | |
203 { "name" : "%(solution_name)s", | |
204 "url" : "%(solution_url)s", | |
205 "custom_deps" : { | |
206 }, | |
207 "safesync_url": "%(safesync_url)s" | |
208 }, | |
209 ] | |
210 """) | |
211 | |
212 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ | |
213 { "name" : "%(solution_name)s", | |
214 "url" : "%(solution_url)s", | |
215 "custom_deps" : { | |
216 %(solution_deps)s, | |
217 }, | |
218 "safesync_url": "%(safesync_url)s" | |
219 }, | |
220 """) | |
221 | |
222 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ | |
223 # Snapshot generated with gclient revinfo --snapshot | |
224 solutions = [ | |
225 %(solution_list)s | |
226 ] | |
227 """) | |
228 | |
229 def __init__(self, root_dir, options): | |
230 Dependency.__init__(self, None, None, None) | |
231 self._root_dir = root_dir | |
232 self._options = options | |
233 self.config_content = None | |
234 | |
235 def SetConfig(self, content): | |
236 assert self.dependencies == [] | |
237 config_dict = {} | |
238 self.config_content = content | |
239 try: | |
240 exec(content, config_dict) | |
241 except SyntaxError, e: | |
242 try: | |
243 # Try to construct a human readable error message | |
244 error_message = [ | |
245 'There is a syntax error in your configuration file.', | |
246 'Line #%s, character %s:' % (e.lineno, e.offset), | |
247 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ] | |
248 except: | |
249 # Something went wrong, re-raise the original exception | |
250 raise e | |
251 else: | |
252 # Raise a new exception with the human readable message: | |
253 raise gclient_utils.Error('\n'.join(error_message)) | |
254 for s in config_dict.get('solutions', []): | |
255 self.dependencies.append(Dependency( | |
256 self, s['name'], s['url'], | |
257 s.get('safesync_url', None), | |
258 s.get('custom_deps', {}), | |
259 s.get('custom_vars', {}))) | |
260 # .gclient can have hooks. | |
261 self.deps_hooks = config_dict.get('hooks', []) | |
262 | |
263 def SaveConfig(self): | |
264 gclient_utils.FileWrite(os.path.join(self.root_dir(), | |
265 self._options.config_filename), | |
266 self.config_content) | |
267 | |
268 @staticmethod | |
269 def LoadCurrentConfig(options): | |
270 """Searches for and loads a .gclient file relative to the current working | |
271 dir. Returns a GClient object.""" | |
272 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) | |
273 if not path: | |
274 return None | |
275 client = GClient(path, options) | |
276 client.SetConfig(gclient_utils.FileRead( | |
277 os.path.join(path, options.config_filename))) | |
278 return client | |
279 | |
280 def SetDefaultConfig(self, solution_name, solution_url, safesync_url): | |
281 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { | |
282 'solution_name': solution_name, | |
283 'solution_url': solution_url, | |
284 'safesync_url' : safesync_url, | |
285 }) | |
286 | |
287 def _SaveEntries(self, entries): | |
288 """Creates a .gclient_entries file to record the list of unique checkouts. | |
289 | |
290 The .gclient_entries file lives in the same directory as .gclient. | |
291 | |
292 Args: | |
293 entries: A sequence of solution names. | |
294 """ | |
295 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It | |
296 # makes testing a bit too fun. | |
297 result = pprint.pformat(entries, 2) | |
298 if result.startswith('{\''): | |
299 result = '{ \'' + result[2:] | |
300 text = "entries = \\\n" + result + '\n' | |
301 file_path = os.path.join(self.root_dir(), self._options.entries_filename) | |
302 gclient_utils.FileWrite(file_path, text) | |
303 | |
304 def _ReadEntries(self): | |
305 """Read the .gclient_entries file for the given client. | |
306 | |
307 Returns: | |
308 A sequence of solution names, which will be empty if there is the | |
309 entries file hasn't been created yet. | |
310 """ | |
311 scope = {} | |
312 filename = os.path.join(self.root_dir(), self._options.entries_filename) | |
313 if not os.path.exists(filename): | |
314 return [] | |
315 exec(gclient_utils.FileRead(filename), scope) | |
316 return scope['entries'] | |
317 | |
318 def _ParseSolutionDeps(self, solution_name, solution_deps_content, | 182 def _ParseSolutionDeps(self, solution_name, solution_deps_content, |
319 custom_vars, parse_hooks): | 183 custom_vars, parse_hooks): |
320 """Parses the DEPS file for the specified solution. | 184 """Parses the DEPS file for the specified solution. |
321 | 185 |
322 Args: | 186 Args: |
323 solution_name: The name of the solution to query. | 187 solution_name: The name of the solution to query. |
324 solution_deps_content: Content of the DEPS file for the solution | 188 solution_deps_content: Content of the DEPS file for the solution |
325 custom_vars: A dict of vars to override any vars defined in the DEPS file. | 189 custom_vars: A dict of vars to override any vars defined in the DEPS file. |
326 | 190 |
327 Returns: | 191 Returns: |
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
508 return | 372 return |
509 | 373 |
510 # Run hooks on the basis of whether the files from the gclient operation | 374 # Run hooks on the basis of whether the files from the gclient operation |
511 # match each hook's pattern. | 375 # match each hook's pattern. |
512 for hook_dict in hooks: | 376 for hook_dict in hooks: |
513 pattern = re.compile(hook_dict['pattern']) | 377 pattern = re.compile(hook_dict['pattern']) |
514 matching_file_list = [f for f in file_list if pattern.search(f)] | 378 matching_file_list = [f for f in file_list if pattern.search(f)] |
515 if matching_file_list: | 379 if matching_file_list: |
516 self._RunHookAction(hook_dict, matching_file_list) | 380 self._RunHookAction(hook_dict, matching_file_list) |
517 | 381 |
| 382 |
| 383 class GClient(Dependency): |
| 384 """Main gclient checkout root where .gclient resides.""" |
| 385 SUPPORTED_COMMANDS = [ |
| 386 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', |
| 387 'runhooks' |
| 388 ] |
| 389 |
| 390 DEPS_OS_CHOICES = { |
| 391 "win32": "win", |
| 392 "win": "win", |
| 393 "cygwin": "win", |
| 394 "darwin": "mac", |
| 395 "mac": "mac", |
| 396 "unix": "unix", |
| 397 "linux": "unix", |
| 398 "linux2": "unix", |
| 399 } |
| 400 |
| 401 DEFAULT_CLIENT_FILE_TEXT = ("""\ |
| 402 solutions = [ |
| 403 { "name" : "%(solution_name)s", |
| 404 "url" : "%(solution_url)s", |
| 405 "custom_deps" : { |
| 406 }, |
| 407 "safesync_url": "%(safesync_url)s" |
| 408 }, |
| 409 ] |
| 410 """) |
| 411 |
| 412 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ |
| 413 { "name" : "%(solution_name)s", |
| 414 "url" : "%(solution_url)s", |
| 415 "custom_deps" : { |
| 416 %(solution_deps)s, |
| 417 }, |
| 418 "safesync_url": "%(safesync_url)s" |
| 419 }, |
| 420 """) |
| 421 |
| 422 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ |
| 423 # Snapshot generated with gclient revinfo --snapshot |
| 424 solutions = [ |
| 425 %(solution_list)s |
| 426 ] |
| 427 """) |
| 428 |
| 429 def __init__(self, root_dir, options): |
| 430 Dependency.__init__(self, None, None, None) |
| 431 self._root_dir = root_dir |
| 432 self._options = options |
| 433 self.config_content = None |
| 434 |
| 435 def SetConfig(self, content): |
| 436 assert self.dependencies == [] |
| 437 config_dict = {} |
| 438 self.config_content = content |
| 439 try: |
| 440 exec(content, config_dict) |
| 441 except SyntaxError, e: |
| 442 try: |
| 443 # Try to construct a human readable error message |
| 444 error_message = [ |
| 445 'There is a syntax error in your configuration file.', |
| 446 'Line #%s, character %s:' % (e.lineno, e.offset), |
| 447 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ] |
| 448 except: |
| 449 # Something went wrong, re-raise the original exception |
| 450 raise e |
| 451 else: |
| 452 # Raise a new exception with the human readable message: |
| 453 raise gclient_utils.Error('\n'.join(error_message)) |
| 454 for s in config_dict.get('solutions', []): |
| 455 self.dependencies.append(Dependency( |
| 456 self, s['name'], s['url'], |
| 457 s.get('safesync_url', None), |
| 458 s.get('custom_deps', {}), |
| 459 s.get('custom_vars', {}))) |
| 460 # .gclient can have hooks. |
| 461 self.deps_hooks = config_dict.get('hooks', []) |
| 462 |
| 463 def SaveConfig(self): |
| 464 gclient_utils.FileWrite(os.path.join(self.root_dir(), |
| 465 self._options.config_filename), |
| 466 self.config_content) |
| 467 |
| 468 @staticmethod |
| 469 def LoadCurrentConfig(options): |
| 470 """Searches for and loads a .gclient file relative to the current working |
| 471 dir. Returns a GClient object.""" |
| 472 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename) |
| 473 if not path: |
| 474 return None |
| 475 client = GClient(path, options) |
| 476 client.SetConfig(gclient_utils.FileRead( |
| 477 os.path.join(path, options.config_filename))) |
| 478 return client |
| 479 |
| 480 def SetDefaultConfig(self, solution_name, solution_url, safesync_url): |
| 481 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % { |
| 482 'solution_name': solution_name, |
| 483 'solution_url': solution_url, |
| 484 'safesync_url' : safesync_url, |
| 485 }) |
| 486 |
| 487 def _SaveEntries(self, entries): |
| 488 """Creates a .gclient_entries file to record the list of unique checkouts. |
| 489 |
| 490 The .gclient_entries file lives in the same directory as .gclient. |
| 491 |
| 492 Args: |
| 493 entries: A sequence of solution names. |
| 494 """ |
| 495 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It |
| 496 # makes testing a bit too fun. |
| 497 result = pprint.pformat(entries, 2) |
| 498 if result.startswith('{\''): |
| 499 result = '{ \'' + result[2:] |
| 500 text = "entries = \\\n" + result + '\n' |
| 501 file_path = os.path.join(self.root_dir(), self._options.entries_filename) |
| 502 gclient_utils.FileWrite(file_path, text) |
| 503 |
| 504 def _ReadEntries(self): |
| 505 """Read the .gclient_entries file for the given client. |
| 506 |
| 507 Returns: |
| 508 A sequence of solution names, which will be empty if there is the |
| 509 entries file hasn't been created yet. |
| 510 """ |
| 511 scope = {} |
| 512 filename = os.path.join(self.root_dir(), self._options.entries_filename) |
| 513 if not os.path.exists(filename): |
| 514 return [] |
| 515 exec(gclient_utils.FileRead(filename), scope) |
| 516 return scope['entries'] |
| 517 |
518 def _EnforceRevisions(self): | 518 def _EnforceRevisions(self): |
519 """Checks for revision overrides.""" | 519 """Checks for revision overrides.""" |
520 revision_overrides = {} | 520 revision_overrides = {} |
521 if self._options.head: | 521 if self._options.head: |
522 return revision_overrides | 522 return revision_overrides |
523 for s in self.dependencies: | 523 for s in self.dependencies: |
524 if not s.safesync_url: | 524 if not s.safesync_url: |
525 continue | 525 continue |
526 handle = urllib.urlopen(s.safesync_url) | 526 handle = urllib.urlopen(s.safesync_url) |
527 rev = handle.read().strip() | 527 rev = handle.read().strip() |
(...skipping 617 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1145 return CMDhelp(parser, argv) | 1145 return CMDhelp(parser, argv) |
1146 except gclient_utils.Error, e: | 1146 except gclient_utils.Error, e: |
1147 print >> sys.stderr, 'Error: %s' % str(e) | 1147 print >> sys.stderr, 'Error: %s' % str(e) |
1148 return 1 | 1148 return 1 |
1149 | 1149 |
1150 | 1150 |
1151 if '__main__' == __name__: | 1151 if '__main__' == __name__: |
1152 sys.exit(Main(sys.argv[1:])) | 1152 sys.exit(Main(sys.argv[1:])) |
1153 | 1153 |
1154 # vim: ts=2:sw=2:tw=80:et: | 1154 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |