Skip to content

Commit 2136215

Browse files
authored
refactor(pypi): further cleanup of pip.parse code (#2534)
Summary: - Move the `whl_library` creation into a separate function and remove the `TODO` note. - Move the creation of the `get_index_urls` functions into outer `parse_modules` function and simplify the reproducible extension setting logic. - Remove the `prefix` parameter from the `*repo_name` functions. - Add an extra error message, for ensuring that invariants are met. Work towards #260
1 parent a632044 commit 2136215

File tree

3 files changed

+124
-109
lines changed

3 files changed

+124
-109
lines changed

python/private/pypi/extension.bzl

Lines changed: 116 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,17 @@ def _create_whl_repos(
6767
*,
6868
pip_attr,
6969
whl_overrides,
70-
simpleapi_cache,
7170
evaluate_markers = evaluate_markers,
7271
available_interpreters = INTERPRETER_LABELS,
73-
simpleapi_download = simpleapi_download):
72+
get_index_urls = None):
7473
"""create all of the whl repositories
7574
7675
Args:
7776
module_ctx: {type}`module_ctx`.
7877
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
7978
whl_overrides: {type}`dict[str, struct]` - per-wheel overrides.
80-
simpleapi_cache: {type}`dict` - an opaque dictionary used for caching the results from calling
81-
SimpleAPI evaluating all of the tag class invocations {bzl:obj}`pip.parse`.
8279
evaluate_markers: the function to use to evaluate markers.
83-
simpleapi_download: Used for testing overrides
80+
get_index_urls: A function used to get the index URLs
8481
available_interpreters: {type}`dict[str, Label]` The dictionary of available
8582
interpreters that have been registered using the `python` bzlmod extension.
8683
The keys are in the form `python_{snake_case_version}_host`. This is to be
@@ -96,12 +93,9 @@ def _create_whl_repos(
9693
aparent repository names for the hub repo and the values are the
9794
arguments that will be passed to {bzl:obj}`whl_library` repository
9895
rule.
99-
is_reproducible: {type}`bool` set to True if does not make calls to the
100-
internet to evaluate the requirements files.
10196
"""
10297
logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos")
10398
python_interpreter_target = pip_attr.python_interpreter_target
104-
is_reproducible = True
10599

106100
# containers to aggregate outputs from this function
107101
whl_map = {}
@@ -158,26 +152,6 @@ def _create_whl_repos(
158152
whl_group_mapping = {}
159153
requirement_cycles = {}
160154

161-
# Create a new wheel library for each of the different whls
162-
163-
get_index_urls = None
164-
if pip_attr.experimental_index_url:
165-
get_index_urls = lambda ctx, distributions: simpleapi_download(
166-
ctx,
167-
attr = struct(
168-
index_url = pip_attr.experimental_index_url,
169-
extra_index_urls = pip_attr.experimental_extra_index_urls or [],
170-
index_url_overrides = pip_attr.experimental_index_url_overrides or {},
171-
sources = distributions,
172-
envsubst = pip_attr.envsubst,
173-
# Auth related info
174-
netrc = pip_attr.netrc,
175-
auth_patterns = pip_attr.auth_patterns,
176-
),
177-
cache = simpleapi_cache,
178-
parallel_download = pip_attr.parallel_download,
179-
)
180-
181155
requirements_by_platform = parse_requirements(
182156
module_ctx,
183157
requirements_by_platform = requirements_files_by_platform(
@@ -258,77 +232,27 @@ def _create_whl_repos(
258232
if v != default
259233
})
260234

261-
# TODO @aignas 2024-05-26: move to a separate function
262235
for requirement in requirements:
263-
dists = requirement.whls
264-
if not pip_attr.download_only and requirement.sdist:
265-
dists = dists + [requirement.sdist]
266-
267-
for distribution in dists:
268-
args = dict(whl_library_args)
269-
if pip_attr.netrc:
270-
args["netrc"] = pip_attr.netrc
271-
if pip_attr.auth_patterns:
272-
args["auth_patterns"] = pip_attr.auth_patterns
273-
274-
if not distribution.filename.endswith(".whl"):
275-
# pip is not used to download wheels and the python
276-
# `whl_library` helpers are only extracting things, however
277-
# for sdists, they will be built by `pip`, so we still
278-
# need to pass the extra args there.
279-
args["extra_pip_args"] = requirement.extra_pip_args
280-
281-
# This is no-op because pip is not used to download the wheel.
282-
args.pop("download_only", None)
283-
284-
repo_name = whl_repo_name(pip_name, distribution.filename, distribution.sha256)
285-
args["requirement"] = requirement.srcs.requirement
286-
args["urls"] = [distribution.url]
287-
args["sha256"] = distribution.sha256
288-
args["filename"] = distribution.filename
289-
args["experimental_target_platforms"] = requirement.target_platforms
290-
291-
# Pure python wheels or sdists may need to have a platform here
292-
target_platforms = None
293-
if distribution.filename.endswith("-any.whl") or not distribution.filename.endswith(".whl"):
294-
if len(requirements) > 1:
295-
target_platforms = requirement.target_platforms
236+
for repo_name, (args, config_setting) in _whl_repos(
237+
requirement = requirement,
238+
whl_library_args = whl_library_args,
239+
download_only = pip_attr.download_only,
240+
netrc = pip_attr.netrc,
241+
auth_patterns = pip_attr.auth_patterns,
242+
python_version = major_minor,
243+
multiple_requirements_for_whl = len(requirements) > 1.,
244+
).items():
245+
repo_name = "{}_{}".format(pip_name, repo_name)
246+
if repo_name in whl_libraries:
247+
fail("Attempting to creating a duplicate library {} for {}".format(
248+
repo_name,
249+
whl_name,
250+
))
296251

297252
whl_libraries[repo_name] = args
298-
299-
whl_map.setdefault(whl_name, {})[whl_config_setting(
300-
version = major_minor,
301-
filename = distribution.filename,
302-
target_platforms = target_platforms,
303-
)] = repo_name
304-
305-
if dists:
306-
is_reproducible = False
307-
continue
308-
309-
# Fallback to a pip-installed wheel
310-
args = dict(whl_library_args) # make a copy
311-
args["requirement"] = requirement.srcs.requirement_line
312-
if requirement.extra_pip_args:
313-
args["extra_pip_args"] = requirement.extra_pip_args
314-
315-
if pip_attr.download_only:
316-
args.setdefault("experimental_target_platforms", requirement.target_platforms)
317-
318-
target_platforms = requirement.target_platforms if len(requirements) > 1 else []
319-
repo_name = pypi_repo_name(
320-
pip_name,
321-
whl_name,
322-
*target_platforms
323-
)
324-
whl_libraries[repo_name] = args
325-
whl_map.setdefault(whl_name, {})[whl_config_setting(
326-
version = major_minor,
327-
target_platforms = target_platforms or None,
328-
)] = repo_name
253+
whl_map.setdefault(whl_name, {})[config_setting] = repo_name
329254

330255
return struct(
331-
is_reproducible = is_reproducible,
332256
whl_map = whl_map,
333257
exposed_packages = {
334258
whl_name: None
@@ -339,11 +263,88 @@ def _create_whl_repos(
339263
whl_libraries = whl_libraries,
340264
)
341265

342-
def parse_modules(module_ctx, _fail = fail, **kwargs):
266+
def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patterns, multiple_requirements_for_whl = False, python_version):
267+
ret = {}
268+
269+
dists = requirement.whls
270+
if not download_only and requirement.sdist:
271+
dists = dists + [requirement.sdist]
272+
273+
for distribution in dists:
274+
args = dict(whl_library_args)
275+
if netrc:
276+
args["netrc"] = netrc
277+
if auth_patterns:
278+
args["auth_patterns"] = auth_patterns
279+
280+
if not distribution.filename.endswith(".whl"):
281+
# pip is not used to download wheels and the python
282+
# `whl_library` helpers are only extracting things, however
283+
# for sdists, they will be built by `pip`, so we still
284+
# need to pass the extra args there.
285+
args["extra_pip_args"] = requirement.extra_pip_args
286+
287+
# This is no-op because pip is not used to download the wheel.
288+
args.pop("download_only", None)
289+
290+
args["requirement"] = requirement.srcs.requirement
291+
args["urls"] = [distribution.url]
292+
args["sha256"] = distribution.sha256
293+
args["filename"] = distribution.filename
294+
args["experimental_target_platforms"] = requirement.target_platforms
295+
296+
# Pure python wheels or sdists may need to have a platform here
297+
target_platforms = None
298+
if distribution.filename.endswith("-any.whl") or not distribution.filename.endswith(".whl"):
299+
if multiple_requirements_for_whl:
300+
target_platforms = requirement.target_platforms
301+
302+
repo_name = whl_repo_name(
303+
distribution.filename,
304+
distribution.sha256,
305+
)
306+
ret[repo_name] = (
307+
args,
308+
whl_config_setting(
309+
version = python_version,
310+
filename = distribution.filename,
311+
target_platforms = target_platforms,
312+
),
313+
)
314+
315+
if ret:
316+
return ret
317+
318+
# Fallback to a pip-installed wheel
319+
args = dict(whl_library_args) # make a copy
320+
args["requirement"] = requirement.srcs.requirement_line
321+
if requirement.extra_pip_args:
322+
args["extra_pip_args"] = requirement.extra_pip_args
323+
324+
if download_only:
325+
args.setdefault("experimental_target_platforms", requirement.target_platforms)
326+
327+
target_platforms = requirement.target_platforms if multiple_requirements_for_whl else []
328+
repo_name = pypi_repo_name(
329+
normalize_name(requirement.distribution),
330+
*target_platforms
331+
)
332+
ret[repo_name] = (
333+
args,
334+
whl_config_setting(
335+
version = python_version,
336+
target_platforms = target_platforms or None,
337+
),
338+
)
339+
340+
return ret
341+
342+
def parse_modules(module_ctx, _fail = fail, simpleapi_download = simpleapi_download, **kwargs):
343343
"""Implementation of parsing the tag classes for the extension and return a struct for registering repositories.
344344
345345
Args:
346346
module_ctx: {type}`module_ctx` module context.
347+
simpleapi_download: Used for testing overrides
347348
_fail: {type}`function` the failure function, mainly for testing.
348349
**kwargs: Extra arguments passed to the layers below.
349350
@@ -460,10 +461,29 @@ You cannot use both the additive_build_content and additive_build_content_file a
460461
else:
461462
pip_hub_map[pip_attr.hub_name].python_versions.append(pip_attr.python_version)
462463

464+
get_index_urls = None
465+
if pip_attr.experimental_index_url:
466+
is_reproducible = False
467+
get_index_urls = lambda ctx, distributions: simpleapi_download(
468+
ctx,
469+
attr = struct(
470+
index_url = pip_attr.experimental_index_url,
471+
extra_index_urls = pip_attr.experimental_extra_index_urls or [],
472+
index_url_overrides = pip_attr.experimental_index_url_overrides or {},
473+
sources = distributions,
474+
envsubst = pip_attr.envsubst,
475+
# Auth related info
476+
netrc = pip_attr.netrc,
477+
auth_patterns = pip_attr.auth_patterns,
478+
),
479+
cache = simpleapi_cache,
480+
parallel_download = pip_attr.parallel_download,
481+
)
482+
463483
out = _create_whl_repos(
464484
module_ctx,
465485
pip_attr = pip_attr,
466-
simpleapi_cache = simpleapi_cache,
486+
get_index_urls = get_index_urls,
467487
whl_overrides = whl_overrides,
468488
**kwargs
469489
)
@@ -476,7 +496,6 @@ You cannot use both the additive_build_content and additive_build_content_file a
476496
extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases)
477497
exposed_packages.setdefault(hub_name, {}).update(out.exposed_packages)
478498
whl_libraries.update(out.whl_libraries)
479-
is_reproducible = is_reproducible and out.is_reproducible
480499

481500
# TODO @aignas 2024-04-05: how do we support different requirement
482501
# cycles for different abis/oses? For now we will need the users to

python/private/pypi/whl_repo_name.bzl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818
load("//python/private:normalize_name.bzl", "normalize_name")
1919
load(":parse_whl_name.bzl", "parse_whl_name")
2020

21-
def whl_repo_name(prefix, filename, sha256):
21+
def whl_repo_name(filename, sha256):
2222
"""Return a valid whl_library repo name given a distribution filename.
2323
2424
Args:
25-
prefix: {type}`str` the prefix of the whl_library.
2625
filename: {type}`str` the filename of the distribution.
2726
sha256: {type}`str` the sha256 of the distribution.
2827
2928
Returns:
3029
a string that can be used in {obj}`whl_library`.
3130
"""
32-
parts = [prefix]
31+
parts = []
3332

3433
if not filename.endswith(".whl"):
3534
# Then the filename is basically foo-3.2.1.<ext>
@@ -51,19 +50,17 @@ def whl_repo_name(prefix, filename, sha256):
5150

5251
return "_".join(parts)
5352

54-
def pypi_repo_name(prefix, whl_name, *target_platforms):
53+
def pypi_repo_name(whl_name, *target_platforms):
5554
"""Return a valid whl_library given a requirement line.
5655
5756
Args:
58-
prefix: {type}`str` the prefix of the whl_library.
5957
whl_name: {type}`str` the whl_name to use.
6058
*target_platforms: {type}`list[str]` the target platforms to use in the name.
6159
6260
Returns:
6361
{type}`str` that can be used in {obj}`whl_library`.
6462
"""
6563
parts = [
66-
prefix,
6764
normalize_name(whl_name),
6865
]
6966
parts.extend([p.partition("_")[-1] for p in target_platforms])

tests/pypi/whl_repo_name/whl_repo_name_tests.bzl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,25 @@ load("//python/private/pypi:whl_repo_name.bzl", "whl_repo_name") # buildifier:
2020
_tests = []
2121

2222
def _test_simple(env):
23-
got = whl_repo_name("prefix", "foo-1.2.3-py3-none-any.whl", "deadbeef")
24-
env.expect.that_str(got).equals("prefix_foo_py3_none_any_deadbeef")
23+
got = whl_repo_name("foo-1.2.3-py3-none-any.whl", "deadbeef")
24+
env.expect.that_str(got).equals("foo_py3_none_any_deadbeef")
2525

2626
_tests.append(_test_simple)
2727

2828
def _test_sdist(env):
29-
got = whl_repo_name("prefix", "foo-1.2.3.tar.gz", "deadbeef000deadbeef")
30-
env.expect.that_str(got).equals("prefix_foo_sdist_deadbeef")
29+
got = whl_repo_name("foo-1.2.3.tar.gz", "deadbeef000deadbeef")
30+
env.expect.that_str(got).equals("foo_sdist_deadbeef")
3131

3232
_tests.append(_test_sdist)
3333

3434
def _test_platform_whl(env):
3535
got = whl_repo_name(
36-
"prefix",
3736
"foo-1.2.3-cp39.cp310-abi3-manylinux1_x86_64.manylinux_2_17_x86_64.whl",
3837
"deadbeef000deadbeef",
3938
)
4039

4140
# We only need the first segment of each
42-
env.expect.that_str(got).equals("prefix_foo_cp39_abi3_manylinux_2_5_x86_64_deadbeef")
41+
env.expect.that_str(got).equals("foo_cp39_abi3_manylinux_2_5_x86_64_deadbeef")
4342

4443
_tests.append(_test_platform_whl)
4544

0 commit comments

Comments
 (0)