feat: Adding variable support for distribution in py_wheel (#1251)
This allows the `distribution` attribute to expand workspace status
keys, just as the
`version` attribute can.
This allows, for example, the VCS's branch name (e.g test, release, etc)
to be part of the
distribution name without having to modify the BUILD file. Having
distinct distribution
names is necessary because tools like pip-compile, which determine
version compatibility
and replacements, and having the same distribution name would mean the
different builds
are seen as equivalent.
Closes #1250
diff --git a/docs/packaging.md b/docs/packaging.md
index b244b42..16fa00c 100755
--- a/docs/packaging.md
+++ b/docs/packaging.md
@@ -82,7 +82,7 @@
| <a id="py_wheel_rule-console_scripts"></a>console_scripts | Deprecated console_script entry points, e.g. <code>{'main': 'examples.wheel.main:main'}</code>.<br><br>Deprecated: prefer the <code>entry_points</code> attribute, which supports <code>console_scripts</code> as well as other entry points. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
| <a id="py_wheel_rule-deps"></a>deps | Targets to be included in the distribution.<br><br>The targets to package are usually <code>py_library</code> rules or filesets (for packaging data files).<br><br>Note it's usually better to package <code>py_library</code> targets and use <code>entry_points</code> attribute to specify <code>console_scripts</code> than to package <code>py_binary</code> rules. <code>py_binary</code> targets would wrap a executable script that tries to locate <code>.runfiles</code> directory which is not packaged in the wheel. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="py_wheel_rule-description_file"></a>description_file | A file containing text describing the package. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
-| <a id="py_wheel_rule-distribution"></a>distribution | Name of the distribution.<br><br>This should match the project name onm PyPI. It's also the name that is used to refer to the package in other packages' dependencies. | String | required | |
+| <a id="py_wheel_rule-distribution"></a>distribution | Name of the distribution.<br><br>This should match the project name onm PyPI. It's also the name that is used to refer to the package in other packages' dependencies.<br><br>Workspace status keys are expanded using <code>{NAME}</code> format, for example: - <code>distribution = "package.{CLASSIFIER}"</code> - <code>distribution = "{DISTRIBUTION}"</code><br><br>For the available keys, see https://bazel.build/docs/user-manual#workspace-status | String | required | |
| <a id="py_wheel_rule-entry_points"></a>entry_points | entry_points, e.g. <code>{'console_scripts': ['main = examples.wheel.main:main']}</code>. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | <code>{}</code> |
| <a id="py_wheel_rule-extra_distinfo_files"></a>extra_distinfo_files | Extra files to add to distinfo directory in the archive. | <a href="https://bazel.build/rules/lib/dict">Dictionary: Label -> String</a> | optional | <code>{}</code> |
| <a id="py_wheel_rule-extra_requires"></a>extra_requires | List of optional requirements for this package | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | <code>{}</code> |
diff --git a/examples/wheel/BUILD.bazel b/examples/wheel/BUILD.bazel
index 61a43ae..49a212b 100644
--- a/examples/wheel/BUILD.bazel
+++ b/examples/wheel/BUILD.bazel
@@ -94,7 +94,7 @@
py_wheel(
name = "minimal_with_py_library_with_stamp",
# Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
- distribution = "example_minimal_library",
+ distribution = "example_minimal_library{BUILD_USER}",
python_tag = "py3",
stamp = 1,
version = "0.1.{BUILD_TIMESTAMP}",
diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py
index c292c87..591e057 100644
--- a/examples/wheel/wheel_test.py
+++ b/examples/wheel/wheel_test.py
@@ -374,30 +374,36 @@
],
)
- def test_rule_sets_stamped_version_in_wheel_metadata(self):
+ def test_rule_expands_workspace_status_keys_in_wheel_metadata(self):
filename = os.path.join(
os.environ["TEST_SRCDIR"],
"rules_python",
"examples",
"wheel",
- "example_minimal_library-0.1._BUILD_TIMESTAMP_-py3-none-any.whl",
+ "example_minimal_library_BUILD_USER_-0.1._BUILD_TIMESTAMP_-py3-none-any.whl",
)
with zipfile.ZipFile(filename) as zf:
metadata_file = None
for f in zf.namelist():
self.assertNotIn("_BUILD_TIMESTAMP_", f)
+ self.assertNotIn("_BUILD_USER_", f)
if os.path.basename(f) == "METADATA":
metadata_file = f
self.assertIsNotNone(metadata_file)
version = None
+ name = None
with zf.open(metadata_file) as fp:
for line in fp:
- if line.startswith(b'Version:'):
+ if line.startswith(b"Version:"):
version = line.decode().split()[-1]
+ if line.startswith(b"Name:"):
+ name = line.decode().split()[-1]
self.assertIsNotNone(version)
+ self.assertIsNotNone(name)
self.assertNotIn("{BUILD_TIMESTAMP}", version)
+ self.assertNotIn("{BUILD_USER}", name)
if __name__ == "__main__":
diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl
index b6f2bfa..84fdeac 100644
--- a/python/private/py_wheel.bzl
+++ b/python/private/py_wheel.bzl
@@ -40,6 +40,12 @@
This should match the project name onm PyPI. It's also the name that is used to
refer to the package in other packages' dependencies.
+
+Workspace status keys are expanded using `{NAME}` format, for example:
+ - `distribution = "package.{CLASSIFIER}"`
+ - `distribution = "{DISTRIBUTION}"`
+
+For the available keys, see https://bazel.build/docs/user-manual#workspace-status
""",
),
"platform": attr.string(
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
index 6138c93..edc25cc 100644
--- a/tools/wheelmaker.py
+++ b/tools/wheelmaker.py
@@ -167,12 +167,12 @@
wheel_contents += "Tag: %s\n" % tag
self.add_string(self.distinfo_path("WHEEL"), wheel_contents)
- def add_metadata(self, metadata, description, version):
+ def add_metadata(self, metadata, name, description, version):
"""Write METADATA file to the distribution."""
# https://www.python.org/dev/peps/pep-0566/
# https://packaging.python.org/specifications/core-metadata/
- metadata += "Version: " + version
- metadata += "\n\n"
+ metadata = re.sub("^Name: .*$", "Name: %s" % name, metadata, flags=re.MULTILINE)
+ metadata += "Version: %s\n\n" % version
# setuptools seems to insert UNKNOWN as description when none is
# provided.
metadata += description if description else "UNKNOWN"
@@ -207,18 +207,18 @@
return files
-def resolve_version_stamp(
- version: str, volatile_status_stamp: Path, stable_status_stamp: Path
+def resolve_argument_stamp(
+ argument: str, volatile_status_stamp: Path, stable_status_stamp: Path
) -> str:
- """Resolve workspace status stamps format strings found in the version string
+ """Resolve workspace status stamps format strings found in the argument string
Args:
- version (str): The raw version represenation for the wheel (may include stamp variables)
+ argument (str): The raw argument represenation for the wheel (may include stamp variables)
volatile_status_stamp (Path): The path to a volatile workspace status file
stable_status_stamp (Path): The path to a stable workspace status file
Returns:
- str: A resolved version string
+ str: A resolved argument string
"""
lines = (
volatile_status_stamp.read_text().splitlines()
@@ -229,9 +229,9 @@
continue
key, value = line.split(" ", maxsplit=1)
stamp = "{" + key + "}"
- version = version.replace(stamp, value)
+ argument = argument.replace(stamp, value)
- return version
+ return argument
def parse_args() -> argparse.Namespace:
@@ -357,7 +357,16 @@
strip_prefixes = [p for p in arguments.strip_path_prefix]
if arguments.volatile_status_file and arguments.stable_status_file:
- version = resolve_version_stamp(
+ name = resolve_argument_stamp(
+ arguments.name,
+ arguments.volatile_status_file,
+ arguments.stable_status_file,
+ )
+ else:
+ name = arguments.name
+
+ if arguments.volatile_status_file and arguments.stable_status_file:
+ version = resolve_argument_stamp(
arguments.version,
arguments.volatile_status_file,
arguments.stable_status_file,
@@ -366,7 +375,7 @@
version = arguments.version
with WheelMaker(
- name=arguments.name,
+ name=name,
version=version,
build_tag=arguments.build_tag,
python_tag=arguments.python_tag,
@@ -398,7 +407,9 @@
with open(arguments.metadata_file, "rt", encoding="utf-8") as metadata_file:
metadata = metadata_file.read()
- maker.add_metadata(metadata=metadata, description=description, version=version)
+ maker.add_metadata(
+ metadata=metadata, name=name, description=description, version=version
+ )
if arguments.entry_points_file:
maker.add_file(