feat(pypi): pip.defaults API for customizing pipstar 1/n (#2987)
Parse env markers in pip.parse using starlark
Summary:
- Allow switching to the Starlark implementation of the marker
evaluation function.
- Add a way for users to modify the `env` for the marker evaluation when
parsing the requirements. This can only be done by `rules_python` or
the root module.
- Limit the platform selection when parsing the requirements files.
Work towards #2747
Work towards #2949
Split out from #2909
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf3d25c..9897dc9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,7 +62,9 @@
{#v0-0-0-added}
### Added
-* Nothing added.
+* (pypi) To configure the environment for `requirements.txt` evaluation, use the newly added
+ developer preview of the `pip.default` tag class. Only `rules_python` and root modules can use
+ this feature.
{#v0-0-0-removed}
### Removed
diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel
index d89dc6c..b569b22 100644
--- a/python/private/pypi/BUILD.bazel
+++ b/python/private/pypi/BUILD.bazel
@@ -97,10 +97,10 @@
name = "evaluate_markers_bzl",
srcs = ["evaluate_markers.bzl"],
deps = [
- ":pep508_env_bzl",
+ ":deps_bzl",
":pep508_evaluate_bzl",
- ":pep508_platform_bzl",
":pep508_requirement_bzl",
+ ":pypi_repo_utils_bzl",
],
)
@@ -113,6 +113,7 @@
":hub_repository_bzl",
":parse_requirements_bzl",
":parse_whl_name_bzl",
+ ":pep508_env_bzl",
":pip_repository_attrs_bzl",
":simpleapi_download_bzl",
":whl_config_setting_bzl",
diff --git a/python/private/pypi/env_marker_info.bzl b/python/private/pypi/env_marker_info.bzl
index c3c5ec6..37eefb2 100644
--- a/python/private/pypi/env_marker_info.bzl
+++ b/python/private/pypi/env_marker_info.bzl
@@ -17,7 +17,7 @@
The values to use for environment markers when evaluating an expression.
The keys and values should be compatible with the [PyPA dependency specifiers
-specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/)
+specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/).
Missing values will be set to the specification's defaults or computed using
available toolchain information.
diff --git a/python/private/pypi/evaluate_markers.bzl b/python/private/pypi/evaluate_markers.bzl
index 1919335..58a29a9 100644
--- a/python/private/pypi/evaluate_markers.bzl
+++ b/python/private/pypi/evaluate_markers.bzl
@@ -15,9 +15,7 @@
"""A simple function that evaluates markers using a python interpreter."""
load(":deps.bzl", "record_files")
-load(":pep508_env.bzl", "env")
load(":pep508_evaluate.bzl", "evaluate")
-load(":pep508_platform.bzl", "platform_from_str")
load(":pep508_requirement.bzl", "requirement")
load(":pypi_repo_utils.bzl", "pypi_repo_utils")
@@ -30,22 +28,27 @@
Label("//python/private/pypi/whl_installer:platform.py"),
]
-def evaluate_markers(requirements, python_version = None):
+def evaluate_markers(*, requirements, platforms):
"""Return the list of supported platforms per requirements line.
Args:
requirements: {type}`dict[str, list[str]]` of the requirement file lines to evaluate.
- python_version: {type}`str | None` the version that can be used when evaluating the markers.
+ platforms: {type}`dict[str, dict[str, str]]` The environments that we for each requirement
+ file to evaluate. The keys between the platforms and requirements should be shared.
Returns:
dict of string lists with target platforms
"""
ret = {}
- for req_string, platforms in requirements.items():
+ for req_string, platform_strings in requirements.items():
req = requirement(req_string)
- for platform in platforms:
- if evaluate(req.marker, env = env(platform_from_str(platform, python_version))):
- ret.setdefault(req_string, []).append(platform)
+ for platform_str in platform_strings:
+ env = platforms.get(platform_str)
+ if not env:
+ fail("Please define platform: '{}'".format(platform_str))
+
+ if evaluate(req.marker, env = env):
+ ret.setdefault(req_string, []).append(platform_str)
return ret
diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl
index 867abe0..97b6825 100644
--- a/python/private/pypi/extension.bzl
+++ b/python/private/pypi/extension.bzl
@@ -25,10 +25,11 @@
load("//python/private:version.bzl", "version")
load("//python/private:version_label.bzl", "version_label")
load(":attrs.bzl", "use_isolated")
-load(":evaluate_markers.bzl", "evaluate_markers_py", EVALUATE_MARKERS_SRCS = "SRCS")
+load(":evaluate_markers.bzl", "evaluate_markers_py", EVALUATE_MARKERS_SRCS = "SRCS", evaluate_markers_star = "evaluate_markers")
load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json")
load(":parse_requirements.bzl", "parse_requirements")
load(":parse_whl_name.bzl", "parse_whl_name")
+load(":pep508_env.bzl", "env")
load(":pip_repository_attrs.bzl", "ATTRS")
load(":requirements_files_by_platform.bzl", "requirements_files_by_platform")
load(":simpleapi_download.bzl", "simpleapi_download")
@@ -65,22 +66,36 @@
whl_mods = whl_mods,
)
+def _platforms(*, python_version, minor_mapping, config):
+ platforms = {}
+ python_version = full_version(
+ version = python_version,
+ minor_mapping = minor_mapping,
+ )
+ abi = "cp3{}".format(python_version[2:])
+
+ for platform, values in config.platforms.items():
+ key = "{}_{}".format(abi, platform)
+ platforms[key] = env(key) | values.env
+ return platforms
+
def _create_whl_repos(
module_ctx,
*,
pip_attr,
whl_overrides,
+ config,
available_interpreters = INTERPRETER_LABELS,
minor_mapping = MINOR_MAPPING,
- evaluate_markers = evaluate_markers_py,
- get_index_urls = None,
- enable_pipstar = False):
+ evaluate_markers = None,
+ get_index_urls = None):
"""create all of the whl repositories
Args:
module_ctx: {type}`module_ctx`.
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
whl_overrides: {type}`dict[str, struct]` - per-wheel overrides.
+ config: The platform configuration.
get_index_urls: A function used to get the index URLs
available_interpreters: {type}`dict[str, Label]` The dictionary of available
interpreters that have been registered using the `python` bzlmod extension.
@@ -89,7 +104,6 @@
minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full
python version used to parse package METADATA files.
evaluate_markers: the function used to evaluate the markers.
- enable_pipstar: enable the pipstar feature.
Returns a {type}`struct` with the following attributes:
whl_map: {type}`dict[str, list[struct]]` the output is keyed by the
@@ -160,23 +174,19 @@
whl_group_mapping = {}
requirement_cycles = {}
- requirements_by_platform = parse_requirements(
- module_ctx,
- requirements_by_platform = requirements_files_by_platform(
- requirements_by_platform = pip_attr.requirements_by_platform,
- requirements_linux = pip_attr.requirements_linux,
- requirements_lock = pip_attr.requirements_lock,
- requirements_osx = pip_attr.requirements_darwin,
- requirements_windows = pip_attr.requirements_windows,
- extra_pip_args = pip_attr.extra_pip_args,
- python_version = full_version(
- version = pip_attr.python_version,
+ if evaluate_markers:
+ # This is most likely unit tests
+ pass
+ elif config.enable_pipstar:
+ evaluate_markers = lambda _, requirements: evaluate_markers_star(
+ requirements = requirements,
+ platforms = _platforms(
+ python_version = pip_attr.python_version,
minor_mapping = minor_mapping,
+ config = config,
),
- logger = logger,
- ),
- extra_pip_args = pip_attr.extra_pip_args,
- get_index_urls = get_index_urls,
+ )
+ else:
# NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either
# in the PATH or if specified as a label. We will configure the env
# markers when evaluating the requirement lines based on the output
@@ -191,14 +201,34 @@
# instances to perform this manipulation. This function should be executed
# only once by the underlying code to minimize the overhead needed to
# spin up a Python interpreter.
- evaluate_markers = lambda module_ctx, requirements: evaluate_markers(
+ evaluate_markers = lambda module_ctx, requirements: evaluate_markers_py(
module_ctx,
requirements = requirements,
python_interpreter = pip_attr.python_interpreter,
python_interpreter_target = python_interpreter_target,
srcs = pip_attr._evaluate_markers_srcs,
logger = logger,
+ )
+
+ requirements_by_platform = parse_requirements(
+ module_ctx,
+ requirements_by_platform = requirements_files_by_platform(
+ requirements_by_platform = pip_attr.requirements_by_platform,
+ requirements_linux = pip_attr.requirements_linux,
+ requirements_lock = pip_attr.requirements_lock,
+ requirements_osx = pip_attr.requirements_darwin,
+ requirements_windows = pip_attr.requirements_windows,
+ extra_pip_args = pip_attr.extra_pip_args,
+ platforms = sorted(config.platforms), # here we only need keys
+ python_version = full_version(
+ version = pip_attr.python_version,
+ minor_mapping = minor_mapping,
+ ),
+ logger = logger,
),
+ extra_pip_args = pip_attr.extra_pip_args,
+ get_index_urls = get_index_urls,
+ evaluate_markers = evaluate_markers,
logger = logger,
)
@@ -233,7 +263,7 @@
for p, args in whl_overrides.get(whl.name, {}).items()
},
)
- if not enable_pipstar:
+ if not config.enable_pipstar:
maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms
whl_library_args.update({k: v for k, v in maybe_args.items() if v})
@@ -258,7 +288,7 @@
auth_patterns = pip_attr.auth_patterns,
python_version = major_minor,
is_multiple_versions = whl.is_multiple_versions,
- enable_pipstar = enable_pipstar,
+ enable_pipstar = config.enable_pipstar,
)
repo_name = "{}_{}".format(pip_name, repo.repo_name)
@@ -342,16 +372,85 @@
),
)
+def _configure(config, *, platform, os_name, arch_name, override = False, env = {}):
+ """Set the value in the config if the value is provided"""
+ config.setdefault("platforms", {})
+ if platform:
+ if not override and config.get("platforms", {}).get(platform):
+ return
+
+ for key in env:
+ if key not in _SUPPORTED_PEP508_KEYS:
+ fail("Unsupported key in the PEP508 environment: {}".format(key))
+
+ config["platforms"][platform] = struct(
+ name = platform.replace("-", "_").lower(),
+ os_name = os_name,
+ arch_name = arch_name,
+ env = env,
+ )
+ else:
+ config["platforms"].pop(platform)
+
+def _create_config(defaults):
+ if defaults["platforms"]:
+ return struct(**defaults)
+
+ # NOTE: We have this so that it is easier to maintain unit tests assuming certain
+ # defaults
+ for cpu in [
+ "x86_64",
+ "aarch64",
+ # TODO @aignas 2025-05-19: only leave tier 0-1 cpus when stabilizing the
+ # `pip.default` extension. i.e. drop the below values - users will have to
+ # define themselves if they need them.
+ "arm",
+ "ppc",
+ "s390x",
+ ]:
+ _configure(
+ defaults,
+ arch_name = cpu,
+ os_name = "linux",
+ platform = "linux_{}".format(cpu),
+ env = {"platform_version": "0"},
+ )
+ for cpu in [
+ "aarch64",
+ "x86_64",
+ ]:
+ _configure(
+ defaults,
+ arch_name = cpu,
+ # We choose the oldest non-EOL version at the time when we release `rules_python`.
+ # See https://endoflife.date/macos
+ env = {"platform_version": "14.0"},
+ os_name = "osx",
+ platform = "osx_{}".format(cpu),
+ )
+
+ _configure(
+ defaults,
+ arch_name = "x86_64",
+ env = {"platform_version": "0"},
+ os_name = "windows",
+ platform = "windows_x86_64",
+ )
+ return struct(**defaults)
+
def parse_modules(
module_ctx,
_fail = fail,
simpleapi_download = simpleapi_download,
+ enable_pipstar = False,
**kwargs):
"""Implementation of parsing the tag classes for the extension and return a struct for registering repositories.
Args:
module_ctx: {type}`module_ctx` module context.
simpleapi_download: Used for testing overrides
+ enable_pipstar: {type}`bool` a flag to enable dropping Python dependency for
+ evaluation of the extension.
_fail: {type}`function` the failure function, mainly for testing.
**kwargs: Extra arguments passed to the layers below.
@@ -389,6 +488,34 @@
srcs_exclude_glob = whl_mod.srcs_exclude_glob,
)
+ defaults = {
+ "enable_pipstar": enable_pipstar,
+ "platforms": {},
+ }
+ for mod in module_ctx.modules:
+ if not (mod.is_root or mod.name == "rules_python"):
+ continue
+
+ for tag in mod.tags.default:
+ _configure(
+ defaults,
+ arch_name = tag.arch_name,
+ env = tag.env,
+ os_name = tag.os_name,
+ platform = tag.platform,
+ override = mod.is_root,
+ # TODO @aignas 2025-05-19: add more attr groups:
+ # * for AUTH - the default `netrc` usage could be configured through a common
+ # attribute.
+ # * for index/downloader config. This includes all of those attributes for
+ # overrides, etc. Index overrides per platform could be also used here.
+ # * for whl selection - selecting preferences of which `platform_tag`s we should use
+ # for what. We could also model the `cp313t` freethreaded as separate platforms.
+ )
+
+ config = _create_config(defaults)
+
+ # TODO @aignas 2025-06-03: Merge override API with the builder?
_overriden_whl_set = {}
whl_overrides = {}
for module in module_ctx.modules:
@@ -498,11 +625,13 @@
elif pip_attr.experimental_index_url_overrides:
fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set")
+ # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls
out = _create_whl_repos(
module_ctx,
pip_attr = pip_attr,
get_index_urls = get_index_urls,
whl_overrides = whl_overrides,
+ config = config,
**kwargs
)
hub_whl_map.setdefault(hub_name, {})
@@ -651,6 +780,72 @@
else:
return None
+_default_attrs = {
+ "arch_name": attr.string(
+ doc = """\
+The CPU architecture name to be used.
+
+:::{note}
+Either this or {attr}`env` `platform_machine` key should be specified.
+:::
+""",
+ ),
+ "os_name": attr.string(
+ doc = """\
+The OS name to be used.
+
+:::{note}
+Either this or the appropriate `env` keys should be specified.
+:::
+""",
+ ),
+ "platform": attr.string(
+ doc = """\
+A platform identifier which will be used as the unique identifier within the extension evaluation.
+If you are defining custom platforms in your project and don't want things to clash, use extension
+[isolation] feature.
+
+[isolation]: https://bazel.build/rules/lib/globals/module#use_extension.isolate
+""",
+ ),
+} | {
+ "env": attr.string_dict(
+ doc = """\
+The values to use for environment markers when evaluating an expression.
+
+The keys and values should be compatible with the [PyPA dependency specifiers
+specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/).
+
+Missing values will be set to the specification's defaults or computed using
+available toolchain information.
+
+Supported keys:
+* `implementation_name`, defaults to `cpython`.
+* `os_name`, defaults to a value inferred from the {attr}`os_name`.
+* `platform_machine`, defaults to a value inferred from the {attr}`arch_name`.
+* `platform_release`, defaults to an empty value.
+* `platform_system`, defaults to a value inferred from the {attr}`os_name`.
+* `platform_version`, defaults to `0`.
+* `sys_platform`, defaults to a value inferred from the {attr}`os_name`.
+
+::::{note}
+This is only used if the {envvar}`RULES_PYTHON_ENABLE_PIPSTAR` is enabled.
+::::
+""",
+ ),
+ # The values for PEP508 env marker evaluation during the lock file parsing
+}
+
+_SUPPORTED_PEP508_KEYS = [
+ "implementation_name",
+ "os_name",
+ "platform_machine",
+ "platform_release",
+ "platform_system",
+ "platform_version",
+ "sys_platform",
+]
+
def _pip_parse_ext_attrs(**kwargs):
"""Get the attributes for the pip extension.
@@ -907,6 +1102,23 @@
""",
implementation = _pip_impl,
tag_classes = {
+ "default": tag_class(
+ attrs = _default_attrs,
+ doc = """\
+This tag class allows for more customization of how the configuration for the hub repositories is built.
+
+
+:::{include} /_includes/experimtal_api.md
+:::
+
+:::{seealso}
+The [environment markers][environment_markers] specification for the explanation of the
+terms used in this extension.
+
+[environment_markers]: https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers
+:::
+""",
+ ),
"override": _override_tag,
"parse": tag_class(
attrs = _pip_parse_ext_attrs(),
diff --git a/python/private/pypi/pep508_evaluate.bzl b/python/private/pypi/pep508_evaluate.bzl
index d4492a7..fe2cac9 100644
--- a/python/private/pypi/pep508_evaluate.bzl
+++ b/python/private/pypi/pep508_evaluate.bzl
@@ -117,7 +117,7 @@
Args:
marker: {type}`str` The string marker to evaluate.
- env: {type}`dict` The environment to evaluate the marker against.
+ env: {type}`dict[str, str]` The environment to evaluate the marker against.
strict: {type}`bool` A setting to not fail on missing values in the env.
**kwargs: Extra kwargs to be passed to the expression evaluator.
diff --git a/python/private/pypi/pip_repository.bzl b/python/private/pypi/pip_repository.bzl
index 724fb6d..e63bd6c 100644
--- a/python/private/pypi/pip_repository.bzl
+++ b/python/private/pypi/pip_repository.bzl
@@ -80,6 +80,16 @@
requirements_osx = rctx.attr.requirements_darwin,
requirements_windows = rctx.attr.requirements_windows,
extra_pip_args = rctx.attr.extra_pip_args,
+ platforms = [
+ "linux_aarch64",
+ "linux_arm",
+ "linux_ppc",
+ "linux_s390x",
+ "linux_x86_64",
+ "osx_aarch64",
+ "osx_x86_64",
+ "windows_x86_64",
+ ],
),
extra_pip_args = rctx.attr.extra_pip_args,
evaluate_markers = lambda rctx, requirements: evaluate_markers_py(
diff --git a/python/private/pypi/requirements_files_by_platform.bzl b/python/private/pypi/requirements_files_by_platform.bzl
index 9165c05..d8d3651 100644
--- a/python/private/pypi/requirements_files_by_platform.bzl
+++ b/python/private/pypi/requirements_files_by_platform.bzl
@@ -16,20 +16,7 @@
load(":whl_target_platforms.bzl", "whl_target_platforms")
-# TODO @aignas 2024-05-13: consider using the same platform tags as are used in
-# the //python:versions.bzl
-DEFAULT_PLATFORMS = [
- "linux_aarch64",
- "linux_arm",
- "linux_ppc",
- "linux_s390x",
- "linux_x86_64",
- "osx_aarch64",
- "osx_x86_64",
- "windows_x86_64",
-]
-
-def _default_platforms(*, filter):
+def _default_platforms(*, filter, platforms):
if not filter:
fail("Must specific a filter string, got: {}".format(filter))
@@ -48,11 +35,13 @@
fail("The filter can only contain '*' at the end of it")
if not prefix:
- return DEFAULT_PLATFORMS
+ return platforms
- return [p for p in DEFAULT_PLATFORMS if p.startswith(prefix)]
+ match = [p for p in platforms if p.startswith(prefix)]
else:
- return [p for p in DEFAULT_PLATFORMS if filter in p]
+ match = [p for p in platforms if filter in p]
+
+ return match
def _platforms_from_args(extra_pip_args):
platform_values = []
@@ -105,6 +94,7 @@
requirements_linux = None,
requirements_lock = None,
requirements_windows = None,
+ platforms,
extra_pip_args = None,
python_version = None,
logger = None,
@@ -123,6 +113,8 @@
be joined with args fined in files.
python_version: str or None. This is needed when the get_index_urls is
specified. It should be of the form "3.x.x",
+ platforms: {type}`list[str]` the list of human-friendly platform labels that should
+ be used for the evaluation.
logger: repo_utils.logger or None, a simple struct to log diagnostic messages.
fail_fn (Callable[[str], None]): A failure function used in testing failure cases.
@@ -144,11 +136,13 @@
)
return None
- platforms = _platforms_from_args(extra_pip_args)
+ platforms_from_args = _platforms_from_args(extra_pip_args)
if logger:
- logger.debug(lambda: "Platforms from pip args: {}".format(platforms))
+ logger.debug(lambda: "Platforms from pip args: {}".format(platforms_from_args))
- if platforms:
+ default_platforms = [_platform(p, python_version) for p in platforms]
+
+ if platforms_from_args:
lock_files = [
f
for f in [
@@ -168,7 +162,7 @@
return None
files_by_platform = [
- (lock_files[0], platforms),
+ (lock_files[0], platforms_from_args),
]
if logger:
logger.debug(lambda: "Files by platform with the platform set in the args: {}".format(files_by_platform))
@@ -177,7 +171,7 @@
file: [
platform
for filter_or_platform in specifier.split(",")
- for platform in (_default_platforms(filter = filter_or_platform) if filter_or_platform.endswith("*") else [filter_or_platform])
+ for platform in (_default_platforms(filter = filter_or_platform, platforms = platforms) if filter_or_platform.endswith("*") else [filter_or_platform])
]
for file, specifier in requirements_by_platform.items()
}.items()
@@ -188,9 +182,9 @@
for f in [
# If the users need a greater span of the platforms, they should consider
# using the 'requirements_by_platform' attribute.
- (requirements_linux, _default_platforms(filter = "linux_*")),
- (requirements_osx, _default_platforms(filter = "osx_*")),
- (requirements_windows, _default_platforms(filter = "windows_*")),
+ (requirements_linux, _default_platforms(filter = "linux_*", platforms = platforms)),
+ (requirements_osx, _default_platforms(filter = "osx_*", platforms = platforms)),
+ (requirements_windows, _default_platforms(filter = "windows_*", platforms = platforms)),
(requirements_lock, None),
]:
if f[0]:
@@ -215,8 +209,7 @@
return None
configured_platforms[p] = file
- else:
- default_platforms = [_platform(p, python_version) for p in DEFAULT_PLATFORMS]
+ elif plats == None:
plats = [
p
for p in default_platforms
@@ -231,6 +224,13 @@
for p in plats:
configured_platforms[p] = file
+ elif logger:
+ logger.warn(lambda: "File {} will be ignored because there are no configured platforms: {}".format(
+ file,
+ default_platforms,
+ ))
+ continue
+
if logger:
logger.debug(lambda: "Configured platforms for file {} are {}".format(file, plats))
diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl
index 8e32572..3d205a2 100644
--- a/tests/pypi/extension/extension_tests.bzl
+++ b/tests/pypi/extension/extension_tests.bzl
@@ -49,23 +49,22 @@
],
)
-def _mod(*, name, parse = [], override = [], whl_mods = [], is_root = True):
+def _mod(*, name, default = [], parse = [], override = [], whl_mods = [], is_root = True):
return struct(
name = name,
tags = struct(
parse = parse,
override = override,
whl_mods = whl_mods,
+ default = default,
),
is_root = is_root,
)
-def _parse_modules(env, **kwargs):
+def _parse_modules(env, enable_pipstar = 0, **kwargs):
return env.expect.that_struct(
parse_modules(
- # TODO @aignas 2025-05-11: start integration testing the branch which
- # includes this.
- enable_pipstar = 0,
+ enable_pipstar = enable_pipstar,
**kwargs
),
attrs = dict(
@@ -77,6 +76,26 @@
),
)
+def _default(
+ arch_name = None,
+ constraint_values = None,
+ os_name = None,
+ platform = None,
+ target_settings = None,
+ env = None,
+ whl_limit = None,
+ whl_platforms = None):
+ return struct(
+ arch_name = arch_name,
+ constraint_values = constraint_values,
+ os_name = os_name,
+ platform = platform,
+ target_settings = target_settings,
+ env = env or {},
+ whl_platforms = whl_platforms,
+ whl_limit = whl_limit,
+ )
+
def _parse(
*,
hub_name,
@@ -1023,6 +1042,88 @@
_tests.append(_test_optimum_sys_platform_extra)
+def _test_pipstar_platforms(env):
+ pypi = _parse_modules(
+ env,
+ module_ctx = _mock_mctx(
+ _mod(
+ name = "rules_python",
+ default = [
+ _default(
+ platform = "{}_{}".format(os, cpu),
+ )
+ for os, cpu in [
+ ("linux", "x86_64"),
+ ("osx", "aarch64"),
+ ]
+ ],
+ parse = [
+ _parse(
+ hub_name = "pypi",
+ python_version = "3.15",
+ requirements_lock = "universal.txt",
+ ),
+ ],
+ ),
+ read = lambda x: {
+ "universal.txt": """\
+optimum[onnxruntime]==1.17.1 ; sys_platform == 'darwin'
+optimum[onnxruntime-gpu]==1.17.1 ; sys_platform == 'linux'
+""",
+ }[x],
+ ),
+ enable_pipstar = True,
+ available_interpreters = {
+ "python_3_15_host": "unit_test_interpreter_target",
+ },
+ minor_mapping = {"3.15": "3.15.19"},
+ )
+
+ pypi.exposed_packages().contains_exactly({"pypi": ["optimum"]})
+ pypi.hub_group_map().contains_exactly({"pypi": {}})
+ pypi.hub_whl_map().contains_exactly({
+ "pypi": {
+ "optimum": {
+ "pypi_315_optimum_linux_x86_64": [
+ whl_config_setting(
+ version = "3.15",
+ target_platforms = [
+ "cp315_linux_x86_64",
+ ],
+ config_setting = None,
+ filename = None,
+ ),
+ ],
+ "pypi_315_optimum_osx_aarch64": [
+ whl_config_setting(
+ version = "3.15",
+ target_platforms = [
+ "cp315_osx_aarch64",
+ ],
+ config_setting = None,
+ filename = None,
+ ),
+ ],
+ },
+ },
+ })
+
+ pypi.whl_libraries().contains_exactly({
+ "pypi_315_optimum_linux_x86_64": {
+ "dep_template": "@pypi//{name}:{target}",
+ "python_interpreter_target": "unit_test_interpreter_target",
+ "requirement": "optimum[onnxruntime-gpu]==1.17.1",
+ },
+ "pypi_315_optimum_osx_aarch64": {
+ "dep_template": "@pypi//{name}:{target}",
+ "python_interpreter_target": "unit_test_interpreter_target",
+ "requirement": "optimum[onnxruntime]==1.17.1",
+ },
+ })
+ pypi.whl_mods().contains_exactly({})
+
+_tests.append(_test_pipstar_platforms)
+
def extension_test_suite(name):
"""Create the test suite.
diff --git a/tests/pypi/requirements_files_by_platform/requirements_files_by_platform_tests.bzl b/tests/pypi/requirements_files_by_platform/requirements_files_by_platform_tests.bzl
index b729b0e..6688d72 100644
--- a/tests/pypi/requirements_files_by_platform/requirements_files_by_platform_tests.bzl
+++ b/tests/pypi/requirements_files_by_platform/requirements_files_by_platform_tests.bzl
@@ -15,10 +15,27 @@
""
load("@rules_testing//lib:test_suite.bzl", "test_suite")
-load("//python/private/pypi:requirements_files_by_platform.bzl", "requirements_files_by_platform") # buildifier: disable=bzl-visibility
+load("//python/private/pypi:requirements_files_by_platform.bzl", _sut = "requirements_files_by_platform") # buildifier: disable=bzl-visibility
_tests = []
+requirements_files_by_platform = lambda **kwargs: _sut(
+ platforms = kwargs.pop(
+ "platforms",
+ [
+ "linux_aarch64",
+ "linux_arm",
+ "linux_ppc",
+ "linux_s390x",
+ "linux_x86_64",
+ "osx_aarch64",
+ "osx_x86_64",
+ "windows_x86_64",
+ ],
+ ),
+ **kwargs
+)
+
def _test_fail_no_requirements(env):
errors = []
requirements_files_by_platform(
@@ -86,6 +103,28 @@
_tests.append(_test_simple)
+def _test_simple_limited(env):
+ for got in [
+ requirements_files_by_platform(
+ requirements_lock = "requirements_lock",
+ platforms = ["linux_x86_64", "osx_x86_64"],
+ ),
+ requirements_files_by_platform(
+ requirements_by_platform = {
+ "requirements_lock": "*",
+ },
+ platforms = ["linux_x86_64", "osx_x86_64"],
+ ),
+ ]:
+ env.expect.that_dict(got).contains_exactly({
+ "requirements_lock": [
+ "linux_x86_64",
+ "osx_x86_64",
+ ],
+ })
+
+_tests.append(_test_simple_limited)
+
def _test_simple_with_python_version(env):
for got in [
requirements_files_by_platform(