aboutsummaryrefslogtreecommitdiff
path: root/python/packaging.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'python/packaging.bzl')
-rw-r--r--python/packaging.bzl182
1 files changed, 182 insertions, 0 deletions
diff --git a/python/packaging.bzl b/python/packaging.bzl
new file mode 100644
index 0000000..d9b9d02
--- /dev/null
+++ b/python/packaging.bzl
@@ -0,0 +1,182 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Public API for for building wheels."""
+
+load("//python/private:py_package.bzl", "py_package_lib")
+load("//python/private:py_wheel.bzl", _PyWheelInfo = "PyWheelInfo", _py_wheel = "py_wheel")
+load("//python/private:util.bzl", "copy_propagating_kwargs")
+
+# Re-export as public API
+PyWheelInfo = _PyWheelInfo
+
+py_package = rule(
+ implementation = py_package_lib.implementation,
+ doc = """\
+A rule to select all files in transitive dependencies of deps which
+belong to given set of Python packages.
+
+This rule is intended to be used as data dependency to py_wheel rule.
+""",
+ 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]), runfiles = ctx.runfiles([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, twine = None, publish_args = [], **kwargs):
+ """Builds a Python Wheel.
+
+ Wheels are Python distribution format defined in https://www.python.org/dev/peps/pep-0427/.
+
+ This macro packages a set of targets into a single wheel.
+ It wraps the [py_wheel rule](#py_wheel_rule).
+
+ Currently only pure-python wheels are supported.
+
+ Examples:
+
+ ```python
+ # Package some specific py_library targets, without their dependencies
+ py_wheel(
+ name = "minimal_with_py_library",
+ # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
+ distribution = "example_minimal_library",
+ python_tag = "py3",
+ version = "0.0.1",
+ deps = [
+ "//examples/wheel/lib:module_with_data",
+ "//examples/wheel/lib:simple_module",
+ ],
+ )
+
+ # Use py_package to collect all transitive dependencies of a target,
+ # selecting just the files within a specific python package.
+ py_package(
+ name = "example_pkg",
+ # Only include these Python packages.
+ packages = ["examples.wheel"],
+ deps = [":main"],
+ )
+
+ py_wheel(
+ name = "minimal_with_py_package",
+ # Package data. We're building "example_minimal_package-0.0.1-py3-none-any.whl"
+ distribution = "example_minimal_package",
+ python_tag = "py3",
+ version = "0.0.1",
+ deps = [":example_pkg"],
+ )
+ ```
+
+ To publish the wheel to Pypi, the twine package is required.
+ rules_python doesn't provide twine itself, see https://github.com/bazelbuild/rules_python/issues/1016
+ However you can install it with pip_parse, just like we do in the WORKSPACE file in rules_python.
+
+ Once you've installed twine, you can pass its label to the `twine` attribute of this macro,
+ to get a "[name].publish" target.
+
+ Example:
+
+ ```python
+ py_wheel(
+ name = "my_wheel",
+ twine = "@publish_deps_twine//:pkg",
+ ...
+ )
+ ```
+
+ Now you can run a command like the following, which publishes to https://test.pypi.org/
+
+ ```sh
+ % TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-*** \\
+ bazel run --stamp --embed_label=1.2.4 -- \\
+ //path/to:my_wheel.publish --repository testpypi
+ ```
+
+ Args:
+ name: A unique name for this target.
+ twine: A label of the external location of the py_library target for twine
+ publish_args: arguments passed to twine, e.g. ["--repository-url", "https://pypi.my.org/simple/"].
+ These are subject to make var expansion, as with the `args` attribute.
+ Note that you can also pass additional args to the bazel run command as in the example above.
+ **kwargs: other named parameters passed to the underlying [py_wheel rule](#py_wheel_rule)
+ """
+ _dist_target = "{}.dist".format(name)
+ py_wheel_dist(
+ name = _dist_target,
+ wheel = name,
+ out = kwargs.pop("dist_folder", "{}_dist".format(name)),
+ **copy_propagating_kwargs(kwargs)
+ )
+
+ _py_wheel(name = name, **kwargs)
+
+ if twine:
+ if not twine.endswith(":pkg"):
+ fail("twine label should look like @my_twine_repo//:pkg")
+ twine_main = twine.replace(":pkg", ":rules_python_wheel_entry_point_twine.py")
+ twine_args = ["upload"]
+ twine_args.extend(publish_args)
+ twine_args.append("$(rootpath :{})/*".format(_dist_target))
+
+ # TODO: use py_binary from //python:defs.bzl after our stardoc setup is less brittle
+ # buildifier: disable=native-py
+ native.py_binary(
+ name = "{}.publish".format(name),
+ srcs = [twine_main],
+ args = twine_args,
+ data = [_dist_target],
+ imports = ["."],
+ main = twine_main,
+ deps = [twine],
+ visibility = kwargs.get("visibility"),
+ **copy_propagating_kwargs(kwargs)
+ )
+
+py_wheel_rule = _py_wheel