Skip to content

Commit 77a89a6

Browse files
Ahajharickeylev
andauthored
feat: Allow files in wheels to be installed to directories (#3233)
When specifying `data_files` in `py_wheel`, allow just the directory to be specified (with a trailing slash), in which case it will use the existing filename. This avoids duplicating (potentially platform-specific) names. Additionally, targets with multiple files can be installed as a group to a folder, with the same filename-preserving behavior. In general I think this is a better starting point, as I imagine most of the time users would want to preserve the names. Before, this would result in the file simply not being installed, so this only changes already-broken behavior. --------- Co-authored-by: Richard Levasseur <rlevasseur@google.com>
1 parent 2d7ff9d commit 77a89a6

4 files changed

Lines changed: 85 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Other changes:
8787
{obj}`experimental_index_url` which should speed up consecutive initializations and should no
8888
longer require the network access if the cache is hydrated.
8989
Implements [#2731](https://github.com/bazel-contrib/rules_python/issues/2731).
90+
* (wheel) Specifying a path ending in `/` as a destination in `data_files`
91+
will now install file(s) to a folder, preserving their basename.
9092

9193
{#v1-9-0}
9294
## [1.9.0] - 2026-02-21

examples/wheel/BUILD.bazel

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ py_wheel(
401401
version = "0.0.1",
402402
)
403403

404+
filegroup(
405+
name = "data_files_test_group",
406+
# Re-using some files already checked into the repo.
407+
srcs = [
408+
"README.md",
409+
"//examples/wheel:NOTICE",
410+
],
411+
)
412+
413+
py_wheel(
414+
name = "data_files_installed_in_folder",
415+
testonly = True, # Set this to verify the generated .dist target doesn't break things
416+
# Re-using some files already checked into the repo.
417+
data_files = {
418+
# Single file
419+
"//examples/wheel:NOTICE": "scripts/",
420+
# Filegroup
421+
":data_files_test_group": "data/",
422+
},
423+
distribution = "data_files_installed_in_folder",
424+
version = "0.0.1",
425+
)
426+
404427
py_test(
405428
name = "wheel_test",
406429
srcs = ["wheel_test.py"],
@@ -409,6 +432,7 @@ py_test(
409432
":custom_package_root_multi_prefix",
410433
":custom_package_root_multi_prefix_reverse_order",
411434
":customized",
435+
":data_files_installed_in_folder",
412436
":empty_requires_files",
413437
":extra_requires",
414438
":filename_escaping",

examples/wheel/wheel_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,25 @@ def test_requires_dist_depends_on_extras_file(self):
615615
requires,
616616
)
617617

618+
def test_data_files_installed_in_folder(self):
619+
filename = self._get_path(
620+
"data_files_installed_in_folder-0.0.1-py3-none-any.whl"
621+
)
622+
623+
with zipfile.ZipFile(filename) as zf:
624+
self.assertAllEntriesHasReproducibleMetadata(zf)
625+
self.assertEqual(
626+
zf.namelist(),
627+
[
628+
"data_files_installed_in_folder-0.0.1.dist-info/WHEEL",
629+
"data_files_installed_in_folder-0.0.1.dist-info/METADATA",
630+
"data_files_installed_in_folder-0.0.1.data/data/NOTICE",
631+
"data_files_installed_in_folder-0.0.1.data/data/README.md",
632+
"data_files_installed_in_folder-0.0.1.data/scripts/NOTICE",
633+
"data_files_installed_in_folder-0.0.1.dist-info/RECORD",
634+
],
635+
)
636+
618637

619638
if __name__ == "__main__":
620639
unittest.main()

python/private/py_wheel.bzl

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,35 @@ _other_attrs = {
182182
doc = "A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers",
183183
),
184184
"data_files": attr.label_keyed_string_dict(
185-
doc = ("Any file that is not normally installed inside site-packages goes into the .data directory, named " +
186-
"as the .dist-info directory but with the .data/ extension. Allowed paths: {prefixes}".format(prefixes = ALLOWED_DATA_FILE_PREFIX)),
185+
doc = ("""
186+
Mapping of data files to go into the wheel.
187+
188+
The keys are targets of files to include, and the values are the `.data`-relative
189+
path to use.
190+
191+
Any file that is not normally installed inside site-packages goes into the .data
192+
directory, named as the .dist-info directory but with the .data/ extension. If
193+
the destination of a file or group of files ends in a `/`, the destination is a
194+
folder and files are placed with their existing basenames under that folder.
195+
196+
For example:
197+
198+
```
199+
":file1.txt": "data/file1.txt", # Destination: <wheelname>.data/data/file1.txt
200+
":file1.txt": "data/", # Destination: <wheelname>.data/data/file1.txt
201+
":file1.txt": "data/special.txt", # Destination: <wheelname>.data/data/special.txt
202+
203+
filegroup(name = "files", srcs = [":file1.txt", ":file2.txt"])
204+
":files": "data/", # Destinations: <wheelname>.data/data/file1.txt, <wheelname>.data/data/file2.txt
205+
```
206+
207+
Allowed paths: {prefixes}
208+
209+
:::{{versionchanged}} VERSION_NEXT_FEATURE
210+
Values can end in slash (`/`) to indicate that all files of the target should
211+
be moved under that directory.
212+
:::
213+
""".format(prefixes = ALLOWED_DATA_FILE_PREFIX)),
187214
allow_files = True,
188215
),
189216
"description_content_type": attr.string(
@@ -506,9 +533,9 @@ def _py_wheel_impl(ctx):
506533

507534
for target, filename in ctx.attr.data_files.items():
508535
target_files = target[DefaultInfo].files.to_list()
509-
if len(target_files) != 1:
536+
if len(target_files) != 1 and not filename.endswith("/"):
510537
fail(
511-
"Multi-file target listed in data_files %s",
538+
"Multi-file target listed in data_files %s, this is only supported when specifying a folder path (i.e. a path ending in '/')",
512539
filename,
513540
)
514541

@@ -520,11 +547,15 @@ def _py_wheel_impl(ctx):
520547
filename,
521548
),
522549
)
523-
other_inputs.extend(target_files)
524-
args.add(
525-
"--data_files",
526-
filename + ";" + target_files[0].path,
527-
)
550+
551+
for file in target_files:
552+
final_filename = filename + file.basename if filename.endswith("/") else filename
553+
554+
other_inputs.extend(target_files)
555+
args.add(
556+
"--data_files",
557+
final_filename + ";" + file.path,
558+
)
528559

529560
ctx.actions.run(
530561
mnemonic = "PyWheel",

0 commit comments

Comments
 (0)