xref: /aosp_15_r20/external/bazelbuild-rules_python/python/packaging.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2018 The Bazel Authors. All rights reserved.
2*60517a1eSAndroid Build Coastguard Worker#
3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*60517a1eSAndroid Build Coastguard Worker#
7*60517a1eSAndroid Build Coastguard Worker#    http://www.apache.org/licenses/LICENSE-2.0
8*60517a1eSAndroid Build Coastguard Worker#
9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*60517a1eSAndroid Build Coastguard Worker# limitations under the License.
14*60517a1eSAndroid Build Coastguard Worker
15*60517a1eSAndroid Build Coastguard Worker"""Public API for for building wheels."""
16*60517a1eSAndroid Build Coastguard Worker
17*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//rules:native_binary.bzl", "native_binary")
18*60517a1eSAndroid Build Coastguard Workerload("//python:py_binary.bzl", "py_binary")
19*60517a1eSAndroid Build Coastguard Workerload("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
20*60517a1eSAndroid Build Coastguard Workerload("//python/private:py_package.bzl", "py_package_lib")
21*60517a1eSAndroid Build Coastguard Workerload("//python/private:py_wheel.bzl", _PyWheelInfo = "PyWheelInfo", _py_wheel = "py_wheel")
22*60517a1eSAndroid Build Coastguard Workerload("//python/private:util.bzl", "copy_propagating_kwargs")
23*60517a1eSAndroid Build Coastguard Worker
24*60517a1eSAndroid Build Coastguard Worker# Re-export as public API
25*60517a1eSAndroid Build Coastguard WorkerPyWheelInfo = _PyWheelInfo
26*60517a1eSAndroid Build Coastguard Worker
27*60517a1eSAndroid Build Coastguard Workerpy_package = rule(
28*60517a1eSAndroid Build Coastguard Worker    implementation = py_package_lib.implementation,
29*60517a1eSAndroid Build Coastguard Worker    doc = """\
30*60517a1eSAndroid Build Coastguard WorkerA rule to select all files in transitive dependencies of deps which
31*60517a1eSAndroid Build Coastguard Workerbelong to given set of Python packages.
32*60517a1eSAndroid Build Coastguard Worker
33*60517a1eSAndroid Build Coastguard WorkerThis rule is intended to be used as data dependency to py_wheel rule.
34*60517a1eSAndroid Build Coastguard Worker""",
35*60517a1eSAndroid Build Coastguard Worker    attrs = py_package_lib.attrs,
36*60517a1eSAndroid Build Coastguard Worker)
37*60517a1eSAndroid Build Coastguard Worker
38*60517a1eSAndroid Build Coastguard Workerdef _py_wheel_dist_impl(ctx):
39*60517a1eSAndroid Build Coastguard Worker    out = ctx.actions.declare_directory(ctx.attr.out)
40*60517a1eSAndroid Build Coastguard Worker    name_file = ctx.attr.wheel[PyWheelInfo].name_file
41*60517a1eSAndroid Build Coastguard Worker    wheel = ctx.attr.wheel[PyWheelInfo].wheel
42*60517a1eSAndroid Build Coastguard Worker
43*60517a1eSAndroid Build Coastguard Worker    args = ctx.actions.args()
44*60517a1eSAndroid Build Coastguard Worker    args.add("--wheel", wheel)
45*60517a1eSAndroid Build Coastguard Worker    args.add("--name_file", name_file)
46*60517a1eSAndroid Build Coastguard Worker    args.add("--output", out.path)
47*60517a1eSAndroid Build Coastguard Worker
48*60517a1eSAndroid Build Coastguard Worker    ctx.actions.run(
49*60517a1eSAndroid Build Coastguard Worker        mnemonic = "PyWheelDistDir",
50*60517a1eSAndroid Build Coastguard Worker        executable = ctx.executable._copier,
51*60517a1eSAndroid Build Coastguard Worker        inputs = [wheel, name_file],
52*60517a1eSAndroid Build Coastguard Worker        outputs = [out],
53*60517a1eSAndroid Build Coastguard Worker        arguments = [args],
54*60517a1eSAndroid Build Coastguard Worker    )
55*60517a1eSAndroid Build Coastguard Worker    return [
56*60517a1eSAndroid Build Coastguard Worker        DefaultInfo(
57*60517a1eSAndroid Build Coastguard Worker            files = depset([out]),
58*60517a1eSAndroid Build Coastguard Worker            runfiles = ctx.runfiles([out]),
59*60517a1eSAndroid Build Coastguard Worker        ),
60*60517a1eSAndroid Build Coastguard Worker    ]
61*60517a1eSAndroid Build Coastguard Worker
62*60517a1eSAndroid Build Coastguard Workerpy_wheel_dist = rule(
63*60517a1eSAndroid Build Coastguard Worker    doc = """\
64*60517a1eSAndroid Build Coastguard WorkerPrepare a dist/ folder, following Python's packaging standard practice.
65*60517a1eSAndroid Build Coastguard Worker
66*60517a1eSAndroid Build Coastguard WorkerSee https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
67*60517a1eSAndroid Build Coastguard Workerwhich recommends a dist/ folder containing the wheel file(s), source distributions, etc.
68*60517a1eSAndroid Build Coastguard Worker
69*60517a1eSAndroid Build Coastguard WorkerThis also has the advantage that stamping information is included in the wheel's filename.
70*60517a1eSAndroid Build Coastguard Worker""",
71*60517a1eSAndroid Build Coastguard Worker    implementation = _py_wheel_dist_impl,
72*60517a1eSAndroid Build Coastguard Worker    attrs = {
73*60517a1eSAndroid Build Coastguard Worker        "out": attr.string(
74*60517a1eSAndroid Build Coastguard Worker            doc = "name of the resulting directory",
75*60517a1eSAndroid Build Coastguard Worker            mandatory = True,
76*60517a1eSAndroid Build Coastguard Worker        ),
77*60517a1eSAndroid Build Coastguard Worker        "wheel": attr.label(
78*60517a1eSAndroid Build Coastguard Worker            doc = "a [py_wheel target](#py_wheel)",
79*60517a1eSAndroid Build Coastguard Worker            providers = [PyWheelInfo],
80*60517a1eSAndroid Build Coastguard Worker        ),
81*60517a1eSAndroid Build Coastguard Worker        "_copier": attr.label(
82*60517a1eSAndroid Build Coastguard Worker            cfg = "exec",
83*60517a1eSAndroid Build Coastguard Worker            executable = True,
84*60517a1eSAndroid Build Coastguard Worker            default = Label("//python/private:py_wheel_dist"),
85*60517a1eSAndroid Build Coastguard Worker        ),
86*60517a1eSAndroid Build Coastguard Worker    },
87*60517a1eSAndroid Build Coastguard Worker)
88*60517a1eSAndroid Build Coastguard Worker
89*60517a1eSAndroid Build Coastguard Workerdef py_wheel(
90*60517a1eSAndroid Build Coastguard Worker        name,
91*60517a1eSAndroid Build Coastguard Worker        twine = None,
92*60517a1eSAndroid Build Coastguard Worker        twine_binary = Label("//tools/publish:twine") if BZLMOD_ENABLED else None,
93*60517a1eSAndroid Build Coastguard Worker        publish_args = [],
94*60517a1eSAndroid Build Coastguard Worker        **kwargs):
95*60517a1eSAndroid Build Coastguard Worker    """Builds a Python Wheel.
96*60517a1eSAndroid Build Coastguard Worker
97*60517a1eSAndroid Build Coastguard Worker    Wheels are Python distribution format defined in https://www.python.org/dev/peps/pep-0427/.
98*60517a1eSAndroid Build Coastguard Worker
99*60517a1eSAndroid Build Coastguard Worker    This macro packages a set of targets into a single wheel.
100*60517a1eSAndroid Build Coastguard Worker    It wraps the [py_wheel rule](#py_wheel_rule).
101*60517a1eSAndroid Build Coastguard Worker
102*60517a1eSAndroid Build Coastguard Worker    Currently only pure-python wheels are supported.
103*60517a1eSAndroid Build Coastguard Worker
104*60517a1eSAndroid Build Coastguard Worker    Examples:
105*60517a1eSAndroid Build Coastguard Worker
106*60517a1eSAndroid Build Coastguard Worker    ```python
107*60517a1eSAndroid Build Coastguard Worker    # Package some specific py_library targets, without their dependencies
108*60517a1eSAndroid Build Coastguard Worker    py_wheel(
109*60517a1eSAndroid Build Coastguard Worker        name = "minimal_with_py_library",
110*60517a1eSAndroid Build Coastguard Worker        # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
111*60517a1eSAndroid Build Coastguard Worker        distribution = "example_minimal_library",
112*60517a1eSAndroid Build Coastguard Worker        python_tag = "py3",
113*60517a1eSAndroid Build Coastguard Worker        version = "0.0.1",
114*60517a1eSAndroid Build Coastguard Worker        deps = [
115*60517a1eSAndroid Build Coastguard Worker            "//examples/wheel/lib:module_with_data",
116*60517a1eSAndroid Build Coastguard Worker            "//examples/wheel/lib:simple_module",
117*60517a1eSAndroid Build Coastguard Worker        ],
118*60517a1eSAndroid Build Coastguard Worker    )
119*60517a1eSAndroid Build Coastguard Worker
120*60517a1eSAndroid Build Coastguard Worker    # Use py_package to collect all transitive dependencies of a target,
121*60517a1eSAndroid Build Coastguard Worker    # selecting just the files within a specific python package.
122*60517a1eSAndroid Build Coastguard Worker    py_package(
123*60517a1eSAndroid Build Coastguard Worker        name = "example_pkg",
124*60517a1eSAndroid Build Coastguard Worker        # Only include these Python packages.
125*60517a1eSAndroid Build Coastguard Worker        packages = ["examples.wheel"],
126*60517a1eSAndroid Build Coastguard Worker        deps = [":main"],
127*60517a1eSAndroid Build Coastguard Worker    )
128*60517a1eSAndroid Build Coastguard Worker
129*60517a1eSAndroid Build Coastguard Worker    py_wheel(
130*60517a1eSAndroid Build Coastguard Worker        name = "minimal_with_py_package",
131*60517a1eSAndroid Build Coastguard Worker        # Package data. We're building "example_minimal_package-0.0.1-py3-none-any.whl"
132*60517a1eSAndroid Build Coastguard Worker        distribution = "example_minimal_package",
133*60517a1eSAndroid Build Coastguard Worker        python_tag = "py3",
134*60517a1eSAndroid Build Coastguard Worker        version = "0.0.1",
135*60517a1eSAndroid Build Coastguard Worker        deps = [":example_pkg"],
136*60517a1eSAndroid Build Coastguard Worker    )
137*60517a1eSAndroid Build Coastguard Worker    ```
138*60517a1eSAndroid Build Coastguard Worker
139*60517a1eSAndroid Build Coastguard Worker    To publish the wheel to PyPI, the twine package is required and it is installed
140*60517a1eSAndroid Build Coastguard Worker    by default on `bzlmod` setups. On legacy `WORKSPACE`, `rules_python`
141*60517a1eSAndroid Build Coastguard Worker    doesn't provide `twine` itself
142*60517a1eSAndroid Build Coastguard Worker    (see https://github.com/bazelbuild/rules_python/issues/1016), but
143*60517a1eSAndroid Build Coastguard Worker    you can install it with `pip_parse`, just like we do any other dependencies.
144*60517a1eSAndroid Build Coastguard Worker
145*60517a1eSAndroid Build Coastguard Worker    Once you've installed twine, you can pass its label to the `twine`
146*60517a1eSAndroid Build Coastguard Worker    attribute of this macro, to get a "[name].publish" target.
147*60517a1eSAndroid Build Coastguard Worker
148*60517a1eSAndroid Build Coastguard Worker    Example:
149*60517a1eSAndroid Build Coastguard Worker
150*60517a1eSAndroid Build Coastguard Worker    ```python
151*60517a1eSAndroid Build Coastguard Worker    py_wheel(
152*60517a1eSAndroid Build Coastguard Worker        name = "my_wheel",
153*60517a1eSAndroid Build Coastguard Worker        twine = "@publish_deps//twine",
154*60517a1eSAndroid Build Coastguard Worker        ...
155*60517a1eSAndroid Build Coastguard Worker    )
156*60517a1eSAndroid Build Coastguard Worker    ```
157*60517a1eSAndroid Build Coastguard Worker
158*60517a1eSAndroid Build Coastguard Worker    Now you can run a command like the following, which publishes to https://test.pypi.org/
159*60517a1eSAndroid Build Coastguard Worker
160*60517a1eSAndroid Build Coastguard Worker    ```sh
161*60517a1eSAndroid Build Coastguard Worker    % TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-*** \\
162*60517a1eSAndroid Build Coastguard Worker        bazel run --stamp --embed_label=1.2.4 -- \\
163*60517a1eSAndroid Build Coastguard Worker        //path/to:my_wheel.publish --repository testpypi
164*60517a1eSAndroid Build Coastguard Worker    ```
165*60517a1eSAndroid Build Coastguard Worker
166*60517a1eSAndroid Build Coastguard Worker    Args:
167*60517a1eSAndroid Build Coastguard Worker        name:  A unique name for this target.
168*60517a1eSAndroid Build Coastguard Worker        twine: A label of the external location of the py_library target for twine
169*60517a1eSAndroid Build Coastguard Worker        twine_binary: A label of the external location of a binary target for twine.
170*60517a1eSAndroid Build Coastguard Worker        publish_args: arguments passed to twine, e.g. ["--repository-url", "https://pypi.my.org/simple/"].
171*60517a1eSAndroid Build Coastguard Worker            These are subject to make var expansion, as with the `args` attribute.
172*60517a1eSAndroid Build Coastguard Worker            Note that you can also pass additional args to the bazel run command as in the example above.
173*60517a1eSAndroid Build Coastguard Worker        **kwargs: other named parameters passed to the underlying [py_wheel rule](#py_wheel_rule)
174*60517a1eSAndroid Build Coastguard Worker    """
175*60517a1eSAndroid Build Coastguard Worker    tags = kwargs.pop("tags", [])
176*60517a1eSAndroid Build Coastguard Worker    manual_tags = depset(tags + ["manual"]).to_list()
177*60517a1eSAndroid Build Coastguard Worker
178*60517a1eSAndroid Build Coastguard Worker    dist_target = "{}.dist".format(name)
179*60517a1eSAndroid Build Coastguard Worker    py_wheel_dist(
180*60517a1eSAndroid Build Coastguard Worker        name = dist_target,
181*60517a1eSAndroid Build Coastguard Worker        wheel = name,
182*60517a1eSAndroid Build Coastguard Worker        out = kwargs.pop("dist_folder", "{}_dist".format(name)),
183*60517a1eSAndroid Build Coastguard Worker        tags = manual_tags,
184*60517a1eSAndroid Build Coastguard Worker        **copy_propagating_kwargs(kwargs)
185*60517a1eSAndroid Build Coastguard Worker    )
186*60517a1eSAndroid Build Coastguard Worker
187*60517a1eSAndroid Build Coastguard Worker    _py_wheel(
188*60517a1eSAndroid Build Coastguard Worker        name = name,
189*60517a1eSAndroid Build Coastguard Worker        tags = tags,
190*60517a1eSAndroid Build Coastguard Worker        **kwargs
191*60517a1eSAndroid Build Coastguard Worker    )
192*60517a1eSAndroid Build Coastguard Worker
193*60517a1eSAndroid Build Coastguard Worker    twine_args = []
194*60517a1eSAndroid Build Coastguard Worker    if twine or twine_binary:
195*60517a1eSAndroid Build Coastguard Worker        twine_args = ["upload"]
196*60517a1eSAndroid Build Coastguard Worker        twine_args.extend(publish_args)
197*60517a1eSAndroid Build Coastguard Worker        twine_args.append("$(rootpath :{})/*".format(dist_target))
198*60517a1eSAndroid Build Coastguard Worker
199*60517a1eSAndroid Build Coastguard Worker    if twine_binary:
200*60517a1eSAndroid Build Coastguard Worker        native_binary(
201*60517a1eSAndroid Build Coastguard Worker            name = "{}.publish".format(name),
202*60517a1eSAndroid Build Coastguard Worker            src = twine_binary,
203*60517a1eSAndroid Build Coastguard Worker            out = select({
204*60517a1eSAndroid Build Coastguard Worker                "@platforms//os:windows": "{}.publish_script.exe".format(name),
205*60517a1eSAndroid Build Coastguard Worker                "//conditions:default": "{}.publish_script".format(name),
206*60517a1eSAndroid Build Coastguard Worker            }),
207*60517a1eSAndroid Build Coastguard Worker            args = twine_args,
208*60517a1eSAndroid Build Coastguard Worker            data = [dist_target],
209*60517a1eSAndroid Build Coastguard Worker            tags = manual_tags,
210*60517a1eSAndroid Build Coastguard Worker            visibility = kwargs.get("visibility"),
211*60517a1eSAndroid Build Coastguard Worker            **copy_propagating_kwargs(kwargs)
212*60517a1eSAndroid Build Coastguard Worker        )
213*60517a1eSAndroid Build Coastguard Worker    elif twine:
214*60517a1eSAndroid Build Coastguard Worker        if not twine.endswith(":pkg"):
215*60517a1eSAndroid Build Coastguard Worker            fail("twine label should look like @my_twine_repo//:pkg")
216*60517a1eSAndroid Build Coastguard Worker
217*60517a1eSAndroid Build Coastguard Worker        twine_main = twine.replace(":pkg", ":rules_python_wheel_entry_point_twine.py")
218*60517a1eSAndroid Build Coastguard Worker
219*60517a1eSAndroid Build Coastguard Worker        py_binary(
220*60517a1eSAndroid Build Coastguard Worker            name = "{}.publish".format(name),
221*60517a1eSAndroid Build Coastguard Worker            srcs = [twine_main],
222*60517a1eSAndroid Build Coastguard Worker            args = twine_args,
223*60517a1eSAndroid Build Coastguard Worker            data = [dist_target],
224*60517a1eSAndroid Build Coastguard Worker            imports = ["."],
225*60517a1eSAndroid Build Coastguard Worker            main = twine_main,
226*60517a1eSAndroid Build Coastguard Worker            deps = [twine],
227*60517a1eSAndroid Build Coastguard Worker            tags = manual_tags,
228*60517a1eSAndroid Build Coastguard Worker            visibility = kwargs.get("visibility"),
229*60517a1eSAndroid Build Coastguard Worker            **copy_propagating_kwargs(kwargs)
230*60517a1eSAndroid Build Coastguard Worker        )
231*60517a1eSAndroid Build Coastguard Worker
232*60517a1eSAndroid Build Coastguard Workerpy_wheel_rule = _py_wheel
233