feat(pypi): add a standards compliant python_tag creator (#3110)
This will be needed when we start selecting wheels entirely in the
bzlmod extension evaluation phase (#3058).
This adds a few unit tests to just ensure that we conform to the spec
even though the code is very simple.
Work towards #2747
Work towards #2759
Work towards #2849
diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel
index e5a916b..3a66170 100644
--- a/python/private/pypi/BUILD.bazel
+++ b/python/private/pypi/BUILD.bazel
@@ -337,6 +337,14 @@
)
bzl_library(
+ name = "python_tag_bzl",
+ srcs = ["python_tag.bzl"],
+ deps = [
+ "//python/private:version_bzl",
+ ],
+)
+
+bzl_library(
name = "render_pkg_aliases_bzl",
srcs = ["render_pkg_aliases.bzl"],
deps = [
diff --git a/python/private/pypi/python_tag.bzl b/python/private/pypi/python_tag.bzl
new file mode 100644
index 0000000..224c5f9
--- /dev/null
+++ b/python/private/pypi/python_tag.bzl
@@ -0,0 +1,41 @@
+"A simple utility function to get the python_tag from the implementation name"
+
+load("//python/private:version.bzl", "version")
+
+# Taken from
+# https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#python-tag
+_PY_TAGS = {
+ # "py": Generic Python (does not require implementation-specific features)
+ "cpython": "cp",
+ "ironpython": "ip",
+ "jython": "jy",
+ "pypy": "pp",
+ "python": "py",
+}
+PY_TAG_GENERIC = "py"
+
+def python_tag(implementation_name, python_version = ""):
+ """Get the python_tag from the implementation_name.
+
+ Args:
+ implementation_name: {type}`str` the implementation name, e.g. "cpython"
+ python_version: {type}`str` a version who can be parsed using PEP440 compliant
+ parser.
+
+ Returns:
+ A {type}`str` that represents the python_tag with a version if the
+ python_version is given.
+ """
+ if python_version:
+ v = version.parse(python_version, strict = True)
+ suffix = "{}{}".format(
+ v.release[0],
+ v.release[1] if len(v.release) > 1 else "",
+ )
+ else:
+ suffix = ""
+
+ return "{}{}".format(
+ _PY_TAGS.get(implementation_name, implementation_name),
+ suffix,
+ )
diff --git a/tests/pypi/python_tag/BUILD.bazel b/tests/pypi/python_tag/BUILD.bazel
new file mode 100644
index 0000000..d4b37ce
--- /dev/null
+++ b/tests/pypi/python_tag/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":python_tag_tests.bzl", "python_tag_test_suite")
+
+python_tag_test_suite(name = "python_tag_tests")
diff --git a/tests/pypi/python_tag/python_tag_tests.bzl b/tests/pypi/python_tag/python_tag_tests.bzl
new file mode 100644
index 0000000..ca86575
--- /dev/null
+++ b/tests/pypi/python_tag/python_tag_tests.bzl
@@ -0,0 +1,34 @@
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/private/pypi:python_tag.bzl", "python_tag") # buildifier: disable=bzl-visibility
+
+_tests = []
+
+def _test_without_version(env):
+ for give, expect in {
+ "cpython": "cp",
+ "ironpython": "ip",
+ "jython": "jy",
+ "pypy": "pp",
+ "python": "py",
+ "something_else": "something_else",
+ }.items():
+ got = python_tag(give)
+ env.expect.that_str(got).equals(expect)
+
+_tests.append(_test_without_version)
+
+def _test_with_version(env):
+ got = python_tag("cpython", "3.1.15")
+ env.expect.that_str(got).equals("cp31")
+
+_tests.append(_test_with_version)
+
+def python_tag_test_suite(name):
+ """Create the test suite.
+
+ Args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)