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 """Runs a command with optional isolated input/output. | 6 """Runs a command with optional isolated input/output. |
7 | 7 |
8 Despite name "run_isolated", can run a generic non-isolated command specified as | 8 Despite name "run_isolated", can run a generic non-isolated command specified as |
9 args. | 9 args. |
10 | 10 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
86 RUN_ISOLATED_LOG_FILE = 'run_isolated.log' | 86 RUN_ISOLATED_LOG_FILE = 'run_isolated.log' |
87 | 87 |
88 | 88 |
89 # The name of the log to use for the run_test_cases.py command | 89 # The name of the log to use for the run_test_cases.py command |
90 RUN_TEST_CASES_LOG = 'run_test_cases.log' | 90 RUN_TEST_CASES_LOG = 'run_test_cases.log' |
91 | 91 |
92 | 92 |
93 # Use short names for temporary directories. This is driven by Windows, which | 93 # Use short names for temporary directories. This is driven by Windows, which |
94 # imposes a relatively short maximum path length of 260 characters, often | 94 # imposes a relatively short maximum path length of 260 characters, often |
95 # referred to as MAX_PATH. It is relatively easy to create files with longer | 95 # referred to as MAX_PATH. It is relatively easy to create files with longer |
96 # path length. A use case is with recursive depedency treesV like npm packages. | 96 # path length. A use case is with recursive dependency trees like npm packages. |
97 # | 97 # |
98 # It is recommended to start the script with a `root_dir` as short as | 98 # It is recommended to start the script with a `root_dir` as short as |
99 # possible. | 99 # possible. |
100 # - ir stands for isolated_run | 100 # - ir stands for isolated_run |
101 # - io stands for isolated_out | 101 # - io stands for isolated_out |
102 # - it stands for isolated_tmp | 102 # - it stands for isolated_tmp |
103 ISOLATED_RUN_DIR = u'ir' | 103 ISOLATED_RUN_DIR = u'ir' |
104 ISOLATED_OUT_DIR = u'io' | 104 ISOLATED_OUT_DIR = u'io' |
105 ISOLATED_TMP_DIR = u'it' | 105 ISOLATED_TMP_DIR = u'it' |
106 | 106 |
107 | 107 |
108 def get_as_zip_package(executable=True): | 108 def get_as_zip_package(executable=True): |
109 """Returns ZipPackage with this module and all its dependencies. | 109 """Returns ZipPackage with this module and all its dependencies. |
110 | 110 |
111 If |executable| is True will store run_isolated.py as __main__.py so that | 111 If |executable| is True will store run_isolated.py as __main__.py so that |
112 zip package is directly executable be python. | 112 zip package is directly executable be python. |
113 """ | 113 """ |
114 # Building a zip package when running from another zip package is | 114 # Building a zip package when running from another zip package is |
115 # unsupported and probably unneeded. | 115 # unsupported and probably unneeded. |
116 assert not zip_package.is_zipped_module(sys.modules[__name__]) | 116 assert not zip_package.is_zipped_module(sys.modules[__name__]) |
117 assert THIS_FILE_PATH | 117 assert THIS_FILE_PATH |
118 assert BASE_DIR | 118 assert BASE_DIR |
119 package = zip_package.ZipPackage(root=BASE_DIR) | 119 package = zip_package.ZipPackage(root=BASE_DIR) |
120 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None) | 120 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None) |
121 package.add_python_file(os.path.join(BASE_DIR, 'isolate_storage.py')) | 121 package.add_python_file(os.path.join(BASE_DIR, 'isolate_storage.py')) |
122 package.add_python_file(os.path.join(BASE_DIR, 'isolated_format.py')) | 122 package.add_python_file(os.path.join(BASE_DIR, 'isolated_format.py')) |
123 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py')) | 123 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py')) |
124 package.add_python_file(os.path.join(BASE_DIR, 'isolate_format.py')) | |
124 package.add_python_file(os.path.join(BASE_DIR, 'auth.py')) | 125 package.add_python_file(os.path.join(BASE_DIR, 'auth.py')) |
125 package.add_python_file(os.path.join(BASE_DIR, 'cipd.py')) | 126 package.add_python_file(os.path.join(BASE_DIR, 'cipd.py')) |
126 package.add_python_file(os.path.join(BASE_DIR, 'named_cache.py')) | 127 package.add_python_file(os.path.join(BASE_DIR, 'named_cache.py')) |
128 package.add_python_file(os.path.join(BASE_DIR, 'run_isolated.py')) | |
Vadim Sh.
2017/05/11 03:09:09
note that this file may already be included in the
| |
127 package.add_directory(os.path.join(BASE_DIR, 'libs')) | 129 package.add_directory(os.path.join(BASE_DIR, 'libs')) |
128 package.add_directory(os.path.join(BASE_DIR, 'third_party')) | 130 package.add_directory(os.path.join(BASE_DIR, 'third_party')) |
129 package.add_directory(os.path.join(BASE_DIR, 'utils')) | 131 package.add_directory(os.path.join(BASE_DIR, 'utils')) |
130 return package | 132 return package |
131 | 133 |
132 | 134 |
133 def make_temp_dir(prefix, root_dir): | 135 def make_temp_dir(prefix, root_dir): |
134 """Returns a new unique temporary directory.""" | 136 """Returns a new unique temporary directory.""" |
135 return unicode(tempfile.mkdtemp(prefix=prefix, dir=root_dir)) | 137 return unicode(tempfile.mkdtemp(prefix=prefix, dir=root_dir)) |
136 | 138 |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
275 exit_code = proc.wait(grace_period or None) | 277 exit_code = proc.wait(grace_period or None) |
276 except subprocess42.TimeoutExpired: | 278 except subprocess42.TimeoutExpired: |
277 # Now kill for real. The user can distinguish between the | 279 # Now kill for real. The user can distinguish between the |
278 # following states: | 280 # following states: |
279 # - signal but process exited within grace period, | 281 # - signal but process exited within grace period, |
280 # hard_timed_out will be set but the process exit code will be | 282 # hard_timed_out will be set but the process exit code will be |
281 # script provided. | 283 # script provided. |
282 # - processed exited late, exit code will be -9 on posix. | 284 # - processed exited late, exit code will be -9 on posix. |
283 logging.warning('Grace exhausted; sending SIGKILL') | 285 logging.warning('Grace exhausted; sending SIGKILL') |
284 proc.kill() | 286 proc.kill() |
285 logging.info('Waiting for proces exit') | 287 logging.info('Waiting for process exit') |
286 exit_code = proc.wait() | 288 exit_code = proc.wait() |
287 except OSError: | 289 except OSError: |
288 # This is not considered to be an internal error. The executable simply | 290 # This is not considered to be an internal error. The executable simply |
289 # does not exit. | 291 # does not exit. |
290 sys.stderr.write( | 292 sys.stderr.write( |
291 '<The executable does not exist or a dependent library is missing>\n' | 293 '<The executable does not exist or a dependent library is missing>\n' |
292 '<Check for missing .so/.dll in the .isolate or GN file>\n' | 294 '<Check for missing .so/.dll in the .isolate or GN file>\n' |
293 '<Command: %s>\n' % command) | 295 '<Command: %s>\n' % command) |
294 if os.environ.get('SWARMING_TASK_ID'): | 296 if os.environ.get('SWARMING_TASK_ID'): |
295 # Give an additional hint when running as a swarming task. | 297 # Give an additional hint when running as a swarming task. |
(...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
679 'pins', # dict with installed cipd pins to return to the server | 681 'pins', # dict with installed cipd pins to return to the server |
680 ]) | 682 ]) |
681 | 683 |
682 | 684 |
683 @contextlib.contextmanager | 685 @contextlib.contextmanager |
684 def noop_install_packages(_run_dir): | 686 def noop_install_packages(_run_dir): |
685 """Placeholder for 'install_client_and_packages' if cipd is disabled.""" | 687 """Placeholder for 'install_client_and_packages' if cipd is disabled.""" |
686 yield None | 688 yield None |
687 | 689 |
688 | 690 |
689 def _install_packages(run_dir, cipd_cache_dir, client, packages, timeout): | 691 def _install_packages(run_dir, cipd_cache_dir, client, packages, timeout, |
692 isolate_cache=None): | |
Vadim Sh.
2017/05/11 03:09:09
please document in Args, at least its type (in all
| |
690 """Calls 'cipd ensure' for packages. | 693 """Calls 'cipd ensure' for packages. |
691 | 694 |
692 Args: | 695 Args: |
693 run_dir (str): root of installation. | 696 run_dir (str): root of installation. |
694 cipd_cache_dir (str): the directory to use for the cipd package cache. | 697 cipd_cache_dir (str): the directory to use for the cipd package cache. |
695 client (CipdClient): the cipd client to use | 698 client (CipdClient): the cipd client to use |
696 packages: packages to install, list [(path, package_name, version), ...]. | 699 packages: packages to install, list [(path, package_name, version), ...]. |
697 timeout: max duration in seconds that this function can take. | 700 timeout: max duration in seconds that this function can take. |
698 | 701 |
699 Returns: list of pinned packages. Looks like [ | 702 Returns: list of pinned packages. Looks like [ |
(...skipping 22 matching lines...) Expand all Loading... | |
722 by_path[path].append((name, version, i)) | 725 by_path[path].append((name, version, i)) |
723 | 726 |
724 pins = client.ensure( | 727 pins = client.ensure( |
725 run_dir, | 728 run_dir, |
726 { | 729 { |
727 subdir: [(name, vers) for name, vers, _ in pkgs] | 730 subdir: [(name, vers) for name, vers, _ in pkgs] |
728 for subdir, pkgs in by_path.iteritems() | 731 for subdir, pkgs in by_path.iteritems() |
729 }, | 732 }, |
730 cache_dir=cipd_cache_dir, | 733 cache_dir=cipd_cache_dir, |
731 timeout=timeout, | 734 timeout=timeout, |
735 isolate_cache=isolate_cache, | |
732 ) | 736 ) |
733 | 737 |
734 for subdir, pin_list in sorted(pins.iteritems()): | 738 for subdir, pin_list in sorted(pins.iteritems()): |
735 this_subdir = by_path[subdir] | 739 this_subdir = by_path[subdir] |
736 for i, (name, version) in enumerate(pin_list): | 740 for i, (name, version) in enumerate(pin_list): |
737 insert_pin(subdir, name, version, this_subdir[i][2]) | 741 insert_pin(subdir, name, version, this_subdir[i][2]) |
738 | 742 |
739 assert None not in package_pins | 743 assert None not in package_pins |
740 | 744 |
741 return package_pins | 745 return package_pins |
742 | 746 |
743 | 747 |
744 @contextlib.contextmanager | 748 @contextlib.contextmanager |
745 def install_client_and_packages( | 749 def install_client_and_packages( |
746 run_dir, packages, service_url, client_package_name, | 750 run_dir, packages, service_url, client_package_name, |
747 client_version, cache_dir, timeout=None): | 751 client_version, cache_dir, timeout=None, isolate_cache=None): |
748 """Bootstraps CIPD client and installs CIPD packages. | 752 """Bootstraps CIPD client and installs CIPD packages. |
749 | 753 |
750 Yields CipdClient, stats, client info and pins (as single CipdInfo object). | 754 Yields CipdClient, stats, client info and pins (as single CipdInfo object). |
751 | 755 |
752 Pins and the CIPD client info are in the form of: | 756 Pins and the CIPD client info are in the form of: |
753 [ | 757 [ |
754 { | 758 { |
755 "path": path, "package_name": package_name, "version": version, | 759 "path": path, "package_name": package_name, "version": version, |
756 }, | 760 }, |
757 ... | 761 ... |
(...skipping 20 matching lines...) Expand all Loading... | |
778 cache_dir (str): where to keep cache of cipd clients, packages and tags. | 782 cache_dir (str): where to keep cache of cipd clients, packages and tags. |
779 timeout: max duration in seconds that this function can take. | 783 timeout: max duration in seconds that this function can take. |
780 """ | 784 """ |
781 assert cache_dir | 785 assert cache_dir |
782 | 786 |
783 timeoutfn = tools.sliding_timeout(timeout) | 787 timeoutfn = tools.sliding_timeout(timeout) |
784 start = time.time() | 788 start = time.time() |
785 | 789 |
786 cache_dir = os.path.abspath(cache_dir) | 790 cache_dir = os.path.abspath(cache_dir) |
787 cipd_cache_dir = os.path.join(cache_dir, 'cache') # tag and instance caches | 791 cipd_cache_dir = os.path.join(cache_dir, 'cache') # tag and instance caches |
792 file_path.ensure_tree(unicode(cipd_cache_dir)) | |
793 | |
788 run_dir = os.path.abspath(run_dir) | 794 run_dir = os.path.abspath(run_dir) |
789 packages = packages or [] | 795 packages = packages or [] |
790 | 796 |
791 get_client_start = time.time() | 797 get_client_start = time.time() |
792 client_manager = cipd.get_client( | 798 client_manager = cipd.get_client( |
793 service_url, client_package_name, client_version, cache_dir, | 799 service_url, client_package_name, client_version, cache_dir, |
794 timeout=timeoutfn()) | 800 timeout=timeoutfn()) |
795 | 801 |
796 with client_manager as client: | 802 with client_manager as client: |
797 get_client_duration = time.time() - get_client_start | 803 get_client_duration = time.time() - get_client_start |
798 | 804 |
799 package_pins = [] | 805 package_pins = [] |
800 if packages: | 806 if packages: |
801 package_pins = _install_packages( | 807 package_pins = _install_packages( |
802 run_dir, cipd_cache_dir, client, packages, timeoutfn()) | 808 run_dir, cipd_cache_dir, client, packages, timeoutfn(), isolate_cache) |
803 | 809 |
804 file_path.make_tree_files_read_only(run_dir) | 810 file_path.make_tree_files_read_only(run_dir) |
805 | 811 |
806 total_duration = time.time() - start | 812 total_duration = time.time() - start |
807 logging.info( | 813 logging.info( |
808 'Installing CIPD client and packages took %d seconds', total_duration) | 814 'Installing CIPD client and packages took %d seconds', total_duration) |
809 | 815 |
810 yield CipdInfo( | 816 yield CipdInfo( |
811 client=client, | 817 client=client, |
812 cache_dir=cipd_cache_dir, | 818 cache_dir=cipd_cache_dir, |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
998 if options.json: | 1004 if options.json: |
999 options.json = unicode(os.path.abspath(options.json)) | 1005 options.json = unicode(os.path.abspath(options.json)) |
1000 | 1006 |
1001 cipd.validate_cipd_options(parser, options) | 1007 cipd.validate_cipd_options(parser, options) |
1002 | 1008 |
1003 install_packages_fn = noop_install_packages | 1009 install_packages_fn = noop_install_packages |
1004 if options.cipd_enabled: | 1010 if options.cipd_enabled: |
1005 install_packages_fn = lambda run_dir: install_client_and_packages( | 1011 install_packages_fn = lambda run_dir: install_client_and_packages( |
1006 run_dir, cipd.parse_package_args(options.cipd_packages), | 1012 run_dir, cipd.parse_package_args(options.cipd_packages), |
1007 options.cipd_server, options.cipd_client_package, | 1013 options.cipd_server, options.cipd_client_package, |
1008 options.cipd_client_version, cache_dir=options.cipd_cache) | 1014 options.cipd_client_version, cache_dir=options.cipd_cache, |
1015 isolate_cache=isolate_cache) | |
1009 | 1016 |
1010 @contextlib.contextmanager | 1017 @contextlib.contextmanager |
1011 def init_named_caches(run_dir): | 1018 def init_named_caches(run_dir): |
1012 # WARNING: this function depends on "options" variable defined in the outer | 1019 # WARNING: this function depends on "options" variable defined in the outer |
1013 # function. | 1020 # function. |
1014 with named_cache_manager.open(): | 1021 with named_cache_manager.open(): |
1015 named_cache_manager.create_symlinks(run_dir, options.named_caches) | 1022 named_cache_manager.create_symlinks(run_dir, options.named_caches) |
1016 try: | 1023 try: |
1017 yield | 1024 yield |
1018 finally: | 1025 finally: |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1060 return 1 | 1067 return 1 |
1061 | 1068 |
1062 | 1069 |
1063 if __name__ == '__main__': | 1070 if __name__ == '__main__': |
1064 subprocess42.inhibit_os_error_reporting() | 1071 subprocess42.inhibit_os_error_reporting() |
1065 # Ensure that we are always running with the correct encoding. | 1072 # Ensure that we are always running with the correct encoding. |
1066 fix_encoding.fix_encoding() | 1073 fix_encoding.fix_encoding() |
1067 file_path.enable_symlink() | 1074 file_path.enable_symlink() |
1068 | 1075 |
1069 sys.exit(main(sys.argv[1:])) | 1076 sys.exit(main(sys.argv[1:])) |
OLD | NEW |