From a0eb69a1f33109cea208fe64dcef390c74e6be53 Mon Sep 17 00:00:00 2001 From: Erik Stringwell Date: Thu, 21 Mar 2024 15:04:19 +0100 Subject: [PATCH] Add support for no parent directory inference (#832) (#834) Add feature as described in #832. RELNOTES: Automatic creation of parent directory specifications for paths with depth can be prevented in `pkg_tar` archives by setting `create_parents=False`. --- docs/latest.md | 3 ++- pkg/private/tar/build_tar.py | 11 +++++++++-- pkg/private/tar/tar.bzl | 4 ++++ pkg/private/tar/tar_writer.py | 22 +++++++++++++--------- tests/tar/tar_writer_test.py | 19 ++++++++++++++++--- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/docs/latest.md b/docs/latest.md index eea69821..c0c98221 100755 --- a/docs/latest.md +++ b/docs/latest.md @@ -287,7 +287,7 @@ pkg_tar(name, empty_dirs, empty_files, extension, files, include_runfiles, mode, modes, mtime, out, owner, ownername, ownernames, owners, package_dir, package_dir_file, package_file_name, package_variables, portable_mtime, private_stamp_detect, remap_paths, srcs, stamp, - strip_prefix, symlinks) + strip_prefix, symlinks, symlinks) @@ -326,6 +326,7 @@ pkg_tar(name, stamp | Enable file time stamping. Possible values:
  • stamp = 1: Use the time of the build as the modification time of each file in the archive.
  • stamp = 0: Use an "epoch" time for the modification time of each file. This gives good build result caching.
  • stamp = -1: Control the chosen modification time using the --[no]stamp flag.
    Since 0.5.0
    | Integer | optional | 0 | | strip_prefix | (note: Use strip_prefix = "." to strip path to the package but preserve relative paths of sub directories beneath the package.) | String | optional | "" | | symlinks | - | Dictionary: String -> String | optional | {} | +| create_parents | Implicitly create parent directories with default permissions for file paths where parent directories are not specified. | Boolean | optional | True | diff --git a/pkg/private/tar/build_tar.py b/pkg/private/tar/build_tar.py index f4b9f0d3..81ad03fe 100644 --- a/pkg/private/tar/build_tar.py +++ b/pkg/private/tar/build_tar.py @@ -42,7 +42,7 @@ class TarFile(object): class DebError(Exception): pass - def __init__(self, output, directory, compression, compressor, default_mtime): + def __init__(self, output, directory, compression, compressor, create_parents, default_mtime): # Directory prefix on all output paths d = directory.strip('/') self.directory = (d + '/') if d else None @@ -50,12 +50,14 @@ def __init__(self, output, directory, compression, compressor, default_mtime): self.compression = compression self.compressor = compressor self.default_mtime = default_mtime + self.create_parents = create_parents def __enter__(self): self.tarfile = tar_writer.TarFileWriter( self.output, self.compression, self.compressor, + self.create_parents, default_mtime=self.default_mtime) return self @@ -383,6 +385,10 @@ def main(): 'path/to/file=root.root.') parser.add_argument('--stamp_from', default='', help='File to find BUILD_STAMP in') + parser.add_argument('--create_parents', + action='store_true', + help='Automatically creates parent directories implied by a' + ' prefix if they do not exist') options = parser.parse_args() # Parse modes arguments @@ -432,7 +438,8 @@ def main(): directory = helpers.GetFlagValue(options.directory), compression = options.compression, compressor = options.compressor, - default_mtime=default_mtime) as output: + default_mtime=default_mtime, + create_parents=options.create_parents) as output: def file_attributes(filename): if filename.startswith('/'): diff --git a/pkg/private/tar/tar.bzl b/pkg/private/tar/tar.bzl index 963cb5a1..8bea9fbc 100644 --- a/pkg/private/tar/tar.bzl +++ b/pkg/private/tar/tar.bzl @@ -173,6 +173,9 @@ def _pkg_tar_impl(ctx): args.set_param_file_format("flag_per_line") args.use_param_file("@%s", use_always = False) + if ctx.attr.create_parents: + args.add("--create_parents") + inputs = depset( direct = ctx.files.deps + files, transitive = mapping_context.file_deps, @@ -264,6 +267,7 @@ pkg_tar_impl = rule( "compressor_args": attr.string( doc = """Arg list for `compressor`.""", ), + "create_parents": attr.bool(default = True), # Common attributes "out": attr.output(mandatory = True), diff --git a/pkg/private/tar/tar_writer.py b/pkg/private/tar/tar_writer.py index 325db761..957ce45a 100644 --- a/pkg/private/tar/tar_writer.py +++ b/pkg/private/tar/tar_writer.py @@ -46,6 +46,7 @@ def __init__(self, name, compression='', compressor='', + create_parents=False, default_mtime=None, preserve_tar_mtimes=True): """TarFileWriter wraps tarfile.open(). @@ -106,6 +107,7 @@ def __init__(self, # we can adjust that here based on the setting of root_dirctory. self.directories.add('/') self.directories.add('./') + self.create_parents = create_parents def __enter__(self): return self @@ -219,7 +221,8 @@ def add_file(self, mtime = self.default_mtime # Make directories up the file - self.add_parents(name, mtime=mtime, mode=0o755, uid=uid, gid=gid, uname=uname, gname=gname) + if self.create_parents: + self.add_parents(name, mtime=mtime, mode=0o755, uid=uid, gid=gid, uname=uname, gname=gname) tarinfo = tarfile.TarInfo(name) tarinfo.mtime = mtime @@ -291,14 +294,15 @@ def add_tar(self, if prefix: in_name = os.path.normpath(prefix + in_name).replace(os.path.sep, '/') tarinfo.name = in_name - self.add_parents( - path=tarinfo.name, - mtime=tarinfo.mtime, - mode=0o755, - uid=tarinfo.uid, - gid=tarinfo.gid, - uname=tarinfo.uname, - gname=tarinfo.gname) + if self.create_parents: + self.add_parents( + path=tarinfo.name, + mtime=tarinfo.mtime, + mode=0o755, + uid=tarinfo.uid, + gid=tarinfo.gid, + uname=tarinfo.uname, + gname=tarinfo.gname) if prefix is not None: # Relocate internal hardlinks as well to avoid breaking them. diff --git a/tests/tar/tar_writer_test.py b/tests/tar/tar_writer_test.py index ad31b0c9..8503d317 100644 --- a/tests/tar/tar_writer_test.py +++ b/tests/tar/tar_writer_test.py @@ -138,7 +138,7 @@ def testMergeTarRelocated(self): {"name": "foo/a", "data": b"a"}, {"name": "foo/ab", "data": b"ab"}, ] - with tar_writer.TarFileWriter(self.tempfile) as f: + with tar_writer.TarFileWriter(self.tempfile, create_parents=True) as f: datafile = self.data_files.Rlocation( "rules_pkg/tests/testdata/tar_test.tar") f.add_tar(datafile, name_filter=lambda n: n != "./b", prefix="foo") @@ -176,7 +176,7 @@ def testPreserveTarMtimesFalse(self): self.assertEqual(output_file.mtime, 0) def testAddingDirectoriesForFile(self): - with tar_writer.TarFileWriter(self.tempfile) as f: + with tar_writer.TarFileWriter(self.tempfile, create_parents=True) as f: f.add_file("d/f") content = [ {"name": "d", "mode": 0o755}, @@ -185,7 +185,7 @@ def testAddingDirectoriesForFile(self): self.assertTarFileContent(self.tempfile, content) def testAddingDirectoriesForFileManually(self): - with tar_writer.TarFileWriter(self.tempfile) as f: + with tar_writer.TarFileWriter(self.tempfile, create_parents=True) as f: f.add_file("d", tarfile.DIRTYPE) f.add_file("d/f") @@ -210,6 +210,19 @@ def testAddingDirectoriesForFileManually(self): ] self.assertTarFileContent(self.tempfile, content) + def testAddingOnlySpecifiedFiles(self): + with tar_writer.TarFileWriter(self.tempfile) as f: + f.add_file("a", tarfile.DIRTYPE) + f.add_file("a/b", tarfile.DIRTYPE) + f.add_file("a/b/", tarfile.DIRTYPE) + f.add_file("a/b/c/f") + content = [ + {"name": "a", "mode": 0o755}, + {"name": "a/b", "mode": 0o755}, + {"name": "a/b/c/f"}, + ] + self.assertTarFileContent(self.tempfile, content) + def testPackageDirAttribute(self): """Tests package_dir of pkg_tar.""" package_dir = self.data_files.Rlocation(