diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e29f84d85..fe7a4b4b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ A brief description of the categories of changes: * New Python versions available: `3.11.8`, `3.12.2` using https://github.com/indygreg/python-build-standalone/releases/tag/20240224. +* (wheel) Add support for `data_files` attributes in py_wheel rule + ([#1777](https://github.com/bazelbuild/rules_python/issues/1777)) + [0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0 ## [0.31.0] - 2024-02-12 diff --git a/examples/wheel/BUILD.bazel b/examples/wheel/BUILD.bazel index 699bf6829e..2e45d7dd4c 100644 --- a/examples/wheel/BUILD.bazel +++ b/examples/wheel/BUILD.bazel @@ -312,6 +312,20 @@ py_wheel( deps = [":example_pkg"], ) +# Package just a specific py_libraries, without their dependencies +py_wheel( + name = "minimal_data_files", + testonly = True, # Set this to verify the generated .dist target doesn't break things + + # Re-using some files already checked into the repo. + data_files = { + "//examples/wheel:NOTICE": "scripts/NOTICE", + "README.md": "data/target/path/README.md", + }, + distribution = "minimal_data_files", + version = "0.0.1", +) + py_test( name = "wheel_test", srcs = ["wheel_test.py"], @@ -321,6 +335,7 @@ py_test( ":custom_package_root_multi_prefix_reverse_order", ":customized", ":filename_escaping", + ":minimal_data_files", ":minimal_with_py_library", ":minimal_with_py_library_with_stamp", ":minimal_with_py_package", diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl index bfd421867e..62f33e0ce9 100644 --- a/python/private/py_wheel.bzl +++ b/python/private/py_wheel.bzl @@ -120,6 +120,7 @@ See [`py_wheel_dist`](#py_wheel_dist) for more info. _feature_flags = {} +ALLOWED_DATA_FILE_PREFIX = ("purelib", "platlib", "headers", "scripts", "data") _requirement_attrs = { "extra_requires": attr.string_list_dict( doc = ("A mapping of [extras](https://peps.python.org/pep-0508/#extras) options to lists of requirements (similar to `requires`). This attribute " + @@ -174,7 +175,7 @@ _other_attrs = { ), "data_files": attr.label_keyed_string_dict( doc = ("Any file that is not normally installed inside site-packages goes into the .data directory, named " + - "as the .dist-info directory but with the .data/ extension."), + "as the .dist-info directory but with the .data/ extension. Allowed paths: {prefixes}".format(prefixes = ALLOWED_DATA_FILE_PREFIX)), allow_files = True, ), "description_content_type": attr.string( @@ -485,6 +486,15 @@ def _py_wheel_impl(ctx): "Multi-file target listed in data_files %s", filename, ) + + if not filename.startswith(ALLOWED_DATA_FILE_PREFIX): + fail( + "The target data file must start with one of these prefixes: '%s'. Target filepath: '%s'" % + ( + ",".join(ALLOWED_DATA_FILE_PREFIX), + filename, + ), + ) other_inputs.extend(target_files) args.add( "--data_files", diff --git a/python/private/repack_whl.py b/python/private/repack_whl.py index be113ef791..ea9c01f76f 100644 --- a/python/private/repack_whl.py +++ b/python/private/repack_whl.py @@ -150,8 +150,9 @@ def main(sys_argv): logging.debug(f"Found dist-info dir: {distinfo_dir}") record_path = distinfo_dir / "RECORD" record_contents = record_path.read_text() if record_path.exists() else "" + distribution_prefix = distinfo_dir.with_suffix("").name - with _WhlFile(args.output, mode="w", distinfo_dir=distinfo_dir) as out: + with _WhlFile(args.output, mode="w", distribution_prefix=distribution_prefix) as out: for p in _files_to_pack(patched_wheel_dir, record_contents): rel_path = p.relative_to(patched_wheel_dir) out.add_file(str(rel_path), p) diff --git a/tests/py_wheel/py_wheel_tests.bzl b/tests/py_wheel/py_wheel_tests.bzl index 3c03a1b8e4..55cfd3720f 100644 --- a/tests/py_wheel/py_wheel_tests.bzl +++ b/tests/py_wheel/py_wheel_tests.bzl @@ -14,6 +14,7 @@ """Test for py_wheel.""" load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") load("@rules_testing//lib:util.bzl", rt_util = "util") load("//python:packaging.bzl", "py_wheel") load("//python/private:py_wheel_normalize_pep440.bzl", "normalize_pep440") # buildifier: disable=bzl-visibility @@ -46,6 +47,55 @@ def _test_metadata_impl(env, target): _tests.append(_test_metadata) +def _test_data(name): + rt_util.helper_target( + py_wheel, + name = name + "_data", + distribution = "mydist_" + name, + version = "0.0.0", + data_files = { + "source_name": "scripts/wheel_name", + }, + ) + analysis_test( + name = name, + impl = _test_data_impl, + target = name + "_data", + ) + +def _test_data_impl(env, target): + action = env.expect.that_target(target).action_named( + "PyWheel", + ) + action.contains_at_least_args(["--data_files", "scripts/wheel_name;tests/py_wheel/source_name"]) + action.contains_at_least_inputs(["tests/py_wheel/source_name"]) + +_tests.append(_test_data) + +def _test_data_bad_path(name): + rt_util.helper_target( + py_wheel, + name = name + "_data", + distribution = "mydist_" + name, + version = "0.0.0", + data_files = { + "source_name": "unsupported_path/wheel_name", + }, + ) + analysis_test( + name = name, + impl = _test_data_bad_path_impl, + target = name + "_data", + expect_failure = True, + ) + +def _test_data_bad_path_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.str_matches("target data file must start with"), + ) + +_tests.append(_test_data_bad_path) + def _test_content_type_from_attr(name): rt_util.helper_target( py_wheel, diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py index 93f9cb5514..8fa3e02d14 100644 --- a/tools/wheelmaker.py +++ b/tools/wheelmaker.py @@ -102,7 +102,7 @@ def __init__( filename, *, mode, - distribution_prefix: str | Path, + distribution_prefix: str, strip_path_prefixes=None, compression=zipfile.ZIP_DEFLATED, **kwargs, @@ -467,7 +467,7 @@ def _parse_file_pairs(content: List[str]) -> List[List[str]]: """ Parse ; delimited lists of files into a 2D list. """ - return [i.split(";") for i in content or []] + return [i.split(";", maxsplit=1) for i in content or []] def main() -> None: