Skip to content

wip preserve symlinks #967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/private/modify_mtree.awk
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Edits mtree files. See the modify_mtree macro in /lib/tar.bzl.
{
if (preserve_symlink != "") {
# 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
if ($0 ~ /type=file/) {

}
}
if (strip_prefix != "") {
if ($1 == strip_prefix) {
# this line declares the directory which is now the root. It may be discarded.
Expand Down
9 changes: 9 additions & 0 deletions lib/tar.bzl
Original file line number Diff line number Diff line change
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,13 +150,16 @@ 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
"""
vars = []
Expand All @@ -168,6 +173,10 @@ def mtree_mutate(
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")

native.genrule(
name = name,
Expand Down
20 changes: 20 additions & 0 deletions lib/tests/tar/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("@aspect_bazel_lib//lib:tar.bzl", "mtree_mutate", "mtree_spec", "tar")
load("@aspect_bazel_lib//lib:testing.bzl", "assert_archive_contains")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load(":asserts.bzl", "assert_tar_listing", "assert_unused_listing")
load(":node_modules_tree.bzl", "node_modules_tree")

# The examples below work with both source files and generated files.
# Here we generate a file to use in the examples.
Expand Down Expand Up @@ -465,3 +466,22 @@ assert_unused_listing(
"lib/tests/tar/unused/space in name.txt",
],
)

#############
# Example 16: mtree_mutate preserves symlinks
node_modules_tree(
name = "e16_node_modules",
)

mtree_spec(
name = "mtree16",
srcs = [
":e16_node_modules",
],
)

assert_tar_listing(
name = "test_16_before_processing",
actual = ":mtree16",
expected = [],
)
51 changes: 51 additions & 0 deletions lib/tests/tar/node_modules_tree.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# https://github.com/bazelbuild/rules_pkg/pull/609
def impl(ctx):
# packages
# - a
# - b depends on a
store_a = ctx.actions.declare_directory("node_modules/.pnpm/[email protected]/node_modules/a")
store_b = ctx.actions.declare_directory("node_modules/.pnpm/[email protected]/node_modules/b")

ctx.actions.run_shell(
outputs = [store_a, store_b],
command = "echo 'test' > %s/package.json" % store_a.path,
)

dep_symlink_b_to_a = ctx.actions.declare_directory("node_modules/.pnpm/[email protected]/node_modules/a")

ctx.actions.symlink(
output = dep_symlink_b_to_a,
target_file = store_a,
)

node_modules_a = ctx.actions.declare_directory("node_modules/a")
ctx.actions.symlink(
output = node_modules_a,
target_file = store_a,
)

# single file
a = ctx.actions.declare_file("dir/a")
ctx.actions.run_shell(
outputs = [a],
command = "echo 'test' > %s" % a.path,
)

b = ctx.actions.declare_file("dir/b")
ctx.actions.symlink(
output = b,
target_file = a,
)

return DefaultInfo(files = depset([
store_a,
store_b,
dep_symlink_b_to_a,
node_modules_a,
a,
b,
]))

node_modules_tree = rule(
implementation = impl,
)
Loading