Chromium Code Reviews| OLD | NEW | 
|---|---|
| 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 240 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 251 # hooks. | 251 # hooks. | 
| 252 self._file_list = [] | 252 self._file_list = [] | 
| 253 # If it is not set to True, the dependency wasn't processed for its child | 253 # If it is not set to True, the dependency wasn't processed for its child | 
| 254 # dependency, i.e. its DEPS wasn't read. | 254 # dependency, i.e. its DEPS wasn't read. | 
| 255 self._deps_parsed = False | 255 self._deps_parsed = False | 
| 256 # This dependency has been processed, i.e. checked out | 256 # This dependency has been processed, i.e. checked out | 
| 257 self._processed = False | 257 self._processed = False | 
| 258 # This dependency had its hook run | 258 # This dependency had its hook run | 
| 259 self._hooks_ran = False | 259 self._hooks_ran = False | 
| 260 | 260 | 
| 261 # Setup self.requirements and find any other dependency who would have self | 261 if not self.name and self.parent: | 
| 262 # as a requirement. | 262 raise gclient_utils.Error('Dependency without name') | 
| 263 | |
| 264 def setup_requirements(self): | |
| 
 
M-A Ruel
2011/10/05 00:40:52
ok you can laugh at me for splitting it again.
 
 | |
| 265 """Setup self.requirements and find any other dependency who would have self | |
| 266 as a requirement. | |
| 
 
Dirk Pranke
2011/10/06 19:34:29
Nit: document the return value? It's not obvious t
 
M-A Ruel
2011/10/06 20:44:19
documented
 
 | |
| 267 """ | |
| 268 if self.name in [s.name for s in self.parent.dependencies]: | |
| 269 raise gclient_utils.Error( | |
| 270 'The same name "%s" appears multiple times in the deps section' % | |
| 271 self.name) | |
| 272 if self.should_process: | |
| 273 sibblings = [d for d in self.root.subtree(False) if d.name == self.name] | |
| 
 
Dirk Pranke
2011/10/06 19:34:29
Nit: there's only one "b" in "siblings". Here and
 
M-A Ruel
2011/10/06 20:44:19
fixed
 
 | |
| 274 for sibbling in sibblings: | |
| 275 if self.url != sibbling.url: | |
| 276 raise gclient_utils.Error( | |
| 277 'Dependency %s specified more than once:\n %s\nvs\n %s' % | |
| 278 (self.name, sibbling.hierarchy(), self.hierarchy())) | |
| 279 # In theory we could keep it as a shadow of the other one. In | |
| 280 # practice, simply ignore it. | |
| 281 logging.warn('Won\'t process duplicate dependency %s' % sibbling) | |
| 282 return False | |
| 263 | 283 | 
| 264 # self.parent is implicitly a requirement. This will be recursive by | 284 # self.parent is implicitly a requirement. This will be recursive by | 
| 265 # definition. | 285 # definition. | 
| 266 if self.parent and self.parent.name: | 286 if self.parent and self.parent.name: | 
| 267 self.add_requirement(self.parent.name) | 287 self.add_requirement(self.parent.name) | 
| 268 | 288 | 
| 269 # For a tree with at least 2 levels*, the leaf node needs to depend | 289 # For a tree with at least 2 levels*, the leaf node needs to depend | 
| 270 # on the level higher up in an orderly way. | 290 # on the level higher up in an orderly way. | 
| 271 # This becomes messy for >2 depth as the DEPS file format is a dictionary, | 291 # This becomes messy for >2 depth as the DEPS file format is a dictionary, | 
| 272 # thus unsorted, while the .gclient format is a list thus sorted. | 292 # thus unsorted, while the .gclient format is a list thus sorted. | 
| (...skipping 18 matching lines...) Expand all Loading... | |
| 291 if self.name and self.should_process: | 311 if self.name and self.should_process: | 
| 292 for obj in self.root.depth_first_tree(): | 312 for obj in self.root.depth_first_tree(): | 
| 293 if obj is self or not obj.name: | 313 if obj is self or not obj.name: | 
| 294 continue | 314 continue | 
| 295 # Step 1: Find any requirements self may need. | 315 # Step 1: Find any requirements self may need. | 
| 296 if self.name.startswith(posixpath.join(obj.name, '')): | 316 if self.name.startswith(posixpath.join(obj.name, '')): | 
| 297 self.add_requirement(obj.name) | 317 self.add_requirement(obj.name) | 
| 298 # Step 2: Find any requirements self may impose. | 318 # Step 2: Find any requirements self may impose. | 
| 299 if obj.name.startswith(posixpath.join(self.name, '')): | 319 if obj.name.startswith(posixpath.join(self.name, '')): | 
| 300 obj.add_requirement(self.name) | 320 obj.add_requirement(self.name) | 
| 301 | 321 return True | 
| 302 if not self.name and self.parent: | |
| 303 raise gclient_utils.Error('Dependency without name') | |
| 304 | 322 | 
| 305 def LateOverride(self, url): | 323 def LateOverride(self, url): | 
| 306 """Resolves the parsed url from url. | 324 """Resolves the parsed url from url. | 
| 307 | 325 | 
| 308 Manages From() keyword accordingly. Do not touch self.parsed_url nor | 326 Manages From() keyword accordingly. Do not touch self.parsed_url nor | 
| 309 self.url because it may called with other urls due to From().""" | 327 self.url because it may called with other urls due to From().""" | 
| 310 assert self.parsed_url == None or not self.should_process, self.parsed_url | 328 assert self.parsed_url == None or not self.should_process, self.parsed_url | 
| 311 parsed_url = self.get_custom_deps(self.name, url) | 329 parsed_url = self.get_custom_deps(self.name, url) | 
| 312 if parsed_url != url: | 330 if parsed_url != url: | 
| 313 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) | 331 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) | 
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 366 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url)) | 384 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url)) | 
| 367 return url | 385 return url | 
| 368 | 386 | 
| 369 if url is None: | 387 if url is None: | 
| 370 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url)) | 388 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url)) | 
| 371 return url | 389 return url | 
| 372 else: | 390 else: | 
| 373 raise gclient_utils.Error('Unknown url type') | 391 raise gclient_utils.Error('Unknown url type') | 
| 374 | 392 | 
| 375 def ParseDepsFile(self): | 393 def ParseDepsFile(self): | 
| 376 """Parses the DEPS file for this dependency.""" | 394 """Parses the DEPS file for this dependency.""" | 
| 
 
Dirk Pranke
2011/10/06 19:34:29
Is this going to be called by multiple threads sim
 
M-A Ruel
2011/10/06 20:44:19
Not starting with http://codereview.chromium.org/8
 
 | |
| 377 assert self.processed == True | 395 assert self.processed == True | 
| 378 if self.deps_parsed: | 396 if self.deps_parsed: | 
| 379 logging.debug('%s was already parsed' % self.name) | 397 logging.debug('%s was already parsed' % self.name) | 
| 380 return | 398 return | 
| 381 # One thing is unintuitive, vars= {} must happen before Var() use. | 399 # One thing is unintuitive, vars= {} must happen before Var() use. | 
| 382 local_scope = {} | 400 local_scope = {} | 
| 383 var = self.VarImpl(self.custom_vars, local_scope) | 401 var = self.VarImpl(self.custom_vars, local_scope) | 
| 384 global_scope = { | 402 global_scope = { | 
| 385 'File': self.FileImpl, | 403 'File': self.FileImpl, | 
| 386 'From': self.FromImpl, | 404 'From': self.FromImpl, | 
| (...skipping 22 matching lines...) Expand all Loading... | |
| 409 os_deps = local_scope['deps_os'].get(deps_os_key, {}) | 427 os_deps = local_scope['deps_os'].get(deps_os_key, {}) | 
| 410 if len(enforced_os) > 1: | 428 if len(enforced_os) > 1: | 
| 411 # Ignore any conflict when including deps for more than one | 429 # Ignore any conflict when including deps for more than one | 
| 412 # platform, so we collect the broadest set of dependencies | 430 # platform, so we collect the broadest set of dependencies | 
| 413 # available. We may end up with the wrong revision of something for | 431 # available. We may end up with the wrong revision of something for | 
| 414 # our platform, but this is the best we can do. | 432 # our platform, but this is the best we can do. | 
| 415 deps.update([x for x in os_deps.items() if not x[0] in deps]) | 433 deps.update([x for x in os_deps.items() if not x[0] in deps]) | 
| 416 else: | 434 else: | 
| 417 deps.update(os_deps) | 435 deps.update(os_deps) | 
| 418 | 436 | 
| 419 self._deps_hooks.extend(local_scope.get('hooks', [])) | |
| 420 | |
| 421 # If a line is in custom_deps, but not in the solution, we want to append | 437 # If a line is in custom_deps, but not in the solution, we want to append | 
| 422 # this line to the solution. | 438 # this line to the solution. | 
| 423 for d in self.custom_deps: | 439 for d in self.custom_deps: | 
| 424 if d not in deps: | 440 if d not in deps: | 
| 425 deps[d] = self.custom_deps[d] | 441 deps[d] = self.custom_deps[d] | 
| 426 | 442 | 
| 427 # If use_relative_paths is set in the DEPS file, regenerate | 443 # If use_relative_paths is set in the DEPS file, regenerate | 
| 428 # the dictionary using paths relative to the directory containing | 444 # the dictionary using paths relative to the directory containing | 
| 429 # the DEPS file. | 445 # the DEPS file. | 
| 430 use_relative_paths = local_scope.get('use_relative_paths', False) | 446 use_relative_paths = local_scope.get('use_relative_paths', False) | 
| 431 if use_relative_paths: | 447 if use_relative_paths: | 
| 432 rel_deps = {} | 448 rel_deps = {} | 
| 433 for d, url in deps.items(): | 449 for d, url in deps.items(): | 
| 434 # normpath is required to allow DEPS to use .. in their | 450 # normpath is required to allow DEPS to use .. in their | 
| 435 # dependency local path. | 451 # dependency local path. | 
| 436 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url | 452 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url | 
| 437 deps = rel_deps | 453 deps = rel_deps | 
| 438 | 454 | 
| 439 # Convert the deps into real Dependency. | 455 # Convert the deps into real Dependency. | 
| 456 deps_to_add = [] | |
| 440 for name, url in deps.iteritems(): | 457 for name, url in deps.iteritems(): | 
| 441 if name in [s.name for s in self._dependencies]: | |
| 442 raise gclient_utils.Error( | |
| 443 'The same name "%s" appears multiple times in the deps section' % | |
| 444 name) | |
| 445 should_process = self.recursion_limit and self.should_process | 458 should_process = self.recursion_limit and self.should_process | 
| 446 if should_process: | 459 deps_to_add.append(Dependency( | 
| 447 tree = dict((d.name, d) for d in self.root.subtree(False)) | 460 self, name, url, None, None, None, None, | 
| 448 if name in tree: | 461 self.deps_file, should_process)) | 
| 449 if url == tree[name].url: | 462 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', [])) | 
| 450 logging.info('Won\'t process duplicate dependency %s' % tree[name]) | 463 logging.info('ParseDepsFile(%s) done' % self.name) | 
| 451 # In theory we could keep it as a shadow of the other one. In | 464 | 
| 452 # practice, simply ignore it. | 465 def add_dependencies_and_close(self, deps_to_add, hooks): | 
| 
 
Dirk Pranke
2011/10/06 19:34:29
does it make sense to make this a locked method? O
 
M-A Ruel
2011/10/06 20:44:19
Yes and that's the purpose. The lock must be kept
 
Dirk Pranke
2011/10/06 20:50:46
Well, there's nothing in setup_requirements that c
 
M-A Ruel
2011/10/06 20:57:35
Yes there is something. setup_requirements() does
 
 | |
| 453 #should_process = False | 466 """Adds the dependencies, hooks and mark the parsing as done.""" | 
| 454 continue | 467 for dep in deps_to_add: | 
| 455 else: | 468 if dep.setup_requirements(): | 
| 456 raise gclient_utils.Error( | 469 self.add_dependency(dep) | 
| 457 'Dependency %s specified more than once:\n %s\nvs\n %s' % | 470 self._mark_as_parsed(hooks) | 
| 
 
Dirk Pranke
2011/10/06 20:50:46
I thought I had mentioned this in the earlier emai
 
M-A Ruel
2011/10/06 20:57:35
If I'd do that, I'd have to add this call at the o
 
 | |
| 458 (name, tree[name].hierarchy(), self.hierarchy())) | |
| 459 self._dependencies.append( | |
| 460 Dependency( | |
| 461 self, name, url, None, None, None, None, | |
| 462 self.deps_file, should_process)) | |
| 463 self._deps_parsed = True | |
| 464 logging.debug('Loaded: %s' % str(self)) | |
| 465 | 471 | 
| 466 # Arguments number differs from overridden method | 472 # Arguments number differs from overridden method | 
| 467 # pylint: disable=W0221 | 473 # pylint: disable=W0221 | 
| 468 def run(self, revision_overrides, command, args, work_queue, options): | 474 def run(self, revision_overrides, command, args, work_queue, options): | 
| 469 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 475 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 
| 470 or a revert.""" | 476 or a revert.""" | 
| 471 | 477 | 
| 472 def maybeGetParentRevision(options): | 478 def maybeGetParentRevision(options): | 
| 473 """If we are performing an update and --transitive is set, set the | 479 """If we are performing an update and --transitive is set, set the | 
| 474 revision to the parent's revision. If we have an explicit revision | 480 revision to the parent's revision. If we have an explicit revision | 
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 625 yield i | 631 yield i | 
| 626 | 632 | 
| 627 def depth_first_tree(self): | 633 def depth_first_tree(self): | 
| 628 """Depth-first recursion including the root node.""" | 634 """Depth-first recursion including the root node.""" | 
| 629 yield self | 635 yield self | 
| 630 for i in self.dependencies: | 636 for i in self.dependencies: | 
| 631 for j in i.depth_first_tree(): | 637 for j in i.depth_first_tree(): | 
| 632 if j.should_process: | 638 if j.should_process: | 
| 633 yield j | 639 yield j | 
| 634 | 640 | 
| 641 @gclient_utils.lockedmethod | |
| 642 def add_dependency(self, new_dep): | |
| 643 self._dependencies.append(new_dep) | |
| 644 | |
| 645 @gclient_utils.lockedmethod | |
| 646 def _mark_as_parsed(self, new_hooks): | |
| 647 self._deps_hooks.extend(new_hooks) | |
| 648 self._deps_parsed = True | |
| 649 | |
| 635 @property | 650 @property | 
| 651 @gclient_utils.lockedmethod | |
| 636 def dependencies(self): | 652 def dependencies(self): | 
| 637 return tuple(self._dependencies) | 653 return tuple(self._dependencies) | 
| 638 | 654 | 
| 639 @property | 655 @property | 
| 640 def deps_hooks(self): | 656 def deps_hooks(self): | 
| 641 return tuple(self._deps_hooks) | 657 return tuple(self._deps_hooks) | 
| 642 | 658 | 
| 643 @property | 659 @property | 
| 644 def parsed_url(self): | 660 def parsed_url(self): | 
| 645 return self._parsed_url | 661 return self._parsed_url | 
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 758 self.config_content = None | 774 self.config_content = None | 
| 759 | 775 | 
| 760 def SetConfig(self, content): | 776 def SetConfig(self, content): | 
| 761 assert not self.dependencies | 777 assert not self.dependencies | 
| 762 config_dict = {} | 778 config_dict = {} | 
| 763 self.config_content = content | 779 self.config_content = content | 
| 764 try: | 780 try: | 
| 765 exec(content, config_dict) | 781 exec(content, config_dict) | 
| 766 except SyntaxError, e: | 782 except SyntaxError, e: | 
| 767 gclient_utils.SyntaxErrorToError('.gclient', e) | 783 gclient_utils.SyntaxErrorToError('.gclient', e) | 
| 784 | |
| 785 deps_to_add = [] | |
| 768 for s in config_dict.get('solutions', []): | 786 for s in config_dict.get('solutions', []): | 
| 769 try: | 787 try: | 
| 770 tree = dict((d.name, d) for d in self.root.subtree(False)) | 788 deps_to_add.append(Dependency( | 
| 771 if s['name'] in tree: | |
| 772 raise gclient_utils.Error( | |
| 773 'Dependency %s specified more than once in .gclient' % s['name']) | |
| 774 self._dependencies.append(Dependency( | |
| 775 self, s['name'], s['url'], | 789 self, s['name'], s['url'], | 
| 776 s.get('safesync_url', None), | 790 s.get('safesync_url', None), | 
| 777 s.get('managed', True), | 791 s.get('managed', True), | 
| 778 s.get('custom_deps', {}), | 792 s.get('custom_deps', {}), | 
| 779 s.get('custom_vars', {}), | 793 s.get('custom_vars', {}), | 
| 780 s.get('deps_file', 'DEPS'), | 794 s.get('deps_file', 'DEPS'), | 
| 781 True)) | 795 True)) | 
| 782 except KeyError: | 796 except KeyError: | 
| 783 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 797 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 
| 784 'incomplete: %s' % s) | 798 'incomplete: %s' % s) | 
| 785 # .gclient can have hooks. | 799 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', [])) | 
| 786 self._deps_hooks = config_dict.get('hooks', []) | 800 logging.info('SetConfig() done') | 
| 787 self._deps_parsed = True | |
| 788 | 801 | 
| 789 def SaveConfig(self): | 802 def SaveConfig(self): | 
| 790 gclient_utils.FileWrite(os.path.join(self.root_dir, | 803 gclient_utils.FileWrite(os.path.join(self.root_dir, | 
| 791 self._options.config_filename), | 804 self._options.config_filename), | 
| 792 self.config_content) | 805 self.config_content) | 
| 793 | 806 | 
| 794 @staticmethod | 807 @staticmethod | 
| 795 def LoadCurrentConfig(options): | 808 def LoadCurrentConfig(options): | 
| 796 """Searches for and loads a .gclient file relative to the current working | 809 """Searches for and loads a .gclient file relative to the current working | 
| 797 dir. Returns a GClient object.""" | 810 dir. Returns a GClient object.""" | 
| (...skipping 649 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1447 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1460 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 
| 1448 print >> sys.stderr, 'Error: %s' % str(e) | 1461 print >> sys.stderr, 'Error: %s' % str(e) | 
| 1449 return 1 | 1462 return 1 | 
| 1450 | 1463 | 
| 1451 | 1464 | 
| 1452 if '__main__' == __name__: | 1465 if '__main__' == __name__: | 
| 1453 fix_encoding.fix_encoding() | 1466 fix_encoding.fix_encoding() | 
| 1454 sys.exit(Main(sys.argv[1:])) | 1467 sys.exit(Main(sys.argv[1:])) | 
| 1455 | 1468 | 
| 1456 # vim: ts=2:sw=2:tw=80:et: | 1469 # vim: ts=2:sw=2:tw=80:et: | 
| OLD | NEW |