diff --git a/compiler/cli.py b/compiler/cli.py index d1fcb59..a721c13 100644 --- a/compiler/cli.py +++ b/compiler/cli.py @@ -92,7 +92,13 @@ def make_command_line_parser(): help='Path to add to sys.path, may be repeated to provide multiple roots.', action='append', default=[], - dest='import_roots') + dest='import_roots'), + parser.add_argument( + '--no_remove', + help='Keep extracted files after program finishes if --zip_safe is ' + + 'enabled.', + type=bool_from_string, + required=True) return parser @@ -170,5 +176,6 @@ def main(argv): manifest_root=args.manifest_root, timestamp=args.timestamp, zip_safe=args.zip_safe, + no_remove=args.no_remove, ) par.create() diff --git a/compiler/cli_test.py b/compiler/cli_test.py index 4bdaf34..bd375e6 100644 --- a/compiler/cli_test.py +++ b/compiler/cli_test.py @@ -38,6 +38,7 @@ def test_make_command_line_parser(self): '--output_par=baz', '--stub_file=quux', '--zip_safe=False', + '--no_remove=False', '--import_root=root1', '--import_root=root2', 'foo', @@ -58,6 +59,7 @@ def test_make_command_line_parser_for_interprerter(self): '--output_par=baz', '--stub_file=quux', '--zip_safe=False', + '--no_remove=False', '--interpreter=foobar', 'foo', ]) diff --git a/compiler/python_archive.py b/compiler/python_archive.py index 2c69c58..a4a9093 100755 --- a/compiler/python_archive.py +++ b/compiler/python_archive.py @@ -48,7 +48,8 @@ _boilerplate_template = """\ # Boilerplate added by subpar/compiler/python_archive.py from %(runtime_package)s import support as _ -_.setup(import_roots=%(import_roots)s, zip_safe=%(zip_safe)s) +_.setup(import_roots=%(import_roots)s, zip_safe=%(zip_safe)s, + no_remove=%(no_remove)s) del _ # End boilerplate """ @@ -102,6 +103,7 @@ def __init__(self, output_filename, timestamp, zip_safe, + no_remove, ): self.main_filename = main_filename @@ -114,6 +116,7 @@ def __init__(self, t = datetime.utcfromtimestamp(timestamp) self.timestamp_tuple = t.timetuple()[0:6] self.zip_safe = zip_safe + self.no_remove = no_remove self.compression = zipfile.ZIP_DEFLATED @@ -172,6 +175,7 @@ def generate_boilerplate(self, import_roots): 'runtime_package': _runtime_package, 'import_roots': str(import_roots), 'zip_safe': self.zip_safe, + 'no_remove': self.no_remove, } return boilerplate_contents.encode('ascii').decode('ascii') diff --git a/compiler/python_archive_test.py b/compiler/python_archive_test.py index b918399..05400d7 100644 --- a/compiler/python_archive_test.py +++ b/compiler/python_archive_test.py @@ -51,6 +51,7 @@ def setUp(self): self.date_time_tuple = (1980, 1, 1, 0, 0, 0) self.timestamp = 315532800 self.zip_safe = True + self.no_remove = False def _construct(self, manifest_filename=None): return python_archive.PythonArchive( @@ -62,6 +63,7 @@ def _construct(self, manifest_filename=None): output_filename=self.output_filename, timestamp=self.timestamp, zip_safe=self.zip_safe, + no_remove=self.no_remove, ) def test_create_manifest_not_found(self): diff --git a/docs/subpar.html b/docs/subpar.html index d1fc5d1..d510172 100644 --- a/docs/subpar.html +++ b/docs/subpar.html @@ -154,7 +154,7 @@

Attributes

parfile

-
parfile(name, src, compiler, compiler_args, default_python_version, imports, main, zip_safe)
+
parfile(name, src, compiler, compiler_args, default_python_version, imports, main, no_remove, zip_safe)

A self-contained, single-file Python program, with a .par file extension.

You probably want to use par_binary() instead of this.

@@ -223,6 +223,14 @@

Attributes

See py_binary.main

+ + no_remove + +

Boolean; Optional; Default is False

+

Whether to keep the extracted temporary directory after the +program finishes, if zip_safe is enabled.

+ + zip_safe @@ -239,7 +247,7 @@

Attributes

parfile_test

-
parfile_test(name, src, compiler, compiler_args, default_python_version, imports, main, zip_safe)
+
parfile_test(name, src, compiler, compiler_args, default_python_version, imports, main, no_remove, zip_safe)

Identical to par_binary, but the rule is marked as being a test.

You probably want to use par_test() instead of this.

@@ -306,6 +314,14 @@

Attributes

See py_binary.main

+ + no_remove + +

Boolean; Optional; Default is False

+

Whether to keep the extracted temporary directory after the +program finishes, if zip_safe is enabled.

+ + zip_safe diff --git a/docs/subpar.md b/docs/subpar.md index aaf19ab..ddde9df 100644 --- a/docs/subpar.md +++ b/docs/subpar.md @@ -108,7 +108,7 @@ specifically need to test a module's behaviour when used in a .par binary. ## parfile
-parfile(name, src, compiler, compiler_args, default_python_version, imports, main, zip_safe)
+parfile(name, src, compiler, compiler_args, default_python_version, imports, main, no_remove, zip_safe)
 
A self-contained, single-file Python program, with a .par file extension. @@ -182,6 +182,14 @@ the application.

See py_binary.main

+ + no_remove + +

Boolean; Optional; Default is False

+

Whether to keep the extracted temporary directory after the +program finishes, if zip_safe is enabled.

+ + zip_safe @@ -198,7 +206,7 @@ par file executes.

## parfile_test
-parfile_test(name, src, compiler, compiler_args, default_python_version, imports, main, zip_safe)
+parfile_test(name, src, compiler, compiler_args, default_python_version, imports, main, no_remove, zip_safe)
 
Identical to par_binary, but the rule is marked as being a test. @@ -269,6 +277,14 @@ the application.

See py_binary.main

+ + no_remove + +

Boolean; Optional; Default is False

+

Whether to keep the extracted temporary directory after the +program finishes, if zip_safe is enabled.

+ + zip_safe diff --git a/runtime/support.py b/runtime/support.py index e8ec253..b5bcb84 100644 --- a/runtime/support.py +++ b/runtime/support.py @@ -95,7 +95,7 @@ def _find_archive(): return archive_path -def _extract_files(archive_path): +def _extract_files(archive_path, no_remove): """Extract the contents of this .par file to disk. This creates a temporary directory, and registers an atexit @@ -107,9 +107,10 @@ def _extract_files(archive_path): """ extract_dir = tempfile.mkdtemp() - def _extract_files_cleanup(): - shutil.rmtree(extract_dir, ignore_errors=True) - atexit.register(_extract_files_cleanup) + if not no_remove: + def _extract_files_cleanup(): + shutil.rmtree(extract_dir, ignore_errors=True) + atexit.register(_extract_files_cleanup) _log('# extracting %s to %s' % (archive_path, extract_dir)) zip_file = zipfile.ZipFile(archive_path, mode='r') @@ -282,7 +283,7 @@ def _initialize_import_path(import_roots, import_prefix): _log('# adding %s to sys.path' % full_roots) -def setup(import_roots, zip_safe): +def setup(import_roots, zip_safe, no_remove): """Initialize subpar run-time support Args: @@ -309,7 +310,7 @@ def setup(import_roots, zip_safe): # Extract files to disk if necessary if not zip_safe: - extract_dir = _extract_files(archive_path) + extract_dir = _extract_files(archive_path, no_remove) # sys.path[0] is the name of the executing .par file. Point # it to the extract directory instead, so that Python searches # there for imports. diff --git a/runtime/support_test.py b/runtime/support_test.py index 565adb1..8dda700 100644 --- a/runtime/support_test.py +++ b/runtime/support_test.py @@ -101,7 +101,7 @@ def test__find_archive(self): def test__extract_files(self): # Extract zipfile - extract_path = support._extract_files(self.zipfile_name) + extract_path = support._extract_files(self.zipfile_name, False) # Check results self.assertTrue(os.path.isdir(extract_path)) @@ -140,7 +140,7 @@ def test_setup(self): mock_sys_path[0] = self.zipfile_name sys.path = mock_sys_path success = support.setup(import_roots=['some_root', 'another_root'], - zip_safe=True) + zip_safe=True, no_remove=False) self.assertTrue(success) finally: sys.path = old_sys_path @@ -165,7 +165,8 @@ def test_setup__extract(self): mock_sys_path = list(sys.path) mock_sys_path[0] = self.zipfile_name sys.path = mock_sys_path - success = support.setup(import_roots=['some_root'], zip_safe=False) + success = support.setup(import_roots=['some_root'], zip_safe=False, + no_remove=False) self.assertTrue(success) finally: sys.path = old_sys_path diff --git a/subpar.bzl b/subpar.bzl index ae685bf..11e3b18 100644 --- a/subpar.bzl +++ b/subpar.bzl @@ -73,6 +73,7 @@ def _parfile_impl(ctx): ] zip_safe = ctx.attr.zip_safe + no_remove = ctx.attr.no_remove # Assemble command line for .par compiler args = ctx.attr.compiler_args + [ @@ -84,6 +85,8 @@ def _parfile_impl(ctx): ctx.attr.src.files_to_run.executable.path, "--zip_safe", str(zip_safe), + '--no_remove', + str(no_remove), ] for import_root in import_roots: args.extend(['--import_root', import_root]) @@ -136,6 +139,7 @@ parfile_attrs = { ), "compiler_args": attr.string_list(default = []), "zip_safe": attr.bool(default = True), + "no_remove": attr.bool(default = False), } # Rule to create a parfile given a py_binary() as input @@ -171,6 +175,9 @@ Args: extracted to a temporary directory on disk each time the par file executes. + no_remove: Whether to keep the extracted temporary directory after the + program finishes, if zip_safe is enabled. + TODO(b/27502830): A directory foo.par.runfiles is also created. This is a bug, don't use or depend on it. """ @@ -204,6 +211,7 @@ def par_binary(name, **kwargs): compiler = kwargs.pop("compiler", None) compiler_args = kwargs.pop("compiler_args", []) zip_safe = kwargs.pop("zip_safe", True) + no_remove = kwargs.pop('no_remove', False) native.py_binary(name = name, **kwargs) main = kwargs.get("main", name + ".py") @@ -222,6 +230,7 @@ def par_binary(name, **kwargs): testonly = testonly, visibility = visibility, zip_safe = zip_safe, + no_remove=no_remove, ) def par_test(name, **kwargs): @@ -232,6 +241,7 @@ def par_test(name, **kwargs): """ compiler = kwargs.pop("compiler", None) zip_safe = kwargs.pop("zip_safe", True) + no_remove = kwargs.pop('no_remove', False) native.py_test(name = name, **kwargs) main = kwargs.get("main", name + ".py") @@ -249,4 +259,5 @@ def par_test(name, **kwargs): testonly = testonly, visibility = visibility, zip_safe = zip_safe, + no_remove=no_remove, ) diff --git a/tests/BUILD b/tests/BUILD index f38083b..00030fd 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -90,6 +90,7 @@ par_binary( main = "package_extract/extract.py", srcs_version = "PY2AND3", zip_safe = False, + no_remove = False, ) par_binary(