diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c19d1176e..3e9f802e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ END_UNRELEASED_TEMPLATE * (toolchains) `3.13.12`, `3.14.3` Python toolchain from [20260325] release. * (toolchains) `3.10.20`, `3.11.15`, `3.12.13`, `3.13.13` `3.14.4`, `3.15.0a8` * Python toolchain from [20260414] release. +* (pypi) `package_metadata` support, fixes + [#2054](https://github.com/bazel-contrib/rules_python/issues/2054). [20260325]: https://github.com/astral-sh/python-build-standalone/releases/tag/20260325 [20260414]: https://github.com/astral-sh/python-build-standalone/releases/tag/20260414 diff --git a/MODULE.bazel b/MODULE.bazel index b5f67c204e..f0dcfc81fc 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,8 +6,9 @@ module( bazel_dep(name = "bazel_features", version = "1.21.0") bazel_dep(name = "bazel_skylib", version = "1.8.2") -bazel_dep(name = "rules_cc", version = "0.1.5") +bazel_dep(name = "package_metadata", version = "0.0.7") bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.5") # Those are loaded only when using py_proto_library # Use py_proto_library directly from protobuf repository diff --git a/python/private/py_repositories.bzl b/python/private/py_repositories.bzl index e3ab11c561..9c4051ede9 100644 --- a/python/private/py_repositories.bzl +++ b/python/private/py_repositories.bzl @@ -72,6 +72,12 @@ def py_repositories(transition_settings = []): strip_prefix = "rules_cc-0.1.5", urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.1.5/rules_cc-0.1.5.tar.gz"], ) + http_archive( + name = "package_metadata", + sha256 = "8f27dc7393e3f3bdc793bdc4ba36d67a63c22cc9d38cc65d3204654974ea4563", + strip_prefix = "supply-chain-0.0.7/metadata", + url = "https://github.com/bazel-contrib/supply-chain/releases/download/v0.0.7/supply-chain-v0.0.7.tar.gz", + ) # Needed by rules_cc, triggered by @rules_java_prebuilt in Bazel by using @rules_cc//cc:defs.bzl # NOTE: This name must be com_google_protobuf until Bazel drops WORKSPACE diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index e7d19ea636..02c06a8096 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -495,6 +495,7 @@ bzl_library( "//python/private:auth_bzl", "//python/private:envsubst_bzl", "//python/private:is_standalone_interpreter_bzl", + "//python/private:normalize_name_bzl", "//python/private:repo_utils_bzl", "//python/private:util_bzl", "@rules_python_internal//:rules_python_config_bzl", diff --git a/python/private/pypi/generate_whl_library_build_bazel.bzl b/python/private/pypi/generate_whl_library_build_bazel.bzl index 5811ed1574..768b064a5d 100644 --- a/python/private/pypi/generate_whl_library_build_bazel.bzl +++ b/python/private/pypi/generate_whl_library_build_bazel.bzl @@ -40,6 +40,12 @@ _TEMPLATE = """\ package(default_visibility = ["//visibility:public"]) +package_metadata( + name = "package_metadata", + purl = {purl}, + visibility = ["//:__subpackages__"], +) + {fn}( {kwargs} ) @@ -49,12 +55,14 @@ def generate_whl_library_build_bazel( *, annotation = None, default_python_version = None, + purl = None, **kwargs): """Generate a BUILD file for an unzipped Wheel Args: annotation: The annotation for the build file. default_python_version: The python version to use to parse the METADATA. + purl: The purl. **kwargs: Extra args serialized to be passed to the {obj}`whl_library_targets`. @@ -62,7 +70,10 @@ def generate_whl_library_build_bazel( A complete BUILD file as a string """ - loads = [] + loads = [ + """load("@package_metadata//rules:package_metadata.bzl", "package_metadata")""", + ] + if kwargs.get("tags"): fn = "whl_library_targets" @@ -119,6 +130,7 @@ def generate_whl_library_build_bazel( "{} = {},".format(k, _RENDER.get(k, repr)(v)) for k, v in sorted(kwargs.items()) ])), + purl = repr(purl), ), ] + additional_content, ) diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl index 36df4dc82e..13a8e6ff8e 100644 --- a/python/private/pypi/whl_library.bzl +++ b/python/private/pypi/whl_library.bzl @@ -18,6 +18,7 @@ load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth") load("//python/private:envsubst.bzl", "envsubst") load("//python/private:is_standalone_interpreter.bzl", "is_standalone_interpreter") +load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils") load(":attrs.bzl", "ATTRS", "use_isolated") load(":deps.bzl", "all_repo_names", "record_files") @@ -276,6 +277,24 @@ def _extract_whl_py(rctx, *, python_interpreter, args, whl_path, environment, lo logger = logger, ) +def _to_purl(*, index, metadata, filename): + """ + Produce a PyPI PURL from the metadata. + + https://github.com/package-url/purl-spec/blob/main/types-doc/pypi-definition.md + """ + + # https://github.com/package-url/purl-spec/blob/main/types-doc/pypi-definition.md#name-definition + name = normalize_name(metadata.name).replace("_", "-") + + qualifiers = {} + if index: + qualifiers["repository_url"] = index + if filename: + qualifiers["file_name"] = filename + + return "pkg:pypi/{}@{}?{}".format(name, metadata.version, "&".join(["{}={}".format(key, val) for key, val in qualifiers.items()])) + def _whl_library_impl(rctx): logger = repo_utils.logger(rctx) @@ -436,6 +455,11 @@ def _whl_library_impl(rctx): group_name = rctx.attr.group_name, namespace_package_files = namespace_package_files, extras = requirement(rctx.attr.requirement).extras, + purl = _to_purl( + index = rctx.attr.index_url, + metadata = metadata, + filename = sdist_filename or whl_path.basename, + ), ) # Delete these in case the wheel had them. They generally don't cause @@ -443,7 +467,13 @@ def _whl_library_impl(rctx): rctx.file("WORKSPACE") rctx.file("WORKSPACE.bazel") rctx.file("MODULE.bazel") - rctx.file("REPO.bazel") + rctx.file("REPO.bazel", """\ +repo( + default_package_metadata = [ + "//:package_metadata", + ], +) +""") # BUILD files interfere with globbing and Bazel package boundaries. _remove_files(rctx, "BUILD", "BUILD.bazel") diff --git a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl index 2f421f35d4..9586581cad 100644 --- a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl +++ b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl @@ -21,10 +21,17 @@ _tests = [] def _test_all_legacy(env): want = """\ +load("@package_metadata//rules:package_metadata.bzl", "package_metadata") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets") package(default_visibility = ["//visibility:public"]) +package_metadata( + name = "package_metadata", + purl = None, + visibility = ["//:__subpackages__"], +) + whl_library_targets( copy_executables = { "exec_src": "exec_dest", @@ -79,11 +86,18 @@ _tests.append(_test_all_legacy) def _test_all_workspace(env): want = """\ +load("@package_metadata//rules:package_metadata.bzl", "package_metadata") load("@pypi//:config.bzl", "packages") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires") package(default_visibility = ["//visibility:public"]) +package_metadata( + name = "package_metadata", + purl = None, + visibility = ["//:__subpackages__"], +) + whl_library_targets_from_requires( copy_executables = { "exec_src": "exec_dest", @@ -138,11 +152,18 @@ _tests.append(_test_all_workspace) def _test_all(env): want = """\ +load("@package_metadata//rules:package_metadata.bzl", "package_metadata") load("@pypi//:config.bzl", "packages") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires") package(default_visibility = ["//visibility:public"]) +package_metadata( + name = "package_metadata", + purl = None, + visibility = ["//:__subpackages__"], +) + whl_library_targets_from_requires( copy_executables = { "exec_src": "exec_dest", @@ -197,11 +218,18 @@ _tests.append(_test_all) def _test_all_with_loads(env): want = """\ +load("@package_metadata//rules:package_metadata.bzl", "package_metadata") load("@pypi//:config.bzl", "packages") load("@rules_python//python/private/pypi:whl_library_targets.bzl", "whl_library_targets_from_requires") package(default_visibility = ["//visibility:public"]) +package_metadata( + name = "package_metadata", + purl = None, + visibility = ["//:__subpackages__"], +) + whl_library_targets_from_requires( copy_executables = { "exec_src": "exec_dest",