Skip to content

Commit 006af3e

Browse files
authored
When pkg_tar.prefix_dir == base of symlink path, don't double-dip. (#749)
* Correct for case where tar has prefix_dir and files/symlinks, but the user has already mapped the prefix_dir into those paths. Under the old behavior, prefix_dir was (incorrectly) not added to symlinks, so some users made the desired prefix part of the link. Protecting them against the double inclusion of the prefix is probably the least surprising behavior. Add tests for this, which required improving verify_archive_test. * make buildifier happy * do not run the symlink test on windows * huh? why are symlinks in a tree failing on CI?
1 parent 320107a commit 006af3e

File tree

7 files changed

+197
-80
lines changed

7 files changed

+197
-80
lines changed

.bazelci/tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ default_tests_with_rpm: &default_tests_with_rpm
3333
- "//toolchains/..."
3434

3535
win_tests: &win_tests
36+
test_flags:
37+
- "--test_tag_filters=-nowindows"
38+
build_flags:
39+
- "--build_tag_filters=-nowindows"
3640
test_targets:
3741
- "//pkg/..."
3842
- "//tests/..."

docs/latest.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ Rules for making .tar files.
259259
## pkg_tar
260260

261261
<pre>
262-
pkg_tar(<a href="#pkg_tar-name">name</a>, <a href="#pkg_tar-build_tar">build_tar</a>, <a href="#pkg_tar-compressor">compressor</a>, <a href="#pkg_tar-compressor_args">compressor_args</a>, <a href="#pkg_tar-deps">deps</a>, <a href="#pkg_tar-empty_dirs">empty_dirs</a>, <a href="#pkg_tar-empty_files">empty_files</a>, <a href="#pkg_tar-extension">extension</a>,
263-
<a href="#pkg_tar-files">files</a>, <a href="#pkg_tar-include_runfiles">include_runfiles</a>, <a href="#pkg_tar-mode">mode</a>, <a href="#pkg_tar-modes">modes</a>, <a href="#pkg_tar-mtime">mtime</a>, <a href="#pkg_tar-out">out</a>, <a href="#pkg_tar-owner">owner</a>, <a href="#pkg_tar-ownername">ownername</a>, <a href="#pkg_tar-ownernames">ownernames</a>, <a href="#pkg_tar-owners">owners</a>,
262+
pkg_tar(<a href="#pkg_tar-name">name</a>, <a href="#pkg_tar-compressor">compressor</a>, <a href="#pkg_tar-compressor_args">compressor_args</a>, <a href="#pkg_tar-deps">deps</a>, <a href="#pkg_tar-empty_dirs">empty_dirs</a>, <a href="#pkg_tar-empty_files">empty_files</a>, <a href="#pkg_tar-extension">extension</a>, <a href="#pkg_tar-files">files</a>,
263+
<a href="#pkg_tar-include_runfiles">include_runfiles</a>, <a href="#pkg_tar-mode">mode</a>, <a href="#pkg_tar-modes">modes</a>, <a href="#pkg_tar-mtime">mtime</a>, <a href="#pkg_tar-out">out</a>, <a href="#pkg_tar-owner">owner</a>, <a href="#pkg_tar-ownername">ownername</a>, <a href="#pkg_tar-ownernames">ownernames</a>, <a href="#pkg_tar-owners">owners</a>,
264264
<a href="#pkg_tar-package_dir">package_dir</a>, <a href="#pkg_tar-package_dir_file">package_dir_file</a>, <a href="#pkg_tar-package_file_name">package_file_name</a>, <a href="#pkg_tar-package_variables">package_variables</a>, <a href="#pkg_tar-portable_mtime">portable_mtime</a>,
265265
<a href="#pkg_tar-private_stamp_detect">private_stamp_detect</a>, <a href="#pkg_tar-remap_paths">remap_paths</a>, <a href="#pkg_tar-srcs">srcs</a>, <a href="#pkg_tar-stamp">stamp</a>, <a href="#pkg_tar-strip_prefix">strip_prefix</a>, <a href="#pkg_tar-symlinks">symlinks</a>)
266266
</pre>
@@ -273,31 +273,30 @@ pkg_tar(<a href="#pkg_tar-name">name</a>, <a href="#pkg_tar-build_tar">build_tar
273273
| Name | Description | Type | Mandatory | Default |
274274
| :------------- | :------------- | :------------- | :------------- | :------------- |
275275
| <a id="pkg_tar-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
276-
| <a id="pkg_tar-build_tar"></a>build_tar | - | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | //pkg/private/tar:build_tar |
277-
| <a id="pkg_tar-compressor"></a>compressor | - | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
278-
| <a id="pkg_tar-compressor_args"></a>compressor_args | - | String | optional | "" |
279-
| <a id="pkg_tar-deps"></a>deps | - | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
276+
| <a id="pkg_tar-compressor"></a>compressor | External tool which can compress the archive. | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
277+
| <a id="pkg_tar-compressor_args"></a>compressor_args | Arg list for <code>compressor</code>. | String | optional | "" |
278+
| <a id="pkg_tar-deps"></a>deps | tar files which will be unpacked and repacked into the archive. | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
280279
| <a id="pkg_tar-empty_dirs"></a>empty_dirs | - | List of strings | optional | [] |
281280
| <a id="pkg_tar-empty_files"></a>empty_files | - | List of strings | optional | [] |
282281
| <a id="pkg_tar-extension"></a>extension | - | String | optional | "tar" |
283-
| <a id="pkg_tar-files"></a>files | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: Label -> String</a> | optional | {} |
282+
| <a id="pkg_tar-files"></a>files | Obsolete. Do not use. | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: Label -> String</a> | optional | {} |
284283
| <a id="pkg_tar-include_runfiles"></a>include_runfiles | - | Boolean | optional | False |
285284
| <a id="pkg_tar-mode"></a>mode | - | String | optional | "0555" |
286285
| <a id="pkg_tar-modes"></a>modes | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
287286
| <a id="pkg_tar-mtime"></a>mtime | - | Integer | optional | -1 |
288287
| <a id="pkg_tar-out"></a>out | - | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | required | |
289-
| <a id="pkg_tar-owner"></a>owner | - | String | optional | "0.0" |
288+
| <a id="pkg_tar-owner"></a>owner | Default numeric owner.group to apply to files when not set via pkg_attribures. | String | optional | "0.0" |
290289
| <a id="pkg_tar-ownername"></a>ownername | - | String | optional | "." |
291290
| <a id="pkg_tar-ownernames"></a>ownernames | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
292291
| <a id="pkg_tar-owners"></a>owners | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
293-
| <a id="pkg_tar-package_dir"></a>package_dir | Prefix to be prepend to all paths written. The name may contain variables, same as [package_file_name](#package_file_name) | String | optional | "" |
292+
| <a id="pkg_tar-package_dir"></a>package_dir | Prefix to be prepend to all paths written.<br><br> This is applied as a final step, while writing to the archive. Any other attributes (e.g. symlinks) which specify a path, must do so relative to package_dir. The value may contain variables. See [package_file_name](#package_file_name) for examples. | String | optional | "" |
294293
| <a id="pkg_tar-package_dir_file"></a>package_dir_file | - | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
295294
| <a id="pkg_tar-package_file_name"></a>package_file_name | See [Common Attributes](#package_file_name) | String | optional | "" |
296295
| <a id="pkg_tar-package_variables"></a>package_variables | See [Common Attributes](#package_variables) | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
297296
| <a id="pkg_tar-portable_mtime"></a>portable_mtime | - | Boolean | optional | True |
298297
| <a id="pkg_tar-private_stamp_detect"></a>private_stamp_detect | - | Boolean | optional | False |
299298
| <a id="pkg_tar-remap_paths"></a>remap_paths | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
300-
| <a id="pkg_tar-srcs"></a>srcs | - | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
299+
| <a id="pkg_tar-srcs"></a>srcs | Inputs which will become part of the tar archive. | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
301300
| <a id="pkg_tar-stamp"></a>stamp | Enable file time stamping. Possible values: <li>stamp = 1: Use the time of the build as the modification time of each file in the archive. <li>stamp = 0: Use an "epoch" time for the modification time of each file. This gives good build result caching. <li>stamp = -1: Control the chosen modification time using the --[no]stamp flag. <div class="since"><i>Since 0.5.0</i></div> | Integer | optional | 0 |
302301
| <a id="pkg_tar-strip_prefix"></a>strip_prefix | (note: Use strip_prefix = "." to strip path to the package but preserve relative paths of sub directories beneath the package.) | String | optional | "" |
303302
| <a id="pkg_tar-symlinks"></a>symlinks | - | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
@@ -583,6 +582,9 @@ pkg_mklink(<a href="#pkg_mklink-name">name</a>, <a href="#pkg_mklink-link_name">
583582

584583
Create a symlink.
585584

585+
Wraps [pkg_mklink_impl](#pkg_mklink_impl)
586+
587+
586588
**PARAMETERS**
587589

588590

pkg/private/tar/build_tar.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@ def normalize_path(self, path: str) -> str:
7070
# No path should ever come in with slashs on either end, but protect
7171
# against that anyway.
7272
dest = dest.strip('/')
73-
if self.directory:
73+
# This prevents a potential problem for users with both a prefix_dir and
74+
# symlinks that also repeat the prefix_dir. The old behavior was that we
75+
# would get just the symlink path. Now we are prefixing with the prefix,
76+
# so you get the file in the wrong place.
77+
# We silently de-dup that. If people come up with a real use case for
78+
# the /a/b/a/b/rest... output we can start an issue and come up with a
79+
# solution at that time.
80+
if self.directory and not dest.startswith(self.directory):
7481
dest = self.directory + dest
7582
return dest
7683

pkg/private/tar/tar.bzl

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def _pkg_tar_impl(ctx):
244244
# or this OutputGroup might be totally removed.
245245
# Depend on it at your own risk!
246246
OutputGroupInfo(
247-
manifest = [manifest_file],
247+
manifest = [manifest_file],
248248
),
249249
]
250250

@@ -253,21 +253,37 @@ pkg_tar_impl = rule(
253253
implementation = _pkg_tar_impl,
254254
attrs = {
255255
"strip_prefix": attr.string(
256-
doc = """(note: Use strip_prefix = "." to strip path to the package but preserve relative paths of sub directories beneath the package.)"""
256+
doc = """(note: Use strip_prefix = "." to strip path to the package but preserve relative paths of sub directories beneath the package.)""",
257257
),
258258
"package_dir": attr.string(
259259
doc = """Prefix to be prepend to all paths written.
260-
The name may contain variables, same as [package_file_name](#package_file_name)""",
260+
261+
This is applied as a final step, while writing to the archive.
262+
Any other attributes (e.g. symlinks) which specify a path, must do so relative to package_dir.
263+
The value may contain variables. See [package_file_name](#package_file_name) for examples.
264+
""",
261265
),
262266
"package_dir_file": attr.label(allow_single_file = True),
263-
"deps": attr.label_list(allow_files = tar_filetype),
264-
"srcs": attr.label_list(allow_files = True),
265-
"files": attr.label_keyed_string_dict(allow_files = True),
267+
"deps": attr.label_list(
268+
doc = """tar files which will be unpacked and repacked into the archive.""",
269+
allow_files = tar_filetype,
270+
),
271+
"srcs": attr.label_list(
272+
doc = """Inputs which will become part of the tar archive.""",
273+
allow_files = True,
274+
),
275+
"files": attr.label_keyed_string_dict(
276+
doc = """Obsolete. Do not use.""",
277+
allow_files = True,
278+
),
266279
"mode": attr.string(default = "0555"),
267280
"modes": attr.string_dict(),
268281
"mtime": attr.int(default = _DEFAULT_MTIME),
269282
"portable_mtime": attr.bool(default = True),
270-
"owner": attr.string(default = "0.0"),
283+
"owner": attr.string(
284+
doc = """Default numeric owner.group to apply to files when not set via pkg_attribures.""",
285+
default = "0.0",
286+
),
271287
"ownername": attr.string(default = "."),
272288
"owners": attr.string_dict(),
273289
"ownernames": attr.string_dict(),
@@ -277,8 +293,14 @@ The name may contain variables, same as [package_file_name](#package_file_name)"
277293
"include_runfiles": attr.bool(),
278294
"empty_dirs": attr.string_list(),
279295
"remap_paths": attr.string_dict(),
280-
"compressor": attr.label(executable = True, cfg = "exec"),
281-
"compressor_args": attr.string(),
296+
"compressor": attr.label(
297+
doc = """External tool which can compress the archive.""",
298+
executable = True,
299+
cfg = "exec",
300+
),
301+
"compressor_args": attr.string(
302+
doc = """Arg list for `compressor`.""",
303+
),
282304

283305
# Common attributes
284306
"out": attr.output(mandatory = True),
@@ -323,6 +345,7 @@ def pkg_tar(name, **kwargs):
323345
324346
@wraps(pkg_tar_impl)
325347
"""
348+
326349
# Compatibility with older versions of pkg_tar that define files as
327350
# a flat list of labels.
328351
if "srcs" not in kwargs:

pkg/verify_archive.bzl

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ The execution time is O(# expected patterns * size of archive).
2323

2424
load("@rules_python//python:defs.bzl", "py_test")
2525

26-
2726
def _gen_verify_archive_test_main_impl(ctx):
2827
ctx.actions.expand_template(
2928
template = ctx.file._template,
@@ -38,6 +37,7 @@ def _gen_verify_archive_test_main_impl(ctx):
3837
"${MUST_NOT_CONTAIN_REGEX}": str(ctx.attr.must_not_contain_regex),
3938
"${MIN_SIZE}": str(ctx.attr.min_size),
4039
"${MAX_SIZE}": str(ctx.attr.max_size),
40+
"${VERIFY_LINKS}": str(ctx.attr.verify_links),
4141
},
4242
)
4343
return [
@@ -55,7 +55,6 @@ _gen_verify_archive_test_main = rule(
5555
allow_single_file = True,
5656
mandatory = True,
5757
),
58-
5958
"must_contain": attr.string_list(
6059
doc = "List of paths which all must appear in the archive.",
6160
),
@@ -69,10 +68,13 @@ _gen_verify_archive_test_main = rule(
6968
doc = """List of regexes that must not be in the archive.""",
7069
),
7170
"min_size": attr.int(
72-
doc = """Minimum number of entries in the archive."""
71+
doc = """Minimum number of entries in the archive.""",
7372
),
7473
"max_size": attr.int(
75-
doc = """Maximum number of entries in the archive."""
74+
doc = """Maximum number of entries in the archive.""",
75+
),
76+
"verify_links": attr.string_dict(
77+
doc = """Dict keyed by paths which must appear, and be symlinks to their values.""",
7678
),
7779

7880
# Implicit dependencies.
@@ -83,10 +85,17 @@ _gen_verify_archive_test_main = rule(
8385
},
8486
)
8587

86-
def verify_archive_test(name, target,
87-
must_contain=None, must_contain_regex=None,
88-
must_not_contain=None, must_not_contain_regex=None,
89-
min_size=1, max_size=-1):
88+
def verify_archive_test(
89+
name,
90+
target,
91+
must_contain = None,
92+
must_contain_regex = None,
93+
must_not_contain = None,
94+
must_not_contain_regex = None,
95+
min_size = 1,
96+
max_size = -1,
97+
tags = None,
98+
verify_links = None):
9099
"""Tests that an archive contains specific file patterns.
91100
92101
This test is used to verify that an archive contains the expected content.
@@ -99,19 +108,23 @@ def verify_archive_test(name, target,
99108
must_not_contain_regex: A list of path regexes which must not appear in the archive.
100109
min_size: The minimum number of entries which must be in the archive.
101110
max_size: The maximum number of entries which must be in the archive.
111+
tags: standard meaning
112+
verify_links: Dict keyed by paths which must appear, and be symlinks to their values.
102113
"""
103-
test_src = name + "__internal_main.py"
114+
test_src = name + "__internal_main.py"
104115
_gen_verify_archive_test_main(
105116
name = name + "_internal_main",
106117
target = target,
107-
test_name = name.replace('-', '_') + "Test",
118+
test_name = name.replace("-", "_") + "Test",
108119
out = test_src,
109120
must_contain = must_contain,
110121
must_contain_regex = must_contain_regex,
111122
must_not_contain = must_not_contain,
112123
must_not_contain_regex = must_not_contain_regex,
113124
min_size = min_size,
114125
max_size = max_size,
126+
tags = tags,
127+
verify_links = verify_links,
115128
)
116129
py_test(
117130
name = name,

pkg/verify_archive_test_main.py.tpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ class VerifyArchiveTest(unittest.TestCase):
3636

3737
def load_tar(self, path):
3838
self.paths = []
39+
self.links = {}
3940
with tarfile.open(path, 'r:*') as f:
4041
i = 0
4142
for info in f:
4243
self.paths.append(info.name)
44+
if info.linkname:
45+
self.links[info.name] = info.linkname
4346

4447
def assertMinSize(self, min_size):
4548
"""Check that the archive contains at least min_size entries.
@@ -60,6 +63,8 @@ class VerifyArchiveTest(unittest.TestCase):
6063
Args:
6164
max_size: The maximum number of targets we expect.
6265
"""
66+
if max_size < 0:
67+
return
6368
actual_size = len(self.paths)
6469
self.assertLessEqual(
6570
len(self.paths),
@@ -100,6 +105,14 @@ class VerifyArchiveTest(unittest.TestCase):
100105
if r_comp.match(path):
101106
self.fail('Found disallowed pattern (%s) in the archive' % pattern)
102107

108+
def verify_links(self, verify_links):
109+
for link, target in verify_links.items():
110+
if link not in self.paths:
111+
self.fail('Required link (%s) is not in the archive' % link)
112+
if self.links[link] != target:
113+
self.fail('link (%s) points to the wrong place. Expected (%s) got (%s)' %
114+
(link, target, self.links[link]))
115+
103116

104117
class ${TEST_NAME}(VerifyArchiveTest):
105118

@@ -125,6 +138,9 @@ class ${TEST_NAME}(VerifyArchiveTest):
125138
def test_must_not_contain(self):
126139
self.check_must_not_contain_regex(${MUST_NOT_CONTAIN_REGEX})
127140

141+
def test_verify_links(self):
142+
self.verify_links(${VERIFY_LINKS})
143+
128144

129145
if __name__ == '__main__':
130146
unittest.main()

0 commit comments

Comments
 (0)