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): |
| 265 """Setup self.requirements and find any other dependency who would have self |
| 266 as a requirement. |
| 267 |
| 268 Returns True if this entry should be added, False if it is a duplicate of |
| 269 another entry. |
| 270 """ |
| 271 if self.name in [s.name for s in self.parent.dependencies]: |
| 272 raise gclient_utils.Error( |
| 273 'The same name "%s" appears multiple times in the deps section' % |
| 274 self.name) |
| 275 if self.should_process: |
| 276 siblings = [d for d in self.root.subtree(False) if d.name == self.name] |
| 277 for sibling in siblings: |
| 278 if self.url != sibling.url: |
| 279 raise gclient_utils.Error( |
| 280 'Dependency %s specified more than once:\n %s\nvs\n %s' % |
| 281 (self.name, sibling.hierarchy(), self.hierarchy())) |
| 282 # In theory we could keep it as a shadow of the other one. In |
| 283 # practice, simply ignore it. |
| 284 logging.warn('Won\'t process duplicate dependency %s' % sibling) |
| 285 return False |
263 | 286 |
264 # self.parent is implicitly a requirement. This will be recursive by | 287 # self.parent is implicitly a requirement. This will be recursive by |
265 # definition. | 288 # definition. |
266 if self.parent and self.parent.name: | 289 if self.parent and self.parent.name: |
267 self.add_requirement(self.parent.name) | 290 self.add_requirement(self.parent.name) |
268 | 291 |
269 # For a tree with at least 2 levels*, the leaf node needs to depend | 292 # For a tree with at least 2 levels*, the leaf node needs to depend |
270 # on the level higher up in an orderly way. | 293 # on the level higher up in an orderly way. |
271 # This becomes messy for >2 depth as the DEPS file format is a dictionary, | 294 # 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. | 295 # 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: | 314 if self.name and self.should_process: |
292 for obj in self.root.depth_first_tree(): | 315 for obj in self.root.depth_first_tree(): |
293 if obj is self or not obj.name: | 316 if obj is self or not obj.name: |
294 continue | 317 continue |
295 # Step 1: Find any requirements self may need. | 318 # Step 1: Find any requirements self may need. |
296 if self.name.startswith(posixpath.join(obj.name, '')): | 319 if self.name.startswith(posixpath.join(obj.name, '')): |
297 self.add_requirement(obj.name) | 320 self.add_requirement(obj.name) |
298 # Step 2: Find any requirements self may impose. | 321 # Step 2: Find any requirements self may impose. |
299 if obj.name.startswith(posixpath.join(self.name, '')): | 322 if obj.name.startswith(posixpath.join(self.name, '')): |
300 obj.add_requirement(self.name) | 323 obj.add_requirement(self.name) |
301 | 324 return True |
302 if not self.name and self.parent: | |
303 raise gclient_utils.Error('Dependency without name') | |
304 | 325 |
305 def LateOverride(self, url): | 326 def LateOverride(self, url): |
306 """Resolves the parsed url from url. | 327 """Resolves the parsed url from url. |
307 | 328 |
308 Manages From() keyword accordingly. Do not touch self.parsed_url nor | 329 Manages From() keyword accordingly. Do not touch self.parsed_url nor |
309 self.url because it may called with other urls due to From().""" | 330 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 | 331 assert self.parsed_url == None or not self.should_process, self.parsed_url |
311 parsed_url = self.get_custom_deps(self.name, url) | 332 parsed_url = self.get_custom_deps(self.name, url) |
312 if parsed_url != url: | 333 if parsed_url != url: |
313 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) | 334 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
409 os_deps = local_scope['deps_os'].get(deps_os_key, {}) | 430 os_deps = local_scope['deps_os'].get(deps_os_key, {}) |
410 if len(enforced_os) > 1: | 431 if len(enforced_os) > 1: |
411 # Ignore any conflict when including deps for more than one | 432 # Ignore any conflict when including deps for more than one |
412 # platform, so we collect the broadest set of dependencies | 433 # platform, so we collect the broadest set of dependencies |
413 # available. We may end up with the wrong revision of something for | 434 # available. We may end up with the wrong revision of something for |
414 # our platform, but this is the best we can do. | 435 # 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]) | 436 deps.update([x for x in os_deps.items() if not x[0] in deps]) |
416 else: | 437 else: |
417 deps.update(os_deps) | 438 deps.update(os_deps) |
418 | 439 |
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 | 440 # If a line is in custom_deps, but not in the solution, we want to append |
422 # this line to the solution. | 441 # this line to the solution. |
423 for d in self.custom_deps: | 442 for d in self.custom_deps: |
424 if d not in deps: | 443 if d not in deps: |
425 deps[d] = self.custom_deps[d] | 444 deps[d] = self.custom_deps[d] |
426 | 445 |
427 # If use_relative_paths is set in the DEPS file, regenerate | 446 # If use_relative_paths is set in the DEPS file, regenerate |
428 # the dictionary using paths relative to the directory containing | 447 # the dictionary using paths relative to the directory containing |
429 # the DEPS file. | 448 # the DEPS file. |
430 use_relative_paths = local_scope.get('use_relative_paths', False) | 449 use_relative_paths = local_scope.get('use_relative_paths', False) |
431 if use_relative_paths: | 450 if use_relative_paths: |
432 rel_deps = {} | 451 rel_deps = {} |
433 for d, url in deps.items(): | 452 for d, url in deps.items(): |
434 # normpath is required to allow DEPS to use .. in their | 453 # normpath is required to allow DEPS to use .. in their |
435 # dependency local path. | 454 # dependency local path. |
436 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url | 455 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url |
437 deps = rel_deps | 456 deps = rel_deps |
438 | 457 |
439 # Convert the deps into real Dependency. | 458 # Convert the deps into real Dependency. |
| 459 deps_to_add = [] |
440 for name, url in deps.iteritems(): | 460 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 | 461 should_process = self.recursion_limit and self.should_process |
446 if should_process: | 462 deps_to_add.append(Dependency( |
447 tree = dict((d.name, d) for d in self.root.subtree(False)) | 463 self, name, url, None, None, None, None, |
448 if name in tree: | 464 self.deps_file, should_process)) |
449 if url == tree[name].url: | 465 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', [])) |
450 logging.info('Won\'t process duplicate dependency %s' % tree[name]) | 466 logging.info('ParseDepsFile(%s) done' % self.name) |
451 # In theory we could keep it as a shadow of the other one. In | 467 |
452 # practice, simply ignore it. | 468 def add_dependencies_and_close(self, deps_to_add, hooks): |
453 #should_process = False | 469 """Adds the dependencies, hooks and mark the parsing as done.""" |
454 continue | 470 for dep in deps_to_add: |
455 else: | 471 if dep.setup_requirements(): |
456 raise gclient_utils.Error( | 472 self.add_dependency(dep) |
457 'Dependency %s specified more than once:\n %s\nvs\n %s' % | 473 self._mark_as_parsed(hooks) |
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 | 474 |
466 # Arguments number differs from overridden method | 475 # Arguments number differs from overridden method |
467 # pylint: disable=W0221 | 476 # pylint: disable=W0221 |
468 def run(self, revision_overrides, command, args, work_queue, options): | 477 def run(self, revision_overrides, command, args, work_queue, options): |
469 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 478 """Runs 'command' before parsing the DEPS in case it's a initial checkout |
470 or a revert.""" | 479 or a revert.""" |
471 | 480 |
472 def maybeGetParentRevision(options): | 481 def maybeGetParentRevision(options): |
473 """If we are performing an update and --transitive is set, set the | 482 """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 | 483 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 | 634 yield i |
626 | 635 |
627 def depth_first_tree(self): | 636 def depth_first_tree(self): |
628 """Depth-first recursion including the root node.""" | 637 """Depth-first recursion including the root node.""" |
629 yield self | 638 yield self |
630 for i in self.dependencies: | 639 for i in self.dependencies: |
631 for j in i.depth_first_tree(): | 640 for j in i.depth_first_tree(): |
632 if j.should_process: | 641 if j.should_process: |
633 yield j | 642 yield j |
634 | 643 |
| 644 @gclient_utils.lockedmethod |
| 645 def add_dependency(self, new_dep): |
| 646 self._dependencies.append(new_dep) |
| 647 |
| 648 @gclient_utils.lockedmethod |
| 649 def _mark_as_parsed(self, new_hooks): |
| 650 self._deps_hooks.extend(new_hooks) |
| 651 self._deps_parsed = True |
| 652 |
635 @property | 653 @property |
| 654 @gclient_utils.lockedmethod |
636 def dependencies(self): | 655 def dependencies(self): |
637 return tuple(self._dependencies) | 656 return tuple(self._dependencies) |
638 | 657 |
639 @property | 658 @property |
640 def deps_hooks(self): | 659 def deps_hooks(self): |
641 return tuple(self._deps_hooks) | 660 return tuple(self._deps_hooks) |
642 | 661 |
643 @property | 662 @property |
644 def parsed_url(self): | 663 def parsed_url(self): |
645 return self._parsed_url | 664 return self._parsed_url |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
758 self.config_content = None | 777 self.config_content = None |
759 | 778 |
760 def SetConfig(self, content): | 779 def SetConfig(self, content): |
761 assert not self.dependencies | 780 assert not self.dependencies |
762 config_dict = {} | 781 config_dict = {} |
763 self.config_content = content | 782 self.config_content = content |
764 try: | 783 try: |
765 exec(content, config_dict) | 784 exec(content, config_dict) |
766 except SyntaxError, e: | 785 except SyntaxError, e: |
767 gclient_utils.SyntaxErrorToError('.gclient', e) | 786 gclient_utils.SyntaxErrorToError('.gclient', e) |
| 787 |
| 788 deps_to_add = [] |
768 for s in config_dict.get('solutions', []): | 789 for s in config_dict.get('solutions', []): |
769 try: | 790 try: |
770 tree = dict((d.name, d) for d in self.root.subtree(False)) | 791 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'], | 792 self, s['name'], s['url'], |
776 s.get('safesync_url', None), | 793 s.get('safesync_url', None), |
777 s.get('managed', True), | 794 s.get('managed', True), |
778 s.get('custom_deps', {}), | 795 s.get('custom_deps', {}), |
779 s.get('custom_vars', {}), | 796 s.get('custom_vars', {}), |
780 s.get('deps_file', 'DEPS'), | 797 s.get('deps_file', 'DEPS'), |
781 True)) | 798 True)) |
782 except KeyError: | 799 except KeyError: |
783 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 800 raise gclient_utils.Error('Invalid .gclient file. Solution is ' |
784 'incomplete: %s' % s) | 801 'incomplete: %s' % s) |
785 # .gclient can have hooks. | 802 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', [])) |
786 self._deps_hooks = config_dict.get('hooks', []) | 803 logging.info('SetConfig() done') |
787 self._deps_parsed = True | |
788 | 804 |
789 def SaveConfig(self): | 805 def SaveConfig(self): |
790 gclient_utils.FileWrite(os.path.join(self.root_dir, | 806 gclient_utils.FileWrite(os.path.join(self.root_dir, |
791 self._options.config_filename), | 807 self._options.config_filename), |
792 self.config_content) | 808 self.config_content) |
793 | 809 |
794 @staticmethod | 810 @staticmethod |
795 def LoadCurrentConfig(options): | 811 def LoadCurrentConfig(options): |
796 """Searches for and loads a .gclient file relative to the current working | 812 """Searches for and loads a .gclient file relative to the current working |
797 dir. Returns a GClient object.""" | 813 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: | 1463 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
1448 print >> sys.stderr, 'Error: %s' % str(e) | 1464 print >> sys.stderr, 'Error: %s' % str(e) |
1449 return 1 | 1465 return 1 |
1450 | 1466 |
1451 | 1467 |
1452 if '__main__' == __name__: | 1468 if '__main__' == __name__: |
1453 fix_encoding.fix_encoding() | 1469 fix_encoding.fix_encoding() |
1454 sys.exit(Main(sys.argv[1:])) | 1470 sys.exit(Main(sys.argv[1:])) |
1455 | 1471 |
1456 # vim: ts=2:sw=2:tw=80:et: | 1472 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |