feat: produce publishable distribution archives (#1019)
* feat: produce publishable distribution archives
Fixes #741
* fix: give unique names to dist/ folders by default
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5906289..9fb1705 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,7 +15,7 @@
- name: Prepare workspace snippet
run: .github/workflows/workspace_snippet.sh > release_notes.txt
- name: Build wheel dist
- run: bazel build --stamp --embed_label=${{ env.GITHUB_REF_NAME }} //python/runfiles:wheel
+ run: bazel build --stamp --embed_label=${{ env.GITHUB_REF_NAME }} //python/runfiles:wheel.dist
- name: Publish runfiles package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
@@ -23,7 +23,7 @@
# https://github.com/bazelbuild/rules_python/settings/secrets/actions
# and currently uses a token which authenticates as https://pypi.org/user/alexeagle/
password: ${{ secrets.PYPI_API_TOKEN }}
- packages_dir: bazel-bin/python/runfiles
+ packages_dir: bazel-bin/python/runfiles/dist
- name: Release
uses: softprops/action-gh-release@v1
with:
diff --git a/docs/packaging.md b/docs/packaging.md
index b750db6..a7a65ab 100755
--- a/docs/packaging.md
+++ b/docs/packaging.md
@@ -26,6 +26,32 @@
| <a id="py_package-packages"></a>packages | List of Python packages to include in the distribution. Sub-packages are automatically included. | List of strings | optional | <code>[]</code> |
+<a id="py_wheel_dist"></a>
+
+## py_wheel_dist
+
+<pre>
+py_wheel_dist(<a href="#py_wheel_dist-name">name</a>, <a href="#py_wheel_dist-out">out</a>, <a href="#py_wheel_dist-wheel">wheel</a>)
+</pre>
+
+Prepare a dist/ folder, following Python's packaging standard practice.
+
+See https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
+which recommends a dist/ folder containing the wheel file(s), source distributions, etc.
+
+This also has the advantage that stamping information is included in the wheel's filename.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_wheel_dist-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_wheel_dist-out"></a>out | name of the resulting directory | String | required | |
+| <a id="py_wheel_dist-wheel"></a>wheel | a [py_wheel rule](/docs/packaging.md#py_wheel_rule) | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+
+
<a id="py_wheel_rule"></a>
## py_wheel_rule
@@ -68,7 +94,7 @@
| <a id="py_wheel_rule-requires"></a>requires | List of requirements for this package. See the section on [Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) for details and examples of the format of this argument. | List of strings | optional | <code>[]</code> |
| <a id="py_wheel_rule-stamp"></a>stamp | Whether to encode build information into the wheel. Possible values:<br><br>- <code>stamp = 1</code>: Always stamp the build information into the wheel, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it potentially kills remote caching for the target and any downstream actions that depend on it.<br><br>- <code>stamp = 0</code>: Always replace build information by constant values. This gives good build result caching.<br><br>- <code>stamp = -1</code>: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.<br><br>Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | <code>-1</code> |
| <a id="py_wheel_rule-strip_path_prefixes"></a>strip_path_prefixes | path prefixes to strip from files added to the generated package | List of strings | optional | <code>[]</code> |
-| <a id="py_wheel_rule-version"></a>version | Version number of the package. Note that this attribute supports stamp format strings (eg. <code>1.2.3-{BUILD_TIMESTAMP}</code>) as well as 'make variables' (e.g. <code>1.2.3-$(VERSION)</code>). | String | required | |
+| <a id="py_wheel_rule-version"></a>version | Version number of the package.<br><br>Note that this attribute supports stamp format strings as well as 'make variables'. For example: - <code>version = "1.2.3-{BUILD_TIMESTAMP}"</code> - <code>version = "{BUILD_EMBED_LABEL}"</code> - <code>version = "$(VERSION)"</code><br><br>Note that Bazel's output filename cannot include the stamp information, as outputs must be known during the analysis phase and the stamp data is available only during the action execution.<br><br>The [<code>py_wheel</code>](/docs/packaging.md#py_wheel) macro produces a <code>.dist</code>-suffix target which creates a <code>dist/</code> folder containing the wheel with the stamped name, suitable for publishing.<br><br>See [<code>py_wheel_dist</code>](/docs/packaging.md#py_wheel_dist) for more info. | String | required | |
<a id="PyWheelInfo"></a>
diff --git a/python/packaging.bzl b/python/packaging.bzl
index 3b0b016..9274579 100644
--- a/python/packaging.bzl
+++ b/python/packaging.bzl
@@ -31,6 +31,43 @@
attrs = py_package_lib.attrs,
)
+# Based on https://github.com/aspect-build/bazel-lib/tree/main/lib/private/copy_to_directory.bzl
+# Avoiding a bazelbuild -> aspect-build dependency :(
+def _py_wheel_dist_impl(ctx):
+ dir = ctx.actions.declare_directory(ctx.attr.out)
+ name_file = ctx.attr.wheel[PyWheelInfo].name_file
+ cmds = [
+ "mkdir -p \"%s\"" % dir.path,
+ """cp "{}" "{}/$(cat "{}")" """.format(ctx.files.wheel[0].path, dir.path, name_file.path),
+ ]
+ ctx.actions.run_shell(
+ inputs = ctx.files.wheel + [name_file],
+ outputs = [dir],
+ command = "\n".join(cmds),
+ mnemonic = "CopyToDirectory",
+ progress_message = "Copying files to directory",
+ use_default_shell_env = True,
+ )
+ return [
+ DefaultInfo(files = depset([dir])),
+ ]
+
+py_wheel_dist = rule(
+ doc = """\
+Prepare a dist/ folder, following Python's packaging standard practice.
+
+See https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
+which recommends a dist/ folder containing the wheel file(s), source distributions, etc.
+
+This also has the advantage that stamping information is included in the wheel's filename.
+""",
+ implementation = _py_wheel_dist_impl,
+ attrs = {
+ "out": attr.string(doc = "name of the resulting directory", mandatory = True),
+ "wheel": attr.label(doc = "a [py_wheel rule](/docs/packaging.md#py_wheel_rule)", providers = [PyWheelInfo]),
+ },
+)
+
def py_wheel(name, **kwargs):
"""Builds a Python Wheel.
@@ -80,15 +117,12 @@
name: A unique name for this target.
**kwargs: other named parameters passed to the underlying [py_wheel rule](#py_wheel_rule)
"""
- _py_wheel(name = name, **kwargs)
+ py_wheel_dist(
+ name = "{}.dist".format(name),
+ wheel = name,
+ out = kwargs.pop("dist_folder", "{}_dist".format(name)),
+ )
- # TODO(alexeagle): produce an executable target like this:
- # py_publish_wheel(
- # name = "{}.publish".format(name),
- # wheel = name,
- # # Optional: override the label for a py_binary that runs twine
- # # https://twine.readthedocs.io/en/stable/
- # twine_bin = "//path/to:twine",
- # )
+ _py_wheel(name = name, **kwargs)
py_wheel_rule = _py_wheel
diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl
index b2ecce9..de9f65d 100644
--- a/python/private/py_wheel.bzl
+++ b/python/private/py_wheel.bzl
@@ -73,11 +73,23 @@
),
"version": attr.string(
mandatory = True,
- doc = (
- "Version number of the package. Note that this attribute " +
- "supports stamp format strings (eg. `1.2.3-{BUILD_TIMESTAMP}`) " +
- "as well as 'make variables' (e.g. `1.2.3-$(VERSION)`)."
- ),
+ doc = """\
+Version number of the package.
+
+Note that this attribute supports stamp format strings as well as 'make variables'.
+For example:
+ - `version = "1.2.3-{BUILD_TIMESTAMP}"`
+ - `version = "{BUILD_EMBED_LABEL}"`
+ - `version = "$(VERSION)"`
+
+Note that Bazel's output filename cannot include the stamp information, as outputs must be known
+during the analysis phase and the stamp data is available only during the action execution.
+
+The [`py_wheel`](/docs/packaging.md#py_wheel) macro produces a `.dist`-suffix target which creates a
+`dist/` folder containing the wheel with the stamped name, suitable for publishing.
+
+See [`py_wheel_dist`](/docs/packaging.md#py_wheel_dist) for more info.
+""",
),
"_stamp_flag": attr.label(
doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
diff --git a/python/runfiles/BUILD.bazel b/python/runfiles/BUILD.bazel
index ea171cc..ea327d7 100644
--- a/python/runfiles/BUILD.bazel
+++ b/python/runfiles/BUILD.bazel
@@ -41,6 +41,7 @@
"License :: OSI Approved :: Apache Software License",
],
description_file = "README.md",
+ dist_folder = "dist",
distribution = "bazel_runfiles",
homepage = "https://github.com/bazelbuild/rules_python",
strip_path_prefixes = ["python"],