OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2012 The LUCI Authors. All rights reserved. | 2 # Copyright 2012 The LUCI Authors. All rights reserved. |
3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
5 | 5 |
6 """Front end tool to operate on .isolate files. | 6 """Front end tool to operate on .isolate files. |
7 | 7 |
8 This includes creating, merging or compiling them to generate a .isolated file. | 8 This includes creating, merging or compiling them to generate a .isolated file. |
9 | 9 |
10 See more information at | 10 See more information at |
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
472 """Loads state from disk.""" | 472 """Loads state from disk.""" |
473 assert os.path.isabs(isolated_filepath), isolated_filepath | 473 assert os.path.isabs(isolated_filepath), isolated_filepath |
474 isolated_basedir = os.path.dirname(isolated_filepath) | 474 isolated_basedir = os.path.dirname(isolated_filepath) |
475 return cls( | 475 return cls( |
476 isolated_filepath, | 476 isolated_filepath, |
477 SavedState.load_file( | 477 SavedState.load_file( |
478 isolatedfile_to_state(isolated_filepath), isolated_basedir)) | 478 isolatedfile_to_state(isolated_filepath), isolated_basedir)) |
479 | 479 |
480 def load_isolate( | 480 def load_isolate( |
481 self, cwd, isolate_file, path_variables, config_variables, | 481 self, cwd, isolate_file, path_variables, config_variables, |
482 extra_variables, blacklist, ignore_broken_items): | 482 extra_variables, blacklist, ignore_broken_items, collapse_symlinks): |
483 """Updates self.isolated and self.saved_state with information loaded from a | 483 """Updates self.isolated and self.saved_state with information loaded from a |
484 .isolate file. | 484 .isolate file. |
485 | 485 |
486 Processes the loaded data, deduce root_dir, relative_cwd. | 486 Processes the loaded data, deduce root_dir, relative_cwd. |
487 """ | 487 """ |
488 # Make sure to not depend on os.getcwd(). | 488 # Make sure to not depend on os.getcwd(). |
489 assert os.path.isabs(isolate_file), isolate_file | 489 assert os.path.isabs(isolate_file), isolate_file |
490 isolate_file = file_path.get_native_path_case(isolate_file) | 490 isolate_file = file_path.get_native_path_case(isolate_file) |
491 logging.info( | 491 logging.info( |
492 'CompleteState.load_isolate(%s, %s, %s, %s, %s, %s)', | 492 'CompleteState.load_isolate(%s, %s, %s, %s, %s, %s, %s)', |
493 cwd, isolate_file, path_variables, config_variables, extra_variables, | 493 cwd, isolate_file, path_variables, config_variables, extra_variables, |
494 ignore_broken_items) | 494 ignore_broken_items, collapse_symlinks) |
495 | 495 |
496 # Config variables are not affected by the paths and must be used to | 496 # Config variables are not affected by the paths and must be used to |
497 # retrieve the paths, so update them first. | 497 # retrieve the paths, so update them first. |
498 self.saved_state.update_config(config_variables) | 498 self.saved_state.update_config(config_variables) |
499 | 499 |
500 with fs.open(isolate_file, 'r') as f: | 500 with fs.open(isolate_file, 'r') as f: |
501 # At that point, variables are not replaced yet in command and infiles. | 501 # At that point, variables are not replaced yet in command and infiles. |
502 # infiles may contain directory entries and is in posix style. | 502 # infiles may contain directory entries and is in posix style. |
503 command, infiles, read_only, isolate_cmd_dir = ( | 503 command, infiles, read_only, isolate_cmd_dir = ( |
504 isolate_format.load_isolate_for_config( | 504 isolate_format.load_isolate_for_config( |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
543 '%s; %s' | 543 '%s; %s' |
544 % (k, v, self.saved_state.root_dir, dest)) | 544 % (k, v, self.saved_state.root_dir, dest)) |
545 # Normalize the files based to self.saved_state.root_dir. It is important to | 545 # Normalize the files based to self.saved_state.root_dir. It is important to |
546 # keep the trailing os.path.sep at that step. | 546 # keep the trailing os.path.sep at that step. |
547 infiles = [ | 547 infiles = [ |
548 file_path.relpath( | 548 file_path.relpath( |
549 file_path.normpath(os.path.join(isolate_cmd_dir, f)), | 549 file_path.normpath(os.path.join(isolate_cmd_dir, f)), |
550 self.saved_state.root_dir) | 550 self.saved_state.root_dir) |
551 for f in infiles | 551 for f in infiles |
552 ] | 552 ] |
553 follow_symlinks = sys.platform != 'win32' | 553 follow_symlinks = False |
| 554 if not collapse_symlinks: |
| 555 follow_symlinks = sys.platform != 'win32' |
554 # Expand the directories by listing each file inside. Up to now, trailing | 556 # Expand the directories by listing each file inside. Up to now, trailing |
555 # os.path.sep must be kept. | 557 # os.path.sep must be kept. |
556 infiles = isolated_format.expand_directories_and_symlinks( | 558 infiles = isolated_format.expand_directories_and_symlinks( |
557 self.saved_state.root_dir, | 559 self.saved_state.root_dir, |
558 infiles, | 560 infiles, |
559 tools.gen_blacklist(blacklist), | 561 tools.gen_blacklist(blacklist), |
560 follow_symlinks, | 562 follow_symlinks, |
561 ignore_broken_items) | 563 ignore_broken_items) |
562 | 564 |
563 # Finally, update the new data to be able to generate the foo.isolated file, | 565 # Finally, update the new data to be able to generate the foo.isolated file, |
564 # the file that is used by run_isolated.py. | 566 # the file that is used by run_isolated.py. |
565 self.saved_state.update_isolated(command, infiles, read_only, relative_cwd) | 567 self.saved_state.update_isolated(command, infiles, read_only, relative_cwd) |
566 logging.debug(self) | 568 logging.debug(self) |
567 | 569 |
568 def files_to_metadata(self, subdir): | 570 def files_to_metadata(self, subdir, collapse_symlinks): |
569 """Updates self.saved_state.files with the files' mode and hash. | 571 """Updates self.saved_state.files with the files' mode and hash. |
570 | 572 |
571 If |subdir| is specified, filters to a subdirectory. The resulting .isolated | 573 If |subdir| is specified, filters to a subdirectory. The resulting .isolated |
572 file is tainted. | 574 file is tainted. |
573 | 575 |
574 See isolated_format.file_to_metadata() for more information. | 576 See isolated_format.file_to_metadata() for more information. |
575 """ | 577 """ |
576 for infile in sorted(self.saved_state.files): | 578 for infile in sorted(self.saved_state.files): |
577 if subdir and not infile.startswith(subdir): | 579 if subdir and not infile.startswith(subdir): |
578 self.saved_state.files.pop(infile) | 580 self.saved_state.files.pop(infile) |
579 else: | 581 else: |
580 filepath = os.path.join(self.root_dir, infile) | 582 filepath = os.path.join(self.root_dir, infile) |
581 self.saved_state.files[infile] = isolated_format.file_to_metadata( | 583 self.saved_state.files[infile] = isolated_format.file_to_metadata( |
582 filepath, | 584 filepath, |
583 self.saved_state.files[infile], | 585 self.saved_state.files[infile], |
584 self.saved_state.read_only, | 586 self.saved_state.read_only, |
585 self.saved_state.algo) | 587 self.saved_state.algo, |
| 588 collapse_symlinks) |
586 | 589 |
587 def save_files(self): | 590 def save_files(self): |
588 """Saves self.saved_state and creates a .isolated file.""" | 591 """Saves self.saved_state and creates a .isolated file.""" |
589 logging.debug('Dumping to %s' % self.isolated_filepath) | 592 logging.debug('Dumping to %s' % self.isolated_filepath) |
590 self.saved_state.child_isolated_files = chromium_save_isolated( | 593 self.saved_state.child_isolated_files = chromium_save_isolated( |
591 self.isolated_filepath, | 594 self.isolated_filepath, |
592 self.saved_state.to_isolated(), | 595 self.saved_state.to_isolated(), |
593 self.saved_state.path_variables, | 596 self.saved_state.path_variables, |
594 self.saved_state.algo) | 597 self.saved_state.algo) |
595 total_bytes = sum( | 598 total_bytes = sum( |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
667 complete_state.saved_state.isolate_file, | 670 complete_state.saved_state.isolate_file, |
668 isolatedfile_to_state(options.isolated)) | 671 isolatedfile_to_state(options.isolated)) |
669 complete_state = CompleteState( | 672 complete_state = CompleteState( |
670 options.isolated, | 673 options.isolated, |
671 SavedState(complete_state.saved_state.isolated_basedir)) | 674 SavedState(complete_state.saved_state.isolated_basedir)) |
672 | 675 |
673 if not skip_update: | 676 if not skip_update: |
674 # Then load the .isolate and expands directories. | 677 # Then load the .isolate and expands directories. |
675 complete_state.load_isolate( | 678 complete_state.load_isolate( |
676 cwd, isolate, options.path_variables, options.config_variables, | 679 cwd, isolate, options.path_variables, options.config_variables, |
677 options.extra_variables, options.blacklist, options.ignore_broken_items) | 680 options.extra_variables, options.blacklist, options.ignore_broken_items, |
| 681 options.collapse_symlinks) |
678 | 682 |
679 # Regenerate complete_state.saved_state.files. | 683 # Regenerate complete_state.saved_state.files. |
680 if subdir: | 684 if subdir: |
681 subdir = unicode(subdir) | 685 subdir = unicode(subdir) |
682 # This is tricky here. If it is a path, take it from the root_dir. If | 686 # This is tricky here. If it is a path, take it from the root_dir. If |
683 # it is a variable, it must be keyed from the directory containing the | 687 # it is a variable, it must be keyed from the directory containing the |
684 # .isolate file. So translate all variables first. | 688 # .isolate file. So translate all variables first. |
685 translated_path_variables = dict( | 689 translated_path_variables = dict( |
686 (k, | 690 (k, |
687 os.path.normpath(os.path.join(complete_state.saved_state.relative_cwd, | 691 os.path.normpath(os.path.join(complete_state.saved_state.relative_cwd, |
688 v))) | 692 v))) |
689 for k, v in complete_state.saved_state.path_variables.iteritems()) | 693 for k, v in complete_state.saved_state.path_variables.iteritems()) |
690 subdir = isolate_format.eval_variables(subdir, translated_path_variables) | 694 subdir = isolate_format.eval_variables(subdir, translated_path_variables) |
691 subdir = subdir.replace('/', os.path.sep) | 695 subdir = subdir.replace('/', os.path.sep) |
692 | 696 |
693 if not skip_update: | 697 if not skip_update: |
694 complete_state.files_to_metadata(subdir) | 698 complete_state.files_to_metadata(subdir, options.collapse_symlinks) |
695 return complete_state | 699 return complete_state |
696 | 700 |
697 | 701 |
698 def create_isolate_tree(outdir, root_dir, files, relative_cwd, read_only): | 702 def create_isolate_tree(outdir, root_dir, files, relative_cwd, read_only): |
699 """Creates a isolated tree usable for test execution. | 703 """Creates a isolated tree usable for test execution. |
700 | 704 |
701 Returns the current working directory where the isolated command should be | 705 Returns the current working directory where the isolated command should be |
702 started in. | 706 started in. |
703 """ | 707 """ |
704 # Forcibly copy when the tree has to be read only. Otherwise the inode is | 708 # Forcibly copy when the tree has to be read only. Otherwise the inode is |
(...skipping 410 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1115 '-i', '--isolate', | 1119 '-i', '--isolate', |
1116 metavar='FILE', | 1120 metavar='FILE', |
1117 help='.isolate file to load the dependency data from') | 1121 help='.isolate file to load the dependency data from') |
1118 add_variable_option(group) | 1122 add_variable_option(group) |
1119 group.add_option( | 1123 group.add_option( |
1120 '--ignore_broken_items', action='store_true', | 1124 '--ignore_broken_items', action='store_true', |
1121 default=bool(os.environ.get('ISOLATE_IGNORE_BROKEN_ITEMS')), | 1125 default=bool(os.environ.get('ISOLATE_IGNORE_BROKEN_ITEMS')), |
1122 help='Indicates that invalid entries in the isolated file to be ' | 1126 help='Indicates that invalid entries in the isolated file to be ' |
1123 'only be logged and not stop processing. Defaults to True if ' | 1127 'only be logged and not stop processing. Defaults to True if ' |
1124 'env var ISOLATE_IGNORE_BROKEN_ITEMS is set') | 1128 'env var ISOLATE_IGNORE_BROKEN_ITEMS is set') |
| 1129 group.add_option( |
| 1130 '-L', '--collapse_symlinks', action='store_true', |
| 1131 help='Treat any symlinks as if they were the normal underlying file') |
1125 parser.add_option_group(group) | 1132 parser.add_option_group(group) |
1126 | 1133 |
1127 | 1134 |
1128 def add_subdir_option(parser): | 1135 def add_subdir_option(parser): |
1129 parser.add_option( | 1136 parser.add_option( |
1130 '--subdir', | 1137 '--subdir', |
1131 help='Filters to a subdirectory. Its behavior changes depending if it ' | 1138 help='Filters to a subdirectory. Its behavior changes depending if it ' |
1132 'is a relative path as a string or as a path variable. Path ' | 1139 'is a relative path as a string or as a path variable. Path ' |
1133 'variables are always keyed from the directory containing the ' | 1140 'variables are always keyed from the directory containing the ' |
1134 '.isolate file. Anything else is keyed on the root directory.') | 1141 '.isolate file. Anything else is keyed on the root directory.') |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1217 print >> sys.stderr, 'Execution failure: %s' % e | 1224 print >> sys.stderr, 'Execution failure: %s' % e |
1218 return 1 | 1225 return 1 |
1219 | 1226 |
1220 | 1227 |
1221 if __name__ == '__main__': | 1228 if __name__ == '__main__': |
1222 subprocess42.inhibit_os_error_reporting() | 1229 subprocess42.inhibit_os_error_reporting() |
1223 fix_encoding.fix_encoding() | 1230 fix_encoding.fix_encoding() |
1224 tools.disable_buffering() | 1231 tools.disable_buffering() |
1225 colorama.init() | 1232 colorama.init() |
1226 sys.exit(main(sys.argv[1:])) | 1233 sys.exit(main(sys.argv[1:])) |
OLD | NEW |