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