Skip to content

Commit cc956d8

Browse files
sin-ackalexeagle
andauthored
fix(tar): append slash to top-level directory mtree entries (#852)
* fix(tar): append slash to top-level directory mtree entries bsdtar's mtree format has a quirk wherein entries without "/" in their first word are treated as "relative" entries, and "relative" directories will cause tar to "change directory" into the declared directory entry. If such a directory is followed by a "relative" entry, then the file will be created within the directory, instead of at top-level as expected. To mitigate, we append a slash to top-level directory entries. Fixes #851. * chore: golden files have BINDIR placeholder --------- Co-authored-by: Alex Eagle <alex@aspect.dev>
1 parent 086624a commit cc956d8

File tree

6 files changed

+114
-0
lines changed

6 files changed

+114
-0
lines changed

BUILD.bazel

+38
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
55
load("@bazel_skylib//rules:write_file.bzl", "write_file")
66
load("@buildifier_prebuilt//:rules.bzl", "buildifier")
77
load("//lib:diff_test.bzl", "diff_test")
8+
load("//lib:tar.bzl", "mtree_spec")
89
load("//lib:testing.bzl", "assert_contains")
910
load("//lib:utils.bzl", "is_bazel_7_or_greater")
1011
load("//lib:write_source_files.bzl", "write_source_files")
@@ -136,3 +137,40 @@ bzl_library(
136137
visibility = ["//visibility:public"],
137138
deps = ["@bazel_gazelle//:deps"],
138139
)
140+
141+
# Test case for mtree_spec: Ensure that multiple entries at the root directory are handled correctly (bug #851)
142+
# See lib/tests/tar/BUILD.bazel for why this is here.
143+
write_file(
144+
name = "tar_test13_main",
145+
out = "13project/__main__.py",
146+
content = ["__main__.py"],
147+
)
148+
149+
write_file(
150+
name = "tar_test13_bin",
151+
out = "13project_bin",
152+
content = ["project_bin"],
153+
)
154+
155+
mtree_spec(
156+
name = "tar_test13_mtree_unsorted",
157+
srcs = [
158+
":tar_test13_bin",
159+
":tar_test13_main",
160+
],
161+
)
162+
163+
# NOTE: On some systems, the mtree_spec output can have a different order.
164+
# To make the test less brittle, we sort the mtree output and replace the BINDIR with a constant placeholder
165+
genrule(
166+
name = "tar_test13_mtree",
167+
srcs = [":tar_test13_mtree_unsorted"],
168+
outs = ["actual13.mtree"],
169+
cmd = "sort $< | sed 's#$(BINDIR)#{BINDIR}#' >$@",
170+
)
171+
172+
diff_test(
173+
name = "tar_test13",
174+
file1 = "tar_test13_mtree",
175+
file2 = "//lib/tests/tar:expected13.mtree",
176+
)

lib/private/modify_mtree.awk

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@
77
} else if (index($1, strip_prefix) == 1) {
88
# this line starts with the strip_prefix
99
sub("^" strip_prefix "/", "");
10+
11+
# NOTE: The mtree format treats file paths without slashes as "relative" entries.
12+
# If a relative entry is a directory, then it will "change directory" to that
13+
# directory, and any subsequent "relative" entries will be created inside that
14+
# directory. This causes issues when there is a top-level directory that is
15+
# followed by a top-level file, as the file will be created inside the directory.
16+
# To avoid this, we append a slash to the directory path to make it a "full" entry.
17+
components = split($1, _, "/");
18+
if ($0 ~ /type=dir/ && components == 1) {
19+
$1 = $1 "/";
20+
}
1021
} else {
1122
# this line declares some path under a parent directory, which will be discarded
1223
next;

lib/private/tar.bzl

+10
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ def _expand(file, expander, transform = to_repository_relative_path):
195195
segments = path.split("/")
196196
for i in range(1, len(segments)):
197197
parent = "/".join(segments[:i])
198+
199+
# NOTE: The mtree format treats file paths without slashes as "relative" entries.
200+
# If a relative entry is a directory, then it will "change directory" to that
201+
# directory, and any subsequent "relative" entries will be created inside that
202+
# directory. This causes issues when there is a top-level directory that is
203+
# followed by a top-level file, as the file will be created inside the directory.
204+
# To avoid this, we append a slash to the directory path to make it a "full" entry.
205+
if i == 1:
206+
parent += "/"
207+
198208
lines.append(_mtree_line(parent, "dir"))
199209

200210
lines.append(_mtree_line(_vis_encode(path), "file", content = _vis_encode(e.path)))

lib/tests/tar/BUILD.bazel

+49
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,52 @@ diff_test(
359359
file1 = "modified2.mtree",
360360
file2 = "expected2.mtree",
361361
)
362+
363+
# Case 13: Ensure that multiple entries at the root directory are handled correctly (bug #851)
364+
# NOTE: The mtree_spec part of this test is placed at the root BUILD.bazel because
365+
# that's the only way to ensure that the mtree_spec generates single-component
366+
# entries (which would trigger the bug).
367+
exports_files(["expected13.mtree"])
368+
369+
# Case 14: Ensure mtree_mutate correctly handles prefix stripping for top-level directories (bug #851)
370+
371+
write_file(
372+
name = "test14_main",
373+
out = "14project/__main__.py",
374+
content = ["__main__.py"],
375+
)
376+
377+
write_file(
378+
name = "test14_bin",
379+
out = "14project_bin",
380+
content = ["project_bin"],
381+
)
382+
383+
mtree_spec(
384+
name = "mtree14",
385+
srcs = [
386+
":test14_bin",
387+
":test14_main",
388+
],
389+
)
390+
391+
mtree_mutate(
392+
name = "strip_prefix14_unsorted",
393+
mtree = "mtree14",
394+
strip_prefix = "lib/tests/tar",
395+
)
396+
397+
# NOTE: On some systems, the mtree_spec output can have a different order.
398+
# To make the test less brittle, we sort the mtree output and replace the BINDIR with a constant placeholder
399+
genrule(
400+
name = "strip_prefix14",
401+
srcs = [":strip_prefix14_unsorted"],
402+
outs = ["actual14.mtree"],
403+
cmd = "sort $< | sed 's#$(BINDIR)#{BINDIR}#' >$@",
404+
)
405+
406+
diff_test(
407+
name = "test14",
408+
file1 = ":strip_prefix14",
409+
file2 = "expected14.mtree",
410+
)

lib/tests/tar/expected13.mtree

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
13project/ uid=0 gid=0 time=1672560000 mode=0755 type=dir
2+
13project/__main__.py uid=0 gid=0 time=1672560000 mode=0755 type=file content={BINDIR}/13project/__main__.py
3+
13project_bin uid=0 gid=0 time=1672560000 mode=0755 type=file content={BINDIR}/13project_bin

lib/tests/tar/expected14.mtree

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
14project/ uid=0 gid=0 time=1672560000 mode=0755 type=dir
2+
14project/__main__.py uid=0 gid=0 time=1672560000 mode=0755 type=file content={BINDIR}/lib/tests/tar/14project/__main__.py
3+
14project_bin uid=0 gid=0 time=1672560000 mode=0755 type=file content={BINDIR}/lib/tests/tar/14project_bin

0 commit comments

Comments
 (0)