Skip to content

Commit fd25d1f

Browse files
aignasrickeylev
andauthored
refactor(pypi): add whl_deps_library repo rule (#3960)
Summary: - Add a new repo rule to just read metadata.json - Add integration tests for the repository rules in `whl_library.bzl` file. - Make some of the arguments optional in the BUILD.bazel code generation. No changelog, because the rule is not yet exposed to the user in any way. Split out of #3856 Work towards #2948 Fixes #3071 --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com>
1 parent 41665f5 commit fd25d1f

17 files changed

Lines changed: 736 additions & 221 deletions

File tree

.bazelrc.deleted_packages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ common --deleted_packages=tests/integration/toolchain_target_settings
4444
common --deleted_packages=tests/integration/unified_pypi
4545
common --deleted_packages=tests/integration/uv_lock
4646
common --deleted_packages=tests/integration/validate_test_main
47+
common --deleted_packages=tests/integration/whl_library
4748
common --deleted_packages=tests/modules/another_module
4849
common --deleted_packages=tests/modules/other
4950
common --deleted_packages=tests/modules/other/nspkg_delta

python/private/pypi/generate_whl_library_build_bazel.bzl

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ _RENDER = {
2525
"extras": render.list,
2626
"group_deps": render.list,
2727
"include": str,
28+
"repo": lambda maybe_label: repr(str(maybe_label)),
2829
"requires_dist": render.list,
2930
"srcs_exclude": render.list,
3031
"tags": render.list,
@@ -38,19 +39,22 @@ _TEMPLATE = """\
3839
3940
package(default_visibility = ["//visibility:public"])
4041
42+
{fn}(
43+
{kwargs}
44+
)
45+
"""
46+
47+
_PURL = """\
4148
package_metadata(
4249
name = "package_metadata",
4350
purl = {purl},
4451
visibility = ["//:__subpackages__"],
4552
)
46-
47-
{fn}(
48-
{kwargs}
49-
)
5053
"""
5154

5255
def generate_whl_library_build_bazel(
5356
*,
57+
metadata_version,
5458
annotation = None,
5559
config_load,
5660
purl = None,
@@ -59,6 +63,7 @@ def generate_whl_library_build_bazel(
5963
"""Generate a BUILD file for an unzipped Wheel
6064
6165
Args:
66+
metadata_version: The version to use for tag generation.
6267
annotation: The annotation for the build file.
6368
config_load: {type}`str` The location from where to load the config.
6469
purl: The purl.
@@ -74,14 +79,26 @@ def generate_whl_library_build_bazel(
7479
"""load("@package_metadata//rules:package_metadata.bzl", "package_metadata")""",
7580
]
7681

77-
fn = "whl_library_targets_from_requires"
82+
if kwargs.get("repo"):
83+
fn = "whl_library_deps_targets"
84+
else:
85+
fn = "whl_library_targets"
86+
87+
tags = [
88+
"pypi_name={}".format(kwargs.get("metadata_name")),
89+
"pypi_version={}".format(metadata_version),
90+
]
91+
kwargs["tags"] = tags
92+
7893
if not requires_dist:
7994
# no deps, we can leave the extra loads out
8095
pass
81-
else:
96+
elif config_load:
8297
loads.append("""load("{}", "{}")""".format(config_load, "packages"))
8398
kwargs["include"] = "packages"
8499
kwargs["requires_dist"] = requires_dist
100+
else:
101+
kwargs["requires_dist"] = requires_dist
85102

86103
loads.extend([
87104
"""load("@rules_python//python/private/pypi:whl_library_targets.bzl", "{}")""".format(fn),
@@ -106,9 +123,8 @@ def generate_whl_library_build_bazel(
106123
"{} = {},".format(k, _RENDER.get(k, repr)(v))
107124
for k, v in sorted(kwargs.items())
108125
])),
109-
purl = repr(purl),
110126
),
111-
] + additional_content,
127+
] + ([_PURL.format(purl = repr(purl))] if purl else []) + additional_content,
112128
)
113129

114130
# NOTE: Ensure that we terminate with a new line

python/private/pypi/pep508_deps.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def deps(
4444
* deps_select: {type}`dict[str, list[str]]` dependencies to include on particular
4545
subset of target platforms.
4646
"""
47+
if not requires_dist:
48+
return struct(
49+
deps = [],
50+
deps_select = {},
51+
)
52+
4753
reqs = sorted(
4854
[requirement(r) for r in requires_dist],
4955
key = lambda x: "{}:{}:".format(x.name, sorted(x.extras), x.marker),

python/private/pypi/whl_library.bzl

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,12 @@ def _whl_extract(rctx, *, whl_path, logger, sdist_filename = None):
332332
read_fn = rctx.read,
333333
logger = logger,
334334
)
335+
rctx.file("metadata.json", json.encode_indent({
336+
"name": metadata.name,
337+
"provides_extra": metadata.provides_extra,
338+
"requires_dist": metadata.requires_dist,
339+
"version": metadata.version,
340+
}))
335341
namespace_package_files = pypi_repo_utils.find_namespace_package_files(rctx, install_dir_path)
336342

337343
entry_points = _get_entry_points(rctx, install_dir_path, metadata)
@@ -429,6 +435,8 @@ def _whl_archive_impl(rctx):
429435
whl_path = rctx.path(filename)
430436
else:
431437
fail("Only wheels are supported")
438+
else:
439+
fail("Either 'whl_file' or 'urls' and 'filename' needs to be specified")
432440

433441
return _whl_extract(rctx, whl_path = whl_path, logger = logger)
434442

@@ -571,9 +579,6 @@ For example if your whl depends on `numpy` and your Python package repo is named
571579
"index_url": attr.string(
572580
doc = "The index_url that the package will be downloaded from.",
573581
),
574-
"repo": attr.string(
575-
doc = "Pointer to parent repo name. Used to make these rules rerun if the parent repo changes.",
576-
),
577582
"repo_prefix": attr.string(
578583
doc = """
579584
Prefix for the generated packages will be of the form `@<prefix><sanitized-package-name>//...`
@@ -676,7 +681,6 @@ whl_archive = repository_rule(
676681
"group_deps",
677682
"group_name",
678683
"index_url",
679-
"repo",
680684
"repo_prefix",
681685
"requirement",
682686
"sha256",
@@ -702,7 +706,74 @@ Does not depend on any python.
702706
],
703707
)
704708

705-
def whl_library(name, **kwargs):
709+
def _whl_deps_library_impl(rctx):
710+
logger = repo_utils.logger(rctx)
711+
712+
if rctx.attr.metadata_file and rctx.attr.metadata:
713+
logger.fail("Only one of 'metadata_file' and 'metadata' can be specified")
714+
return
715+
if not (rctx.attr.metadata_file or rctx.attr.metadata):
716+
logger.fail("At least one of 'metadata_file' and 'metadata' must be specified")
717+
return
718+
719+
if rctx.attr.metadata_file:
720+
metadata_contents = rctx.read(rctx.attr.metadata_file)
721+
else:
722+
metadata_contents = rctx.attr.metadata
723+
724+
metadata = struct(**json.decode(metadata_contents))
725+
726+
build_file_contents = generate_whl_library_build_bazel(
727+
dep_template = rctx.attr.dep_template or "@{}{{name}}//:{{target}}".format(
728+
rctx.attr.repo_prefix,
729+
),
730+
config_load = rctx.attr.config_load,
731+
metadata_name = metadata.name,
732+
metadata_version = metadata.version,
733+
requires_dist = metadata.requires_dist,
734+
group_deps = rctx.attr.group_deps,
735+
group_name = rctx.attr.group_name,
736+
repo = rctx.attr.repo or (
737+
str(rctx.attr.metadata_file) if rctx.attr.metadata_file else None
738+
),
739+
extras = requirement(rctx.attr.requirement).extras,
740+
)
741+
rctx.file("BUILD.bazel", build_file_contents)
742+
743+
whl_deps_library = repository_rule(
744+
attrs = {
745+
k: _pip_archive_attrs[k]
746+
for k in [
747+
"config_load",
748+
"dep_template",
749+
"group_deps",
750+
"group_name",
751+
"requirement",
752+
]
753+
} | {
754+
"metadata": attr.string(
755+
doc = """
756+
The subset of the METADATA contents that is needed for generation of the dependencies.
757+
* name: {type}`str`
758+
* version: {type}`str`
759+
* provides_extra: {type}`list[str]`
760+
* requires_dist: {type}`list[str]`
761+
""",
762+
),
763+
"metadata_file": attr.label(doc = "An alternative way to pass {attr}`metadata` but as a file."),
764+
"repo": attr.label(doc = "A label at the root of the repo to get stuff from."),
765+
},
766+
doc = """
767+
A repo rule that reuses the sources from a different place and then creates the necessary targets
768+
so that this can be used in the repo.
769+
770+
Does not depend on any python.
771+
""",
772+
implementation = _whl_deps_library_impl,
773+
environ = [REPO_DEBUG_ENV_VAR],
774+
)
775+
776+
def whl_library(name, repo = None, **kwargs):
706777
"""Create a whl_library.
707778
708779
This proxies to one of the underlying implementations:
@@ -711,15 +782,18 @@ def whl_library(name, **kwargs):
711782
712783
Args:
713784
name: {type}`str` The name of the repo.
785+
repo: Unused, will be dropped in the next major release.
714786
**kwargs: The args passed to the underlying implementation.
715787
716788
Returns:
717789
the repo metadata.
718790
"""
791+
_ = repo # buildifier: disable=unused-variable
792+
719793
whl_file = kwargs.get("whl_file")
720794
urls = kwargs.get("urls", [])
721795
filename = kwargs.get("filename")
722796
if whl_file or (urls and filename and filename.endswith(".whl")):
723-
return whl_archive(name = name, **kwargs)
724-
725-
return pip_archive(name = name, **kwargs)
797+
whl_archive(name = name, **kwargs)
798+
else:
799+
pip_archive(name = name, **kwargs)

0 commit comments

Comments
 (0)