Skip to content

Commit bc8a62b

Browse files
authored
refactor(pypi): cleanup marker evaluation code in requirement parsing (#3765)
Another cleanup PR to make the code easier to work with and optimize.
1 parent c7efd79 commit bc8a62b

8 files changed

Lines changed: 48 additions & 130 deletions

File tree

python/private/pypi/BUILD.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ bzl_library(
190190
visibility = ["//:__subpackages__"],
191191
deps = [
192192
":attrs_bzl",
193-
":evaluate_markers_bzl",
194193
":parse_requirements_bzl",
195194
":pep508_env_bzl",
196195
":pep508_evaluate_bzl",
@@ -247,6 +246,8 @@ bzl_library(
247246
":argparse_bzl",
248247
":index_sources_bzl",
249248
":parse_requirements_txt_bzl",
249+
":pep508_evaluate_bzl",
250+
":pep508_requirement_bzl",
250251
":pypi_repo_utils_bzl",
251252
":requirements_files_by_platform_bzl",
252253
":select_whl_bzl",
@@ -344,7 +345,6 @@ bzl_library(
344345
srcs = ["pip_repository.bzl"],
345346
deps = [
346347
":attrs_bzl",
347-
":evaluate_markers_bzl",
348348
":parse_requirements_bzl",
349349
":pep508_env_bzl",
350350
":pip_repository_attrs_bzl",

python/private/pypi/extension.bzl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ You cannot use both the additive_build_content and additive_build_content_file a
356356
simpleapi_cache = simpleapi_cache,
357357
# TODO @aignas 2025-09-06: do not use kwargs
358358
minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING),
359-
evaluate_markers_fn = kwargs.get("evaluate_markers", None),
360359
available_interpreters = kwargs.get("available_interpreters", INTERPRETER_LABELS),
361360
logger = repo_utils.logger(module_ctx, "pypi:hub:" + hub_name, mod = mod),
362361
)

python/private/pypi/hub_builder.bzl

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ load("//python/private:text_util.bzl", "render")
77
load("//python/private:version.bzl", "version")
88
load("//python/private:version_label.bzl", "version_label")
99
load(":attrs.bzl", "use_isolated")
10-
load(":evaluate_markers.bzl", "evaluate_markers")
1110
load(":parse_requirements.bzl", "parse_requirements")
1211
load(":pep508_env.bzl", "env")
1312
load(":pep508_evaluate.bzl", "evaluate")
@@ -29,7 +28,6 @@ def hub_builder(
2928
minor_mapping,
3029
available_interpreters,
3130
simpleapi_download_fn,
32-
evaluate_markers_fn,
3331
logger,
3432
simpleapi_cache):
3533
"""Return a hub builder instance
@@ -40,7 +38,6 @@ def hub_builder(
4038
config: The platform configuration.
4139
whl_overrides: {type}`dict[str, struct]` - per-wheel overrides.
4240
minor_mapping: {type}`dict[str, str]` the mapping between minor and full versions.
43-
evaluate_markers_fn: the override function used to evaluate the markers.
4441
available_interpreters: {type}`dict[str, Label]` The dictionary of available
4542
interpreters that have been registered using the `python` bzlmod extension.
4643
The keys are in the form `python_{snake_case_version}_host`. This is to be
@@ -97,7 +94,6 @@ def hub_builder(
9794
# Instance constants passed in by callers
9895
_config = config,
9996
_whl_overrides = whl_overrides,
100-
_evaluate_markers_fn = evaluate_markers_fn,
10197
_logger = logger,
10298
_minor_mapping = minor_mapping,
10399
_available_interpreters = available_interpreters,
@@ -465,15 +461,6 @@ def _platforms(module_ctx, *, python_version, config, target_platforms):
465461
)
466462
return platforms
467463

468-
def _evaluate_markers(self, pip_attr):
469-
if self._evaluate_markers_fn:
470-
return self._evaluate_markers_fn
471-
472-
return lambda requirements: evaluate_markers(
473-
requirements = requirements,
474-
platforms = self._platforms[pip_attr.python_version],
475-
)
476-
477464
def _create_whl_repos(
478465
self,
479466
module_ctx,
@@ -509,7 +496,6 @@ def _create_whl_repos(
509496
platforms = platforms,
510497
extra_pip_args = pip_attr.extra_pip_args,
511498
get_index_urls = self._get_index_urls.get(pip_attr.python_version),
512-
evaluate_markers = _evaluate_markers(self, pip_attr),
513499
logger = logger,
514500
)
515501

python/private/pypi/parse_requirements.bzl

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ load("//python/private:repo_utils.bzl", "repo_utils")
3131
load(":argparse.bzl", "argparse")
3232
load(":index_sources.bzl", "index_sources")
3333
load(":parse_requirements_txt.bzl", "parse_requirements_txt")
34+
load(":pep508_evaluate.bzl", "evaluate")
3435
load(":pep508_requirement.bzl", "requirement")
3536
load(":select_whl.bzl", "select_whl")
3637

@@ -41,7 +42,6 @@ def parse_requirements(
4142
extra_pip_args = [],
4243
platforms = {},
4344
get_index_urls = None,
44-
evaluate_markers = None,
4545
extract_url_srcs = True,
4646
logger):
4747
"""Get the requirements with platforms that the requirements apply to.
@@ -57,11 +57,6 @@ def parse_requirements(
5757
get_index_urls: Callable[[ctx, dict[str, list[str]]], dict], a callable to get all
5858
of the distribution URLs from a PyPI index. Accepts ctx and
5959
distribution names to query.
60-
evaluate_markers: A function to use to evaluate the requirements.
61-
Accepts a dict where keys are requirement lines to evaluate against
62-
the platforms stored as values in the input dict. Returns the same
63-
dict, but with values being platforms that are compatible with the
64-
requirements line.
6560
extract_url_srcs: A boolean to enable extracting URLs from requirement
6661
lines to enable using bazel downloader.
6762
logger: repo_utils.logger, a simple struct to log diagnostic messages.
@@ -86,11 +81,9 @@ def parse_requirements(
8681
8782
The second element is extra_pip_args should be passed to `whl_library`.
8883
"""
89-
evaluate_markers = evaluate_markers or (lambda _requirements: {})
9084
options = {}
9185
requirements = {}
9286
all_files_parsed = {}
93-
reqs_with_env_markers = {}
9487
index_url = None
9588
extra_index_urls = []
9689
for file, plats in requirements_by_platform.items():
@@ -114,39 +107,31 @@ def parse_requirements(
114107
tokenized_options.append(p)
115108

116109
pip_args = tokenized_options + extra_pip_args
110+
111+
# Parse the index URL from the requirement files once per file
112+
index_url = argparse.index_url(pip_args, index_url)
113+
extra_index_urls = argparse.extra_index_url(pip_args, [])
114+
if argparse.platform(pip_args, []):
115+
# No use of downloader if the user specifies "--platform" pip arg. This means that
116+
# they intend to use pip to download the wheels
117+
#
118+
# TODO @aignas 2026-04-11: consider removing this line in the next major release
119+
# (3.0).
120+
get_index_urls = None
121+
122+
# Pre-parse requirements once per file to avoid redundant parsing in loops
123+
parsed_reqs = [(entry, requirement(entry[1])) for entry in parse_result.requirements]
124+
117125
for plat in plats:
118-
requirements[plat] = parse_result.requirements
119-
for entry in parse_result.requirements:
120-
requirement_line = entry[1]
126+
plat_env = platforms.get(plat)
121127

122-
# output all of the requirement lines that have a marker
123-
if ";" in requirement_line:
124-
reqs_with_env_markers.setdefault(requirement_line, []).append(plat)
125-
options[plat] = pip_args
128+
requirements[plat] = [
129+
entry
130+
for entry, req in parsed_reqs
131+
if not req.marker or (plat_env and evaluate(req.marker, env = plat_env.env))
132+
]
126133

127-
# Parse the index URL from the requirement files
128-
index_url = argparse.index_url(pip_args, index_url)
129-
extra_index_urls = argparse.extra_index_url(pip_args, [])
130-
platform = argparse.platform(pip_args, [])
131-
if platform:
132-
# No use of downloader if the user specifies "--platform" pip arg. This means that
133-
# they intend to use pip to download the wheels
134-
#
135-
# TODO @aignas 2026-04-11: consider removing this line in the next major release
136-
# (3.0).
137-
get_index_urls = None
138-
139-
# This may call to Python, so execute it early (before calling to the
140-
# internet below) and ensure that we call it only once.
141-
#
142-
# TODO @aignas 2026-05-10: remove this assumption in the code because we
143-
# are always using pipstar, so we can do the marker evaluation when we are
144-
# parsing the files.
145-
resolved_marker_platforms = evaluate_markers(reqs_with_env_markers)
146-
logger.trace(lambda: "Evaluated env markers from:\n{}\n\nTo:\n{}".format(
147-
reqs_with_env_markers,
148-
resolved_marker_platforms,
149-
))
134+
options[plat] = pip_args
150135

151136
requirements_by_platform = {}
152137
for plat, parse_results in requirements.items():
@@ -170,9 +155,6 @@ def parse_requirements(
170155
req_line = entry[1]
171156
req = requirement(req_line)
172157

173-
if req.marker and plat not in resolved_marker_platforms.get(req_line, []):
174-
continue
175-
176158
requirements_dict[req.name] = entry
177159

178160
extra_pip_args = options[plat]

python/private/pypi/pip_repository.bzl

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ load("@bazel_skylib//lib:sets.bzl", "sets")
1818
load("//python/private:normalize_name.bzl", "normalize_name")
1919
load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
2020
load("//python/private:text_util.bzl", "render")
21-
load(":evaluate_markers.bzl", "evaluate_markers")
2221
load(":parse_requirements.bzl", "host_platform", "parse_requirements", "select_requirement")
2322
load(":pep508_env.bzl", "env")
2423
load(":pip_repository_attrs.bzl", "ATTRS")
@@ -123,15 +122,14 @@ def _pip_repository_impl(rctx):
123122
platforms = platforms,
124123
),
125124
extra_pip_args = rctx.attr.extra_pip_args,
126-
evaluate_markers = lambda requirements: evaluate_markers(
127-
requirements = {
128-
# NOTE @aignas 2025-07-07: because we don't distinguish between
129-
# freethreaded and non-freethreaded, it is a 1:1 mapping.
130-
req: {p: p for p in plats}
131-
for req, plats in requirements.items()
132-
},
133-
platforms = {p: struct(env = marker_env) for p in platforms},
134-
),
125+
platforms = {
126+
p: struct(
127+
env = marker_env,
128+
whl_abi_tags = [],
129+
whl_platform_tags = [],
130+
)
131+
for p in platforms
132+
},
135133
extract_url_srcs = False,
136134
logger = logger,
137135
)

tests/pypi/extension/pip_parse.bzl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,5 @@ def pip_parse(
6565
parallel_download = False,
6666
experimental_index_url_overrides = {},
6767
simpleapi_skip = simpleapi_skip,
68-
_evaluate_markers_srcs = [],
6968
**kwargs
7069
)

tests/pypi/hub_builder/hub_builder_tests.bzl

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def hub_builder(
4747
config = None,
4848
minor_mapping = {},
4949
whl_overrides = {},
50-
evaluate_markers_fn = None,
5150
simpleapi_download_fn = None,
5251
log_printer = None,
5352
available_interpreters = {}):
@@ -87,7 +86,6 @@ def hub_builder(
8786
"python_3_15_host": "unit_test_interpreter_target",
8887
},
8988
simpleapi_download_fn = simpleapi_download_fn or (lambda *a, **k: {}),
90-
evaluate_markers_fn = evaluate_markers_fn,
9189
logger = repo_utils.logger(
9290
struct(
9391
getenv = {
@@ -441,17 +439,7 @@ def _test_simple_with_markers(env):
441439
("linux", "x86_64"): "torch==2.4.1+cpu",
442440
}
443441
for (host_os, host_arch), want_requirement in sub_tests.items():
444-
builder = hub_builder(
445-
env,
446-
evaluate_markers_fn = lambda requirements: {
447-
key: [
448-
platform
449-
for platform in platforms
450-
if ("x86_64" in platform and "platform_machine ==" in key) or ("x86_64" not in platform and "platform_machine !=" in key)
451-
]
452-
for key, platforms in requirements.items()
453-
},
454-
)
442+
builder = hub_builder(env)
455443
builder.pip_parse(
456444
mocks.mctx(
457445
mock_files = {

tests/pypi/parse_requirements/parse_requirements_tests.bzl

Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
load("@rules_testing//lib:test_suite.bzl", "test_suite")
1818
load("//python/private:repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "REPO_VERBOSITY_ENV_VAR", "repo_utils") # buildifier: disable=bzl-visibility
19-
load("//python/private/pypi:evaluate_markers.bzl", "evaluate_markers") # buildifier: disable=bzl-visibility
2019
load("//python/private/pypi:parse_requirements.bzl", "select_requirement", _parse_requirements = "parse_requirements") # buildifier: disable=bzl-visibility
2120
load("//python/private/pypi:pep508_env.bzl", pep508_env = "env") # buildifier: disable=bzl-visibility
2221
load("//tests/support/mocks:mocks.bzl", "mocks")
@@ -68,7 +67,7 @@ foo==0.0.1 --hash=sha256:deadbeef
6867
foo[extra]==0.0.1 --hash=sha256:deadbeef
6968
""",
7069
"requirements_marker": """\
71-
foo[extra]==0.0.1 ;marker --hash=sha256:deadbeef
70+
foo[extra]==0.0.1 ; os_name == 'nt' --hash=sha256:deadbeef
7271
bar==0.0.1 --hash=sha256:deadbeef
7372
""",
7473
"requirements_multi_version": """\
@@ -441,22 +440,24 @@ def _test_select_requirement_none_platform(env):
441440
_tests.append(_test_select_requirement_none_platform)
442441

443442
def _test_env_marker_resolution(env):
444-
"""Test environment marker resolution with ``evaluate_markers``."""
445-
446-
def _mock_eval_markers(input):
447-
ret = {
448-
"foo[extra]==0.0.1 ;marker --hash=sha256:deadbeef": ["cp311_windows_x86_64"],
449-
}
450-
451-
env.expect.that_collection(input.keys()).contains_exactly(ret.keys())
452-
env.expect.that_collection(input.values()[0]).contains_exactly(["cp311_linux_super_exotic", "cp311_windows_x86_64"])
453-
return ret
443+
"""Test environment marker resolution with platform env information."""
454444

455445
got = parse_requirements(
456446
requirements_by_platform = {
457447
"requirements_marker": ["cp311_linux_super_exotic", "cp311_windows_x86_64"],
458448
},
459-
evaluate_markers = _mock_eval_markers,
449+
platforms = {
450+
"cp311_linux_super_exotic": struct(
451+
env = pep508_env(os = "linux", arch = "x86_64", python_version = "3.11.0"),
452+
whl_abi_tags = [],
453+
whl_platform_tags = [],
454+
),
455+
"cp311_windows_x86_64": struct(
456+
env = pep508_env(os = "windows", arch = "x86_64", python_version = "3.11.0"),
457+
whl_abi_tags = [],
458+
whl_platform_tags = [],
459+
),
460+
},
460461
)
461462
env.expect.that_collection(got).contains_exactly([
462463
struct(
@@ -797,17 +798,6 @@ def _test_get_index_urls_different_versions(env):
797798
},
798799
),
799800
},
800-
evaluate_markers = lambda requirements: evaluate_markers(
801-
requirements = requirements,
802-
platforms = {
803-
"cp310_linux_x86_64": struct(
804-
env = {"python_full_version": "3.10.0"},
805-
),
806-
"cp39_linux_x86_64": struct(
807-
env = {"python_full_version": "3.9.0"},
808-
),
809-
},
810-
),
811801
)
812802

813803
env.expect.that_collection(got).contains_exactly([
@@ -891,14 +881,6 @@ def _test_get_index_urls_cross_platform(env):
891881
),
892882
},
893883
get_index_urls = _get_index_urls,
894-
evaluate_markers = lambda requirements: evaluate_markers(
895-
requirements = requirements,
896-
platforms = {
897-
"cp39_osx_x86_64": struct(
898-
env = {"python_full_version": "3.9.0"},
899-
),
900-
},
901-
),
902884
)
903885

904886
# distributions must include packages from ALL files, even those with
@@ -947,14 +929,6 @@ def _test_get_index_urls_single_py_version(env):
947929
},
948930
),
949931
},
950-
evaluate_markers = lambda requirements: evaluate_markers(
951-
requirements = requirements,
952-
platforms = {
953-
"cp310_linux_x86_64": struct(
954-
env = {"python_full_version": "3.10.0"},
955-
),
956-
},
957-
),
958932
)
959933

960934
env.expect.that_collection(got).contains_exactly([
@@ -1004,14 +978,6 @@ def _test_get_index_urls_all_versions(env):
1004978
),
1005979
},
1006980
get_index_urls = _get_index_urls,
1007-
evaluate_markers = lambda requirements: evaluate_markers(
1008-
requirements = requirements,
1009-
platforms = {
1010-
"cp39_linux_x86_64": struct(
1011-
env = {"python_full_version": "3.9.0"},
1012-
),
1013-
},
1014-
),
1015981
)
1016982

1017983
env.expect.that_collection(calls).contains_exactly([

0 commit comments

Comments
 (0)