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 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 dep for dep in self.root.subtree(True) if url.module_name == dep.name | 339 dep for dep in self.root.subtree(True) if url.module_name == dep.name |
340 ] | 340 ] |
341 if not ref: | 341 if not ref: |
342 raise gclient_utils.Error('Failed to find one reference to %s. %s' % ( | 342 raise gclient_utils.Error('Failed to find one reference to %s. %s' % ( |
343 url.module_name, ref)) | 343 url.module_name, ref)) |
344 # It may happen that len(ref) > 1 but it's no big deal. | 344 # It may happen that len(ref) > 1 but it's no big deal. |
345 ref = ref[0] | 345 ref = ref[0] |
346 sub_target = url.sub_target_name or self.name | 346 sub_target = url.sub_target_name or self.name |
347 # Make sure the referenced dependency DEPS file is loaded and file the | 347 # Make sure the referenced dependency DEPS file is loaded and file the |
348 # inner referenced dependency. | 348 # inner referenced dependency. |
| 349 # TODO(maruel): Shouldn't do that. |
349 ref.ParseDepsFile() | 350 ref.ParseDepsFile() |
350 found_dep = None | 351 found_dep = None |
351 for d in ref.dependencies: | 352 for d in ref.dependencies: |
352 if d.name == sub_target: | 353 if d.name == sub_target: |
353 found_dep = d | 354 found_dep = d |
354 break | 355 break |
355 if not found_dep: | 356 if not found_dep: |
356 raise gclient_utils.Error( | 357 raise gclient_utils.Error( |
357 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % ( | 358 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % ( |
358 sub_target, ref.name, self.name, self.parent.name, | 359 sub_target, ref.name, self.name, self.parent.name, |
(...skipping 24 matching lines...) Expand all Loading... |
383 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) | 384 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url)) |
384 return parsed_url | 385 return parsed_url |
385 | 386 |
386 if isinstance(url, self.FileImpl): | 387 if isinstance(url, self.FileImpl): |
387 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url)) | 388 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url)) |
388 return url | 389 return url |
389 | 390 |
390 if url is None: | 391 if url is None: |
391 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url)) | 392 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url)) |
392 return url | 393 return url |
393 else: | 394 |
394 raise gclient_utils.Error('Unknown url type') | 395 raise gclient_utils.Error('Unknown url type') |
395 | 396 |
396 def ParseDepsFile(self): | 397 def ParseDepsFile(self): |
397 """Parses the DEPS file for this dependency.""" | 398 """Parses the DEPS file for this dependency.""" |
398 assert self.processed == True | |
399 if self.deps_parsed: | 399 if self.deps_parsed: |
400 logging.debug('%s was already parsed' % self.name) | 400 logging.debug('%s was already parsed' % self.name) |
401 return | 401 return |
402 # One thing is unintuitive, vars= {} must happen before Var() use. | 402 assert not self.dependencies |
| 403 # One thing is unintuitive, vars = {} must happen before Var() use. |
403 local_scope = {} | 404 local_scope = {} |
404 var = self.VarImpl(self.custom_vars, local_scope) | 405 var = self.VarImpl(self.custom_vars, local_scope) |
405 global_scope = { | 406 global_scope = { |
406 'File': self.FileImpl, | 407 'File': self.FileImpl, |
407 'From': self.FromImpl, | 408 'From': self.FromImpl, |
408 'Var': var.Lookup, | 409 'Var': var.Lookup, |
409 'deps_os': {}, | 410 'deps_os': {}, |
410 } | 411 } |
411 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file) | 412 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file) |
412 if not os.path.isfile(filepath): | 413 if not os.path.isfile(filepath): |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
465 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', [])) | 466 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', [])) |
466 logging.info('ParseDepsFile(%s) done' % self.name) | 467 logging.info('ParseDepsFile(%s) done' % self.name) |
467 | 468 |
468 def add_dependencies_and_close(self, deps_to_add, hooks): | 469 def add_dependencies_and_close(self, deps_to_add, hooks): |
469 """Adds the dependencies, hooks and mark the parsing as done.""" | 470 """Adds the dependencies, hooks and mark the parsing as done.""" |
470 for dep in deps_to_add: | 471 for dep in deps_to_add: |
471 if dep.setup_requirements(): | 472 if dep.setup_requirements(): |
472 self.add_dependency(dep) | 473 self.add_dependency(dep) |
473 self._mark_as_parsed(hooks) | 474 self._mark_as_parsed(hooks) |
474 | 475 |
| 476 @staticmethod |
| 477 def maybeGetParentRevision( |
| 478 command, options, parsed_url, parent_name, revision_overrides): |
| 479 """If we are performing an update and --transitive is set, set the |
| 480 revision to the parent's revision. If we have an explicit revision |
| 481 do nothing.""" |
| 482 if command == 'update' and options.transitive and not options.revision: |
| 483 _, revision = gclient_utils.SplitUrlRevision(parsed_url) |
| 484 if not revision: |
| 485 options.revision = revision_overrides.get(parent_name) |
| 486 if options.verbose and options.revision: |
| 487 print("Using parent's revision date: %s" % options.revision) |
| 488 # If the parent has a revision override, then it must have been |
| 489 # converted to date format. |
| 490 assert (not options.revision or |
| 491 gclient_utils.IsDateRevision(options.revision)) |
| 492 |
| 493 @staticmethod |
| 494 def maybeConvertToDateRevision( |
| 495 command, options, name, scm, revision_overrides): |
| 496 """If we are performing an update and --transitive is set, convert the |
| 497 revision to a date-revision (if necessary). Instead of having |
| 498 -r 101 replace the revision with the time stamp of 101 (e.g. |
| 499 "{2011-18-04}"). |
| 500 This way dependencies are upgraded to the revision they had at the |
| 501 check-in of revision 101.""" |
| 502 if (command == 'update' and |
| 503 options.transitive and |
| 504 options.revision and |
| 505 not gclient_utils.IsDateRevision(options.revision)): |
| 506 revision_date = scm.GetRevisionDate(options.revision) |
| 507 revision = gclient_utils.MakeDateRevision(revision_date) |
| 508 if options.verbose: |
| 509 print("Updating revision override from %s to %s." % |
| 510 (options.revision, revision)) |
| 511 revision_overrides[name] = revision |
| 512 |
475 # Arguments number differs from overridden method | 513 # Arguments number differs from overridden method |
476 # pylint: disable=W0221 | 514 # pylint: disable=W0221 |
477 def run(self, revision_overrides, command, args, work_queue, options): | 515 def run(self, revision_overrides, command, args, work_queue, options): |
478 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 516 """Runs |command| then parse the DEPS file.""" |
479 or a revert.""" | |
480 | |
481 def maybeGetParentRevision(options): | |
482 """If we are performing an update and --transitive is set, set the | |
483 revision to the parent's revision. If we have an explicit revision | |
484 do nothing.""" | |
485 if command == 'update' and options.transitive and not options.revision: | |
486 _, revision = gclient_utils.SplitUrlRevision(self.parsed_url) | |
487 if not revision: | |
488 options.revision = revision_overrides.get(self.parent.name) | |
489 if options.verbose and options.revision: | |
490 print("Using parent's revision date: %s" % options.revision) | |
491 # If the parent has a revision override, then it must have been | |
492 # converted to date format. | |
493 assert (not options.revision or | |
494 gclient_utils.IsDateRevision(options.revision)) | |
495 | |
496 def maybeConvertToDateRevision(options): | |
497 """If we are performing an update and --transitive is set, convert the | |
498 revision to a date-revision (if necessary). Instead of having | |
499 -r 101 replace the revision with the time stamp of 101 (e.g. | |
500 "{2011-18-04}"). | |
501 This way dependencies are upgraded to the revision they had at the | |
502 check-in of revision 101.""" | |
503 if (command == 'update' and | |
504 options.transitive and | |
505 options.revision and | |
506 not gclient_utils.IsDateRevision(options.revision)): | |
507 revision_date = scm.GetRevisionDate(options.revision) | |
508 revision = gclient_utils.MakeDateRevision(revision_date) | |
509 if options.verbose: | |
510 print("Updating revision override from %s to %s." % | |
511 (options.revision, revision)) | |
512 revision_overrides[self.name] = revision | |
513 | |
514 assert self._file_list == [] | 517 assert self._file_list == [] |
515 if not self.should_process: | 518 if not self.should_process: |
516 return | 519 return |
517 # When running runhooks, there's no need to consult the SCM. | 520 # When running runhooks, there's no need to consult the SCM. |
518 # All known hooks are expected to run unconditionally regardless of working | 521 # All known hooks are expected to run unconditionally regardless of working |
519 # copy state, so skip the SCM status check. | 522 # copy state, so skip the SCM status check. |
520 run_scm = command not in ('runhooks', None) | 523 run_scm = command not in ('runhooks', None) |
521 self._parsed_url = self.LateOverride(self.url) | 524 parsed_url = self.LateOverride(self.url) |
522 if run_scm and self.parsed_url: | 525 file_list = [] |
523 if isinstance(self.parsed_url, self.FileImpl): | 526 if run_scm and parsed_url: |
| 527 if isinstance(parsed_url, self.FileImpl): |
524 # Special support for single-file checkout. | 528 # Special support for single-file checkout. |
525 if not command in (None, 'cleanup', 'diff', 'pack', 'status'): | 529 if not command in (None, 'cleanup', 'diff', 'pack', 'status'): |
526 options.revision = self.parsed_url.GetRevision() | 530 # Sadly, pylint doesn't realize that parsed_url is of FileImpl. |
527 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(), | 531 # pylint: disable=E1103 |
| 532 options.revision = parsed_url.GetRevision() |
| 533 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(), |
528 self.root.root_dir, | 534 self.root.root_dir, |
529 self.name) | 535 self.name) |
530 scm.RunCommand('updatesingle', options, | 536 scm.RunCommand('updatesingle', options, |
531 args + [self.parsed_url.GetFilename()], | 537 args + [parsed_url.GetFilename()], |
532 self._file_list) | 538 file_list) |
533 else: | 539 else: |
534 # Create a shallow copy to mutate revision. | 540 # Create a shallow copy to mutate revision. |
535 options = copy.copy(options) | 541 options = copy.copy(options) |
536 options.revision = revision_overrides.get(self.name) | 542 options.revision = revision_overrides.get(self.name) |
537 maybeGetParentRevision(options) | 543 self.maybeGetParentRevision( |
538 scm = gclient_scm.CreateSCM( | 544 command, options, parsed_url, self.parent.name, revision_overrides) |
539 self.parsed_url, self.root.root_dir, self.name) | 545 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name) |
540 scm.RunCommand(command, options, args, self._file_list) | 546 scm.RunCommand(command, options, args, file_list) |
541 maybeConvertToDateRevision(options) | 547 self.maybeConvertToDateRevision( |
542 self._file_list = [os.path.join(self.name, f.strip()) | 548 command, options, self.name, scm, revision_overrides) |
543 for f in self._file_list] | 549 file_list = [os.path.join(self.name, f.strip()) for f in file_list] |
544 | 550 |
545 # TODO(phajdan.jr): We should know exactly when the paths are absolute. | 551 # TODO(phajdan.jr): We should know exactly when the paths are absolute. |
546 # Convert all absolute paths to relative. | 552 # Convert all absolute paths to relative. |
547 for i in range(len(self._file_list)): | 553 for i in range(len(file_list)): |
548 # It depends on the command being executed (like runhooks vs sync). | 554 # It depends on the command being executed (like runhooks vs sync). |
549 if not os.path.isabs(self._file_list[i]): | 555 if not os.path.isabs(file_list[i]): |
550 continue | 556 continue |
551 prefix = os.path.commonprefix( | 557 prefix = os.path.commonprefix( |
552 [self.root.root_dir.lower(), self._file_list[i].lower()]) | 558 [self.root.root_dir.lower(), file_list[i].lower()]) |
553 self._file_list[i] = self._file_list[i][len(prefix):] | 559 file_list[i] = file_list[i][len(prefix):] |
554 # Strip any leading path separators. | 560 # Strip any leading path separators. |
555 while (self._file_list[i].startswith('\\') or | 561 while file_list[i].startswith(('\\', '/')): |
556 self._file_list[i].startswith('/')): | 562 file_list[i] = file_list[i][1:] |
557 self._file_list[i] = self._file_list[i][1:] | 563 |
558 self._processed = True | |
559 if self.recursion_limit: | 564 if self.recursion_limit: |
560 # Then we can parse the DEPS file. | 565 # Then we can parse the DEPS file. |
561 self.ParseDepsFile() | 566 self.ParseDepsFile() |
562 | 567 |
| 568 self._run_is_done(file_list, parsed_url) |
| 569 |
| 570 if self.recursion_limit: |
563 # Parse the dependencies of this dependency. | 571 # Parse the dependencies of this dependency. |
564 for s in self.dependencies: | 572 for s in self.dependencies: |
565 work_queue.enqueue(s) | 573 work_queue.enqueue(s) |
566 | 574 |
| 575 @gclient_utils.lockedmethod |
| 576 def _run_is_done(self, file_list, parsed_url): |
| 577 # Both these are kept for hooks that are run as a separate tree traversal. |
| 578 self._file_list = file_list |
| 579 self._parsed_url = parsed_url |
| 580 self._processed = True |
| 581 |
567 def RunHooksRecursively(self, options): | 582 def RunHooksRecursively(self, options): |
568 """Evaluates all hooks, running actions as needed. run() | 583 """Evaluates all hooks, running actions as needed. run() |
569 must have been called before to load the DEPS.""" | 584 must have been called before to load the DEPS.""" |
570 assert self.hooks_ran == False | 585 assert self.hooks_ran == False |
571 if not self.should_process or not self.recursion_limit: | 586 if not self.should_process or not self.recursion_limit: |
572 # Don't run the hook when it is above recursion_limit. | 587 # Don't run the hook when it is above recursion_limit. |
573 return | 588 return |
574 # If "--force" was specified, run all hooks regardless of what files have | 589 # If "--force" was specified, run all hooks regardless of what files have |
575 # changed. | 590 # changed. |
576 if self.deps_hooks: | 591 if self.deps_hooks: |
577 # TODO(maruel): If the user is using git or git-svn, then we don't know | 592 # TODO(maruel): If the user is using git or git-svn, then we don't know |
578 # what files have changed so we always run all hooks. It'd be nice to fix | 593 # what files have changed so we always run all hooks. It'd be nice to fix |
579 # that. | 594 # that. |
580 if (options.force or | 595 if (options.force or |
581 isinstance(self.parsed_url, self.FileImpl) or | 596 isinstance(self.parsed_url, self.FileImpl) or |
582 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or | 597 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or |
583 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))): | 598 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))): |
584 for hook_dict in self.deps_hooks: | 599 for hook_dict in self.deps_hooks: |
585 self._RunHookAction(hook_dict, []) | 600 self._RunHookAction(hook_dict, []) |
586 else: | 601 else: |
587 # Run hooks on the basis of whether the files from the gclient operation | 602 # Run hooks on the basis of whether the files from the gclient operation |
588 # match each hook's pattern. | 603 # match each hook's pattern. |
589 for hook_dict in self.deps_hooks: | 604 for hook_dict in self.deps_hooks: |
590 pattern = re.compile(hook_dict['pattern']) | 605 pattern = re.compile(hook_dict['pattern']) |
591 matching_file_list = [f for f in self.file_list if pattern.search(f)] | 606 matching_file_list = [ |
| 607 f for f in self.file_list_and_children if pattern.search(f) |
| 608 ] |
592 if matching_file_list: | 609 if matching_file_list: |
593 self._RunHookAction(hook_dict, matching_file_list) | 610 self._RunHookAction(hook_dict, matching_file_list) |
594 for s in self.dependencies: | 611 for s in self.dependencies: |
595 s.RunHooksRecursively(options) | 612 s.RunHooksRecursively(options) |
596 | 613 |
597 def _RunHookAction(self, hook_dict, matching_file_list): | 614 def _RunHookAction(self, hook_dict, matching_file_list): |
598 """Runs the action from a single hook.""" | 615 """Runs the action from a single hook.""" |
599 # A single DEPS file can specify multiple hooks so this function can be | 616 # A single DEPS file can specify multiple hooks so this function can be |
600 # called multiple times on a single Dependency. | 617 # called multiple times on a single Dependency. |
601 #assert self.hooks_ran == False | 618 #assert self.hooks_ran == False |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
649 def _mark_as_parsed(self, new_hooks): | 666 def _mark_as_parsed(self, new_hooks): |
650 self._deps_hooks.extend(new_hooks) | 667 self._deps_hooks.extend(new_hooks) |
651 self._deps_parsed = True | 668 self._deps_parsed = True |
652 | 669 |
653 @property | 670 @property |
654 @gclient_utils.lockedmethod | 671 @gclient_utils.lockedmethod |
655 def dependencies(self): | 672 def dependencies(self): |
656 return tuple(self._dependencies) | 673 return tuple(self._dependencies) |
657 | 674 |
658 @property | 675 @property |
| 676 @gclient_utils.lockedmethod |
659 def deps_hooks(self): | 677 def deps_hooks(self): |
660 return tuple(self._deps_hooks) | 678 return tuple(self._deps_hooks) |
661 | 679 |
662 @property | 680 @property |
| 681 @gclient_utils.lockedmethod |
663 def parsed_url(self): | 682 def parsed_url(self): |
664 return self._parsed_url | 683 return self._parsed_url |
665 | 684 |
666 @property | 685 @property |
| 686 @gclient_utils.lockedmethod |
667 def deps_parsed(self): | 687 def deps_parsed(self): |
668 return self._deps_parsed | 688 return self._deps_parsed |
669 | 689 |
670 @property | 690 @property |
| 691 @gclient_utils.lockedmethod |
671 def processed(self): | 692 def processed(self): |
672 return self._processed | 693 return self._processed |
673 | 694 |
674 @property | 695 @property |
| 696 @gclient_utils.lockedmethod |
675 def hooks_ran(self): | 697 def hooks_ran(self): |
676 return self._hooks_ran | 698 return self._hooks_ran |
677 | 699 |
678 @property | 700 @property |
| 701 @gclient_utils.lockedmethod |
679 def file_list(self): | 702 def file_list(self): |
680 result = self._file_list[:] | 703 return tuple(self._file_list) |
| 704 |
| 705 @property |
| 706 def file_list_and_children(self): |
| 707 result = list(self.file_list) |
681 for d in self.dependencies: | 708 for d in self.dependencies: |
682 result.extend(d.file_list) | 709 result.extend(d.file_list_and_children) |
683 return tuple(result) | 710 return tuple(result) |
684 | 711 |
685 def __str__(self): | 712 def __str__(self): |
686 out = [] | 713 out = [] |
687 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', | 714 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', |
688 'custom_vars', 'deps_hooks', 'file_list', 'should_process', | 715 'custom_vars', 'deps_hooks', 'file_list', 'should_process', |
689 'processed', 'hooks_ran', 'deps_parsed', 'requirements'): | 716 'processed', 'hooks_ran', 'deps_parsed', 'requirements'): |
690 # First try the native property if it exists. | 717 # First try the native property if it exists. |
691 if hasattr(self, '_' + i): | 718 if hasattr(self, '_' + i): |
692 value = getattr(self, '_' + i, False) | 719 value = getattr(self, '_' + i, False) |
(...skipping 770 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1463 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1490 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
1464 print >> sys.stderr, 'Error: %s' % str(e) | 1491 print >> sys.stderr, 'Error: %s' % str(e) |
1465 return 1 | 1492 return 1 |
1466 | 1493 |
1467 | 1494 |
1468 if '__main__' == __name__: | 1495 if '__main__' == __name__: |
1469 fix_encoding.fix_encoding() | 1496 fix_encoding.fix_encoding() |
1470 sys.exit(Main(sys.argv[1:])) | 1497 sys.exit(Main(sys.argv[1:])) |
1471 | 1498 |
1472 # vim: ts=2:sw=2:tw=80:et: | 1499 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |