Skip to content

Commit 88c8959

Browse files
committed
Merge branch 'main' into exp/pypi-simplify
2 parents 0bd5fbf + 7685993 commit 88c8959

File tree

14 files changed

+145
-26
lines changed

14 files changed

+145
-26
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ END_UNRELEASED_TEMPLATE
9494
* (pypi) To configure the environment for `requirements.txt` evaluation, use the newly added
9595
developer preview of the `pip.default` tag class. Only `rules_python` and root modules can use
9696
this feature. You can also configure custom `config_settings` using `pip.default`.
97+
* (pypi) PyPI dependencies now expose an `:extracted_whl_files` filegroup target
98+
of all the files extracted from the wheel. This can be used in lieu of
99+
{obj}`whl_filegroup` to avoid copying/extracting wheel multiple times to
100+
get a subset of their files.
97101
* (gazelle) New directive `gazelle:python_generate_pyi_deps`; when `true`,
98102
dependencies added to satisfy type-only imports (`if TYPE_CHECKING`) and type
99103
stub packages are added to `pyi_deps` instead of `deps`.

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,13 @@ merged:
234234
## Binary artifacts
235235

236236
Checking in binary artifacts is not allowed. This is because they are extremely
237-
problematic to verify and ensure they're safe
237+
problematic to verify and ensure they're safe. This is true even in
238+
test contexts.
238239

239240
Examples include, but aren't limited to: prebuilt binaries, shared libraries,
240241
zip files, or wheels.
241242

243+
See the dev guide for utilities to help with testing.
242244

243245
(breaking-changes)=
244246
## Breaking Changes

docs/devguide.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ to be perfectly factored and not every common thing a test does needs to be
3737
factored into a more generally reusable piece. Copying and pasting is fine. It's
3838
more important for tests to balance understandability and maintainability.
3939

40+
### Test utilities
41+
42+
General code to support testing is in {gh-path}`tests/support`. It has a variety
43+
of functions, constants, rules etc, to make testing easier. Below are some
44+
common utilities that are frequently used.
45+
4046
### sh_py_run_test
4147

4248
The {gh-path}`sh_py_run_test <tests/support/sh_py_run_test.bzl` rule is a helper to
@@ -68,6 +74,12 @@ the rule. To have it support setting a new flag:
6874
list, then modify the transition's logic to check the attribute and set
6975
the flag value if the attribute is set.
7076

77+
### whl_from_dir_repo
78+
79+
The `whl_from_dir_repo` repository rule in {gh-path}`tests/support/whl_from_dir`
80+
takes a directory tree and turns it into a `.whl` file. This can be used to
81+
create arbitrary whl files to verify functionality.
82+
7183
### Integration tests
7284

7385
An integration test is one that runs a separate Bazel instance inside the test.

docs/pypi/use.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,16 @@ Note that the hub repo contains the following targets for each package:
4040
* `@pypi//numpy:data` - the {obj}`filegroup` for all of the extra files that are included
4141
as data in the `pkg` target.
4242
* `@pypi//numpy:dist_info` - the {obj}`filegroup` for all of the files in the `<pkg prefix with version>.distinfo` directory.
43+
* `@pypi//numpy:extracted_whl_files` - a {obj}`filegroup` of all the files
44+
extracted from the whl file.
4345
* `@pypi//numpy:whl` - the {obj}`filegroup` that is the `.whl` file itself, which includes all
4446
transitive dependencies via the {attr}`filegroup.data` attribute.
4547

48+
:::{versionadded} VERSION_NEXT_FEATURE
49+
50+
The `:extracted_whl_files` target was added
51+
:::
52+
4653
## Entry points
4754

4855
If you would like to access [entry points][whl_ep], see the `py_console_script_binary` rule documentation,

python/private/pypi/labels.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Constants used by parts of pip_repository for naming libraries and wheels."""
1616

17+
EXTRACTED_WHEEL_FILES = "extracted_whl_files"
1718
WHEEL_FILE_PUBLIC_LABEL = "whl"
1819
WHEEL_FILE_IMPL_LABEL = "_whl"
1920
PY_LIBRARY_PUBLIC_LABEL = "pkg"

python/private/pypi/pkg_aliases.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ load(
7979
":labels.bzl",
8080
"DATA_LABEL",
8181
"DIST_INFO_LABEL",
82+
"EXTRACTED_WHEEL_FILES",
8283
"PY_LIBRARY_IMPL_LABEL",
8384
"PY_LIBRARY_PUBLIC_LABEL",
8485
"WHEEL_FILE_IMPL_LABEL",
@@ -151,6 +152,7 @@ def pkg_aliases(
151152
WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL,
152153
DATA_LABEL: DATA_LABEL,
153154
DIST_INFO_LABEL: DIST_INFO_LABEL,
155+
EXTRACTED_WHEEL_FILES: EXTRACTED_WHEEL_FILES,
154156
} | {
155157
x: x
156158
for x in extra_aliases or []

python/private/pypi/whl_library.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def _whl_library_impl(rctx):
248248
environment = _create_repository_execution_environment(rctx, python_interpreter, logger = logger)
249249

250250
whl_path = None
251+
sdist_filename = None
251252
if rctx.attr.whl_file:
252253
rctx.watch(rctx.attr.whl_file)
253254
whl_path = rctx.path(rctx.attr.whl_file)
@@ -277,6 +278,8 @@ def _whl_library_impl(rctx):
277278
if filename.endswith(".whl"):
278279
whl_path = rctx.path(filename)
279280
else:
281+
sdist_filename = filename
282+
280283
# It is an sdist and we need to tell PyPI to use a file in this directory
281284
# and, allow getting build dependencies from PYTHONPATH, which we
282285
# setup in this repository rule, but still download any necessary
@@ -382,6 +385,7 @@ def _whl_library_impl(rctx):
382385

383386
build_file_contents = generate_whl_library_build_bazel(
384387
name = whl_path.basename,
388+
sdist_filename = sdist_filename,
385389
dep_template = rctx.attr.dep_template or "@{}{{name}}//:{{target}}".format(rctx.attr.repo_prefix),
386390
entry_points = entry_points,
387391
metadata_name = metadata.name,
@@ -455,6 +459,7 @@ def _whl_library_impl(rctx):
455459

456460
build_file_contents = generate_whl_library_build_bazel(
457461
name = whl_path.basename,
462+
sdist_filename = sdist_filename,
458463
dep_template = rctx.attr.dep_template or "@{}{{name}}//:{{target}}".format(rctx.attr.repo_prefix),
459464
entry_points = entry_points,
460465
# TODO @aignas 2025-05-17: maybe have a build flag for this instead

python/private/pypi/whl_library_targets.bzl

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ load(
2424
":labels.bzl",
2525
"DATA_LABEL",
2626
"DIST_INFO_LABEL",
27+
"EXTRACTED_WHEEL_FILES",
2728
"PY_LIBRARY_IMPL_LABEL",
2829
"PY_LIBRARY_PUBLIC_LABEL",
2930
"WHEEL_ENTRY_POINT_PREFIX",
@@ -33,6 +34,16 @@ load(
3334
load(":namespace_pkgs.bzl", _create_inits = "create_inits")
3435
load(":pep508_deps.bzl", "deps")
3536

37+
# Files that are special to the Bazel processing of things.
38+
_BAZEL_REPO_FILE_GLOBS = [
39+
"BUILD",
40+
"BUILD.bazel",
41+
"REPO.bazel",
42+
"WORKSPACE",
43+
"WORKSPACE",
44+
"WORKSPACE.bazel",
45+
]
46+
3647
def whl_library_targets_from_requires(
3748
*,
3849
name,
@@ -97,14 +108,12 @@ def whl_library_targets(
97108
*,
98109
name,
99110
dep_template,
111+
sdist_filename = None,
100112
data_exclude = [],
101113
srcs_exclude = [],
102114
tags = [],
103-
filegroups = {
104-
DIST_INFO_LABEL: ["site-packages/*.dist-info/**"],
105-
DATA_LABEL: ["data/**"],
106-
},
107115
dependencies = [],
116+
filegroups = None,
108117
dependencies_by_platform = {},
109118
dependencies_with_markers = {},
110119
group_deps = [],
@@ -129,14 +138,16 @@ def whl_library_targets(
129138
filegroup. This may be also parsed to generate extra metadata.
130139
dep_template: {type}`str` The dep_template to use for dependency
131140
interpolation.
141+
sdist_filename: {type}`str | None` If the wheel was built from an sdist,
142+
the filename of the sdist.
132143
tags: {type}`list[str]` The tags set on the `py_library`.
133144
dependencies: {type}`list[str]` A list of dependencies.
134145
dependencies_by_platform: {type}`dict[str, list[str]]` A list of
135146
dependencies by platform key.
136147
dependencies_with_markers: {type}`dict[str, str]` A marker to evaluate
137148
in order for the dep to be included.
138-
filegroups: {type}`dict[str, list[str]]` A dictionary of the target
139-
names and the glob matches.
149+
filegroups: {type}`dict[str, list[str]] | None` A dictionary of the target
150+
names and the glob matches. If `None`, defaults will be used.
140151
group_name: {type}`str` name of the dependency group (if any) which
141152
contains this library. If set, this library will behave as a shim
142153
to group implementation rules which will provide simultaneously
@@ -169,10 +180,28 @@ def whl_library_targets(
169180
tags = sorted(tags)
170181
data = [] + data
171182

172-
for filegroup_name, glob in filegroups.items():
183+
if filegroups == None:
184+
filegroups = {
185+
EXTRACTED_WHEEL_FILES: dict(
186+
include = ["**"],
187+
exclude = (
188+
_BAZEL_REPO_FILE_GLOBS +
189+
[sdist_filename] if sdist_filename else []
190+
),
191+
),
192+
DIST_INFO_LABEL: dict(
193+
include = ["site-packages/*.dist-info/**"],
194+
),
195+
DATA_LABEL: dict(
196+
include = ["data/**"],
197+
),
198+
}
199+
200+
for filegroup_name, glob_kwargs in filegroups.items():
201+
glob_kwargs = {"allow_empty": True} | glob_kwargs
173202
native.filegroup(
174203
name = filegroup_name,
175-
srcs = native.glob(glob, allow_empty = True),
204+
srcs = native.glob(**glob_kwargs),
176205
visibility = ["//visibility:public"],
177206
)
178207

python/private/version.bzl

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ def _in(reference):
4444
return lambda token: token in reference
4545

4646
def _ctx(start):
47-
return {"norm": "", "start": start}
47+
"""Creates a context, which is state for parsing (or sub-parsing)."""
48+
return {
49+
# The result value from parsing
50+
"norm": "",
51+
# Where in the parser's input string this context starts.
52+
"start": start,
53+
}
4854

4955
def _open_context(self):
5056
"""Open an new parsing ctx.
@@ -60,7 +66,16 @@ def _open_context(self):
6066
return self.contexts[-1]
6167

6268
def _accept(self, key = None):
63-
"""Close the current ctx successfully and merge the results."""
69+
"""Close the current ctx successfully and merge the results.
70+
71+
Args:
72+
self: {type}`Parser}
73+
key: {type}`str | None` the key to store the result in
74+
the most recent context. If not set, the key is "norm".
75+
76+
Returns:
77+
{type}`bool` always True
78+
"""
6479
finished = self.contexts.pop()
6580
self.contexts[-1]["norm"] += finished["norm"]
6681
if key:
@@ -79,7 +94,14 @@ def _discard(self, key = None):
7994
return False
8095

8196
def _new(input):
82-
"""Create a new normalizer"""
97+
"""Create a new parser
98+
99+
Args:
100+
input: {type}`str` input to parse
101+
102+
Returns:
103+
{type}`Parser` a struct for a parser object.
104+
"""
83105
self = struct(
84106
input = input,
85107
contexts = [_ctx(0)],
@@ -167,7 +189,7 @@ def accept_placeholder(parser):
167189
return parser.accept()
168190

169191
def accept_digits(parser):
170-
"""Accept multiple digits (or placeholders).
192+
"""Accept multiple digits (or placeholders), up to a non-digit/placeholder.
171193
172194
Args:
173195
parser: The normalizer.
@@ -275,13 +297,20 @@ def accept_separator_alnum(parser):
275297
Returns:
276298
whether a separator and an alphanumeric string were accepted.
277299
"""
278-
parser.open_context()
300+
ctx = parser.open_context()
279301

280302
# PEP 440: Local version segments
281-
if (
282-
accept(parser, _in([".", "-", "_"]), ".") and
283-
(accept_digits(parser) or accept_alnum(parser))
284-
):
303+
if not accept(parser, _in([".", "-", "_"]), "."):
304+
return parser.discard()
305+
306+
if accept_alnum(parser):
307+
# First character is separator; skip it.
308+
value = ctx["norm"][1:]
309+
310+
# PEP 440: Integer Normalization
311+
if value.isdigit():
312+
value = str(int(value))
313+
ctx["norm"] = ctx["norm"][0] + value
285314
return parser.accept()
286315

287316
return parser.discard()

python/private/whl_filegroup/whl_filegroup.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ cc_library(
4242
includes = ["numpy_includes/numpy/core/include"],
4343
deps = ["@rules_python//python/cc:current_py_cc_headers"],
4444
)
45+
4546
```
47+
48+
:::{seealso}
49+
50+
The `:extracted_whl_files` target, which is a filegroup of all the files
51+
from the already extracted whl file.
52+
:::
4653
""",
4754
attrs = {
4855
"pattern": attr.string(default = "", doc = "Only file paths matching this regex pattern will be extracted."),

0 commit comments

Comments
 (0)