chore: publish a runfiles library as a wheel (#995)
* chore: publish a runfiles library as a wheel
Wire it up to GH actions so it is published for each release.
Tested locally with:
bazel build python/runfiles:wheel --embed_label=1.0.2 --stamp
PYTHONPATH=bazel-bin/python/runfiles/bazel_runfiles-_BUILD_EMBED_LABEL_-py3-none-any.whl python
>>> import runfiles
>>> runfiles.Create()
Note, I would have liked to call the package bazel-runfiles, but this isn't possible without either refactoring the paths in this repo, or doing some fancy starlark to copy files around to create a folder that we turn into the wheel.
There is no project https://pypi.org/project/runfiles though there is a https://pypi.org/project/runfile
We could try harder to get the name we prefer.
* Apply suggestions from code review
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
* more code review cleanup
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a675fe1..5906289 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,7 +13,17 @@
- name: Checkout
uses: actions/checkout@v2
- name: Prepare workspace snippet
- run: .github/workflows/workspace_snippet.sh ${{ env.GITHUB_REF_NAME }} > release_notes.txt
+ 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
+ - name: Publish runfiles package to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ # Note, the PYPI_API_TOKEN was added on
+ # 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
- name: Release
uses: softprops/action-gh-release@v1
with:
diff --git a/python/runfiles/BUILD.bazel b/python/runfiles/BUILD.bazel
index 2089c41..ea171cc 100644
--- a/python/runfiles/BUILD.bazel
+++ b/python/runfiles/BUILD.bazel
@@ -13,6 +13,7 @@
# limitations under the License.
load("//python:defs.bzl", "py_library")
+load("//python:packaging.bzl", "py_wheel")
filegroup(
name = "distribution",
@@ -22,6 +23,28 @@
py_library(
name = "runfiles",
- srcs = ["runfiles.py"],
+ srcs = [
+ "__init__.py",
+ "runfiles.py",
+ ],
visibility = ["//visibility:public"],
)
+
+# This can be manually tested by running tests/runfiles/runfiles_wheel_integration_test.sh
+# We ought to have an automated integration test for it, too.
+# see https://github.com/bazelbuild/rules_python/issues/1002
+py_wheel(
+ name = "wheel",
+ # From https://pypi.org/classifiers/
+ classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: Apache Software License",
+ ],
+ description_file = "README.md",
+ distribution = "bazel_runfiles",
+ homepage = "https://github.com/bazelbuild/rules_python",
+ strip_path_prefixes = ["python"],
+ version = "{BUILD_EMBED_LABEL}",
+ visibility = ["//visibility:public"],
+ deps = [":runfiles"],
+)
diff --git a/python/runfiles/README.md b/python/runfiles/README.md
new file mode 100644
index 0000000..79ba82c
--- /dev/null
+++ b/python/runfiles/README.md
@@ -0,0 +1,52 @@
+# bazel-runfiles library
+
+This is a Bazel Runfiles lookup library for Bazel-built Python binaries and tests.
+
+Typical Usage
+-------------
+
+1. Add the 'runfiles' dependency along with other third-party dependencies, for example in your
+ `requirements.txt` file.
+
+2. Depend on this runfiles library from your build rule, like you would other third-party libraries.
+
+ py_binary(
+ name = "my_binary",
+ ...
+ deps = [requirement("runfiles")],
+ )
+
+3. Import the runfiles library.
+
+ import runfiles # not "from runfiles import runfiles"
+
+4. Create a Runfiles object and use rlocation to look up runfile paths:
+
+ r = runfiles.Create()
+ ...
+ with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f:
+ contents = f.readlines()
+ ...
+
+ The code above creates a manifest- or directory-based implementations based
+ on the environment variables in os.environ. See `Create()` for more info.
+
+ If you want to explicitly create a manifest- or directory-based
+ implementations, you can do so as follows:
+
+ r1 = runfiles.CreateManifestBased("path/to/foo.runfiles_manifest")
+
+ r2 = runfiles.CreateDirectoryBased("path/to/foo.runfiles/")
+
+ If you wnat to start subprocesses, and the subprocess can't automatically
+ find the correct runfiles directory, you can explicitly set the right
+ environment variables for them:
+
+ import subprocess
+ import runfiles
+
+ r = runfiles.Create()
+ env = {}
+ ...
+ env.update(r.EnvVars())
+ p = subprocess.Popen([r.Rlocation("path/to/binary")], env, ...)
\ No newline at end of file
diff --git a/python/runfiles/__init__.py b/python/runfiles/__init__.py
new file mode 100644
index 0000000..eb42f79
--- /dev/null
+++ b/python/runfiles/__init__.py
@@ -0,0 +1 @@
+from .runfiles import *
diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py
index c310f06..01413fc 100644
--- a/python/runfiles/runfiles.py
+++ b/python/runfiles/runfiles.py
@@ -14,49 +14,7 @@
"""Runfiles lookup library for Bazel-built Python binaries and tests.
-USAGE:
-
-1. Depend on this runfiles library from your build rule:
-
- py_binary(
- name = "my_binary",
- ...
- deps = ["@rules_python//python/runfiles"],
- )
-
-2. Import the runfiles library.
-
- from python.runfiles import runfiles
-
-3. Create a Runfiles object and use rlocation to look up runfile paths:
-
- r = runfiles.Create()
- ...
- with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f:
- contents = f.readlines()
- ...
-
- The code above creates a manifest- or directory-based implementations based
- on the environment variables in os.environ. See `Create()` for more info.
-
- If you want to explicitly create a manifest- or directory-based
- implementations, you can do so as follows:
-
- r1 = runfiles.CreateManifestBased("path/to/foo.runfiles_manifest")
-
- r2 = runfiles.CreateDirectoryBased("path/to/foo.runfiles/")
-
- If you want to start subprocesses that also need runfiles, you need to set
- the right environment variables for them:
-
- import subprocess
- from bazel_tools.tools.python.runfiles import runfiles
-
- r = runfiles.Create()
- env = {}
- ...
- env.update(r.EnvVars())
- p = subprocess.Popen([r.Rlocation("path/to/binary")], env, ...)
+See README.md for usage instructions.
"""
import inspect
import os
diff --git a/tests/runfiles/runfiles_wheel_integration_test.sh b/tests/runfiles/runfiles_wheel_integration_test.sh
new file mode 100755
index 0000000..7faa027
--- /dev/null
+++ b/tests/runfiles/runfiles_wheel_integration_test.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# Manual test, run outside of Bazel, to check that our runfiles wheel should be functional
+# for users who install it from pypi.
+set -o errexit
+
+SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+
+bazel 2>/dev/null build --stamp --embed_label=1.2.3 //python/runfiles:wheel
+wheelpath=$SCRIPTPATH/../../$(bazel 2>/dev/null cquery --output=files //python/runfiles:wheel)
+PYTHONPATH=$wheelpath python3 -c 'import importlib;print(importlib.import_module("runfiles"))'