Skip to content

Commit

Permalink
Handle symlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
ewianda committed Jan 22, 2025
1 parent 0b8b916 commit 0e4d8de
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 287 deletions.
72 changes: 71 additions & 1 deletion lib/private/modify_mtree.awk
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,75 @@
if (package_dir != "") {
sub(/^/, package_dir "/")
}
print;
if (preserve_symlinks != "") {
# By default Bazel reports symlinks as regular file/dir therefore mtree_spec has no way of knowing that a file
# is a symlink. This is a problem when we want to preserve symlinks especially for symlink sensitive applications
# such as nodejs with pnpm. To work around this we need to determine if a file a symlink and if so, we need to
# determine where the symlink points to by calling readlink repeatedly until we get the final destination.
#
# We then need to decide if it's a symlink based on how many times we had to call readlink and where we ended up.
#
# Unlike Bazels own symlinks, which points out of the sandbox symlinks, symlinks created by ctx.actions.symlink
# stays within the bazel sandbox so it's possible to detect those.
#
# See https://github.com/bazelbuild/rules_pkg/pull/609

symlink = ""
if ($0 ~ /type=file/ && $0 ~ /content=/) {
match($0, /content=[^ ]+/)
content_field = substr($0, RSTART, RLENGTH)
split(content_field, parts, "=")
path = parts[2]
# Store paths for look up
symlink_map[path] = $1
# Resolve the symlink if it exists
resolved_path = ""
cmd = "readlink -f " path
cmd | getline resolved_path
close(cmd)

if (resolved_path) {
if (resolved_path ~ bin_dir) {
# Strip down the resolved path to start from bin_dir
sub("^.*" bin_dir, bin_dir, resolved_path)
if (path != resolved_path) {
# Replace the content field with the new path
symlink = resolved_path
}
}
}
}
if (symlink != "") {
line_array[NR] = $1 SUBSEP resolved_path
}
else {
line_array[NR] = $0 # Store other lines too, with an empty path
}
}

else {

print; # Print immediately if symlinks are not preserved

}
}
END {
if (preserve_symlinks != "") {
# Process symlinks if needed
for (i = 1; i <= NR; i++) {
line = line_array[i]
if (index(line, SUBSEP) > 0) { # Check if this path was a symlink
split(line, fields, SUBSEP)
field0 = fields[1]
resolved_path = fields[2]
linked_to = symlink_map[resolved_path]
# Adjust the line for symlink using the map we created
new_line = field0 " type=link link=" linked_to
print new_line
} else {
# Print the original line if no symlink adjustment was needed
print line
}
}
}
}
65 changes: 0 additions & 65 deletions lib/private/tar.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -446,71 +446,6 @@ def _mtree_impl(ctx):

return DefaultInfo(files = depset([out]), runfiles = ctx.runfiles([out]))

def _mtree_mutate_impl(ctx):
srcs_runfiles = [
src[DefaultInfo].default_runfiles.files
for src in ctx.attr.srcs
]
args = ctx.actions.args()
bsdtar = ctx.toolchains[TAR_TOOLCHAIN_TYPE]
mtree_generator = ctx.executable.mtree_generator.path

out_mtree = ctx.outputs.out
args.add("--input", ctx.file.mtree)
args.add("--output", out_mtree)
args.add("--bin_dir", ctx.bin_dir.path)

if ctx.attr.owner:
args.add("--owner", ctx.attr.owner)
if ctx.attr.ownername:
args.add("--ownername", ctx.attr.ownername)
if ctx.attr.strip_prefix:
args.add("--strip_prefix", ctx.attr.strip_prefix)
if ctx.attr.package_dir:
args.add("--package_dir", ctx.attr.package_dir)
if ctx.attr.mtime:
args.add("--mtime", ctx.attr.mtime)

#executable = bsdtar.tarinfo.binary,
inputs = ctx.files.srcs[:]
inputs.append(ctx.file.mtree)
ctx.actions.run(
executable = mtree_generator,
arguments = [args],
inputs = depset(
direct = inputs,
transitive = srcs_runfiles + [
ctx.attr.mtree_generator.default_runfiles.files,
],
),
outputs = [out_mtree],
)

return [DefaultInfo(files = depset([out_mtree]))]

mtree_mutate = rule(
implementation = _mtree_mutate_impl,
attrs = {
"mtree": attr.label(allow_single_file = True),
"awk_script": attr.label(allow_single_file = True, default = "@aspect_bazel_lib//lib/private:modify_mtree.awk"),
"srcs": attr.label_list(allow_files = True),
"strip_prefix": attr.string(),
"package_dir": attr.string(),
"mtime": attr.string(),
"owner": attr.string(),
"ownername": attr.string(),
"out": attr.output(),
"mtree_generator": attr.label(
default = Label("//tools/mtree:mtree"),
executable = True,
cfg = "exec",
),
},
toolchains = [
TAR_TOOLCHAIN_TYPE,
"@aspect_bazel_lib//lib:coreutils_toolchain_type",
],
)
tar_lib = struct(
attrs = _tar_attrs,
implementation = _tar_impl,
Expand Down
48 changes: 38 additions & 10 deletions lib/tar.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ TODO:
load("@bazel_skylib//lib:types.bzl", "types")
load("//lib:expand_template.bzl", "expand_template")
load("//lib:utils.bzl", "propagate_common_rule_attributes")
load("//lib/private:tar.bzl", _mutate_mtree = "mtree_mutate", _tar = "tar", _tar_lib = "tar_lib")
load("//lib/private:tar.bzl", _tar = "tar", _tar_lib = "tar_lib")

mtree_spec = rule(
doc = "Create an mtree specification to map a directory hierarchy. See https://man.freebsd.org/cgi/man.cgi?mtree(8)",
Expand Down Expand Up @@ -137,6 +137,8 @@ def tar(name, mtree = "auto", stamp = 0, **kwargs):
def mtree_mutate(
name,
mtree,
srcs = None,
preserve_symlinks = False,
strip_prefix = None,
package_dir = None,
mtime = None,
Expand All @@ -148,24 +150,50 @@ def mtree_mutate(
Args:
name: name of the target, output will be `[name].mtree`.
srcs: source files to be used when resolving symlinks. required if `preserve_symlinks` is set to True.
preserve_symlinks: preserve symlinks
mtree: input mtree file, typically created by `mtree_spec`.
strip_prefix: prefix to remove from all paths in the tar. Files and directories not under this prefix are dropped.
package_dir: directory prefix to add to all paths in the tar.
mtime: new modification time for all entries.
owner: new uid for all entries.
ownername: new uname for all entries.
awk_script: may be overridden to change the script containing the modification logic.
**kwargs: additional named parameters to genrule
"""
_mutate_mtree(
vars = []
if strip_prefix:
vars.append("-v strip_prefix='{}'".format(strip_prefix))
if package_dir:
vars.append("-v package_dir='{}'".format(package_dir))
if mtime:
vars.append("-v mtime='{}'".format(mtime))
if owner:
vars.append("-v owner='{}'".format(owner))
if ownername:
vars.append("-v ownername='{}'".format(ownername))
if preserve_symlinks:
vars.append("-v preserve_symlinks=1")
if not srcs:
fail("preserve_symlinks requires srcs to be set in order to resolve symlinks")

# Check if srcs is of type list
if srcs and not types.is_list(srcs):
srcs = [srcs]

native.genrule(
name = name,
mtree = mtree,
strip_prefix = strip_prefix,
package_dir = package_dir,
mtime = str(mtime) if mtime else None,
owner = owner,
ownername = ownername,
awk_script = awk_script,
out = "{}.mtree".format(name),
srcs = [mtree] + (srcs or []),
outs = [name + ".mtree"],
cmd = """
awk {variables} \
-v bin_dir=$(BINDIR) \
-f $(execpath {awk_script}) $(execpath {mtree_spec}) >$@""".format(
variables = " ".join(vars),
awk_script = awk_script,
mtree_spec = mtree,
),
tools = [awk_script],
**kwargs
)
1 change: 1 addition & 0 deletions lib/tests/tar/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ mtree_mutate(
":e17_node_modules",
],
mtree = ":mtree17",
preserve_symlinks = True,
)

assert_tar_listing(
Expand Down
14 changes: 0 additions & 14 deletions tools/mtree/BUILD.bazel

This file was deleted.

Loading

0 comments on commit 0e4d8de

Please sign in to comment.