8000 fix(pypi): finish PEP508/PEP440 impl for version matching (#2856) · kaycebasques/rules_python@efc7589 · GitHub
[go: up one dir, main page]

Skip to content

Commit efc7589

Browse files
aignasrickeylev
andauthored
fix(pypi): finish PEP508/PEP440 impl for version matching (bazel-contrib#2856)
This reuses the previous work by @vonschultz who implemented a PEP440 version normalizer. We extend it and use it in the PEP508 marker evaluation. Summary: - Extend the normalization parser to output individual parts of the versions to the parsing context. - Re-implement all of the version comparison calls to use the parsed version. - Add extra validation for `.*` usage in the environment markers - Fallback to non-version matching in the environment markers if one of the sides is not a version. - Rename the original normalizer file to `version.bzl` because as far as Python is concerned this is the only version that there can be. We could in theory probably reuse this in other code where we are parsing the Python interpreter version many times, but this is left for the future. Fixes bazel-contrib#2826 Work towards bazel-contrib#2821 --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com> Co-authored-by: Richard Levasseur <rlevasseur@google.com>
1 parent a2ff7da commit efc7589

File tree

14 files changed

+641
-257
lines changed

14 files changed

+641
-257
lines changed

.bazelrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# (Note, we cannot use `com 10000 mon --deleted_packages` because the bazel version command doesn't support it)
55
# To update these lines, execute
66
# `bazel run @rules_bazel_integration_test//tools:update_deleted_packages`
7-
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
8-
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
7+
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
8+
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
99

1010
test --test_output=errors
1111

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ END_UNRELEASED_TEMPLATE
9494
(the default), the subprocess's stdout/stderr will be logged.
9595
* (toolchains) Local toolchains can be activated with custom flags. See
9696
[Conditionally using local toolchains] docs for how to configure.
97-
* (pypi) `RULES_PYTHON_ENABLE_PIPSTAR` environment variable: when `1`, the Starlark
98-
implementation of wheel METADATA parsing is used (which has improved multi-platform
99-
build support).
97+
* (pypi) Starlark-based evaluation of environment markers (requirements.txt conditionals)
98+
available (not enabled by default) for improved multi-platform build support.
99+
Set the `RULES_PYTHON_ENABLE_PIPSTAR=1` environment variable to enable it.
100100

101101
{#v0-0-0-removed}
102102
### Removed

python/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ bzl_library(
9393
"//python/private:bzlmod_enabled_bzl",
9494
"//python/private:py_package.bzl",
9595
"//python/private:py_wheel_bzl",
96-
"//python/private:py_wheel_normalize_pep440.bzl",
9796
"//python/private:stamp_bzl",
9897
"//python/private:util_bzl",
98+
"//python/private:version.bzl",
9999
"@bazel_skylib//rules:native_binary",
100100
],
101101
)

python/private/BUILD.bazel

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,11 @@ bzl_library(
658658
],
659659
)
660660

661+
bzl_library(
662+
name = "version_bzl",
663+
srcs = ["version.bzl"],
664+
)
665+
661666
bzl_library(
662667
name = "version_label_bzl",
663668
srcs = ["version_label.bzl"],
@@ -701,7 +706,7 @@ exports_files(
701706
"repack_whl.py",
702707
"py_package.bzl",
703708
"py_wheel.bzl",
704-
"py_wheel_normalize_pep440.bzl",
709+
"version.bzl",
705710
"reexports.bzl",
706711
"stamp.bzl",
707712
"util.bzl",

python/private/py_wheel.bzl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
load(":py_info.bzl", "PyInfo")
1818
load(":py_package.bzl", "py_package_lib")
19-
load(":py_wheel_normalize_pep440.bzl", "normalize_pep440")
2019
load(":stamp.bzl", "is_stamping_enabled")
20+
load(":version.bzl", "version")
2121

2222
PyWheelInfo = provider(
2323
doc = "Information about a wheel produced by `py_wheel`",
@@ -306,11 +306,11 @@ def _input_file_to_arg(input_file):
306306
def _py_wheel_impl(ctx):
307307
abi = _replace_make_variables(ctx.attr.abi, ctx)
308308
python_tag = _replace_make_variables(ctx.attr.python_tag, ctx)
309-
version = _replace_make_variables(ctx.attr.version, ctx)
309+ F41A
version_str = _replace_make_variables(ctx.attr.version, ctx)
310310

311311
filename_segments = [
312312
_escape_filename_distribution_name(ctx.attr.distribution),
313-
normalize_pep440(version),
313+
version.normalize(version_str),
314314
_escape_filename_segment(python_tag),
315315
_escape_filename_segment(abi),
316316
_escape_filename_segment(ctx.attr.platform),
@@ -343,7 +343,7 @@ def _py_wheel_impl(ctx):
343343

344344
args = ctx.actions.args()
345345
args.add("--name", ctx.attr.distribution)
346-
args.add("--version", version)
346+
args.add("--version", version_str)
347347
args.add("--python_tag", python_tag)
348348
args.add("--abi", abi)
349349
args.add("--platform", ctx.attr.platform)

python/private/pypi/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ bzl_library(
251251
srcs = ["pep508_env.bzl"],
252252
deps = [
253253
":pep508_platform_bzl",
254+
"//python/private:version_bzl",
254255
],
255256
)
256257

python/private/pypi/pep508_evaluate.bzl

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,11 @@
1616
"""
1717

1818
load("//python/private:enum.bzl", "enum")
19-
load("//python/private:semver.bzl", "semver")
19+
load("//python/private:version.bzl", "version")
2020

2121
# The expression parsing and resolution for the PEP508 is below
2222
#
2323

24-
# Taken from
25-
# https://peps.python.org/pep-0508/#grammar
26-
#
27-
# version_cmp = wsp* '<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '==='
28-
_VERSION_CMP = sorted(
29-
[
30-
i.strip(" '")
31-
for i in "'<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '==='".split(" | ")
32-
],
33-
key = lambda x: (-len(x), x),
34-
)
35-
3624
_STATE = enum(
3725
STRING = "string",
3826
VAR = "var",
@@ -353,36 +341,34 @@ def _env_expr(left, op, right):
353341
elif op == ">=":
354342
return left >= right
355343
else:
356-
return fail("TODO: op unsupported: '{}'".format(op))
344+
return fail("unsupported op: '{}' {} '{}'".format(left, op, right))
357345

358346
def _version_expr(left, op, right):
359347
"""Evaluate a version comparison expression"""
360-
left = semver(left)
361-
right = semver(right)
362-
_left = left.key()
363-
_right = right.key()
364-
if op == "<":
365-
return _left < _right
348+
_left = version.parse(left)
349+
_right = version.parse(right)
350+
if _left == None or _right == None:
351+
# Per spec, if either can't be normalized to a version, then
352+
# fallback to simple string comparison. Usually this is `platform_version`
353+
# or `platform_release`, which vary depending on platform.
354+
return _env_expr(left, op, right)
355+
356+
if op == "===":
357+
return version.is_eeq(_left, _right)
358+
elif op == "!=":
359+
return version.is_ne(_left, _right)
360+
elif op == "==":
361+
return version.is_eq(_left, _right)
362+
elif op == "<":
363+
return version.is_lt(_left, _right)
366364
elif op == ">":
367-
return _left > _right
365+
return version.is_gt(_left, _right)
368366
elif op == "<=":
369-
return _left <= _right
367+
return version.is_le(_left, _right)
370368
elif op == ">=":
371-
return _left >= _right
372-
elif op == "!=":
373-
return _left != _right
374-
elif op == "==":
375-
# Matching of major, minor, patch only
376-
return _left[:3] == _right[:3]
369+
return version.is_ge(_left, _right)
377370
elif op == "~=":
378-
right_plus = right.upper()
379-
_right_plus = right_plus.key()
380-
return _left >= _right and _left < _right_plus
381-
elif op == "===":
382-
# Strict matching
383-
return _left == _right
384-
elif op in _VERSION_CMP:
385-
fail("TODO: op unsupported: '{}'".format(op))
371+
return version.is_compatible(_left, _right)
386372
else:
387373
return False # Let's just ignore the invalid ops
388374

python/private/semver.bzl

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,6 @@ def _to_dict(self):
4343
"pre_release": self.pre_release,
4444
}
4545

46-
def _upper(self):
47-
major = self.major
48-
minor = self.minor
49-
patch = self.patch
50-
build = ""
51-
pre_release = ""
52-
version = self.str()
53-
54-
if patch != None:
55-
minor = minor + 1
56-
patch = 0
57-
elif minor != None:
58-
major = major + 1
59-
minor = 0
60-
elif minor == None:
61-
major = major + 1
62-
63-
return _new(
64-
major = major,
65-
minor = minor,
66-
patch = patch,
67-
build = build,
68-
pre_release = pre_release,
69-
version = "~" + version,
70-
)
71-
7246
def _new(*, major, minor, patch, pre_release, build, version = None):
7347
# buildifier: disable=uninitialized
7448
self = struct(
@@ -82,7 +56,6 @@ def _new(*, major, minor, patch, pre_release, build, version = None):
8256
key = lambda: _key(self),
8357
str = lambda: version,
8458
to_dict = lambda: _to_dict(self),
85-
upper = lambda: _upper(self),
8659
)
8760
return self
8861

0 commit comments

Comments
 (0)
0