xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/py_wheel.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2023 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"Implementation of py_wheel rule"
16*60517a1eSAndroid Build Coastguard Worker
17*60517a1eSAndroid Build Coastguard Workerload("//python/private:stamp.bzl", "is_stamping_enabled")
18*60517a1eSAndroid Build Coastguard Workerload(":py_package.bzl", "py_package_lib")
19*60517a1eSAndroid Build Coastguard Workerload(":py_wheel_normalize_pep440.bzl", "normalize_pep440")
20*60517a1eSAndroid Build Coastguard Worker
21*60517a1eSAndroid Build Coastguard WorkerPyWheelInfo = provider(
22*60517a1eSAndroid Build Coastguard Worker    doc = "Information about a wheel produced by `py_wheel`",
23*60517a1eSAndroid Build Coastguard Worker    fields = {
24*60517a1eSAndroid Build Coastguard Worker        "name_file": (
25*60517a1eSAndroid Build Coastguard Worker            "File: A file containing the canonical name of the wheel (after " +
26*60517a1eSAndroid Build Coastguard Worker            "stamping, if enabled)."
27*60517a1eSAndroid Build Coastguard Worker        ),
28*60517a1eSAndroid Build Coastguard Worker        "wheel": "File: The wheel file itself.",
29*60517a1eSAndroid Build Coastguard Worker    },
30*60517a1eSAndroid Build Coastguard Worker)
31*60517a1eSAndroid Build Coastguard Worker
32*60517a1eSAndroid Build Coastguard Worker_distribution_attrs = {
33*60517a1eSAndroid Build Coastguard Worker    "abi": attr.string(
34*60517a1eSAndroid Build Coastguard Worker        default = "none",
35*60517a1eSAndroid Build Coastguard Worker        doc = "Python ABI tag. 'none' for pure-Python wheels.",
36*60517a1eSAndroid Build Coastguard Worker    ),
37*60517a1eSAndroid Build Coastguard Worker    "distribution": attr.string(
38*60517a1eSAndroid Build Coastguard Worker        mandatory = True,
39*60517a1eSAndroid Build Coastguard Worker        doc = """\
40*60517a1eSAndroid Build Coastguard WorkerName of the distribution.
41*60517a1eSAndroid Build Coastguard Worker
42*60517a1eSAndroid Build Coastguard WorkerThis should match the project name on PyPI. It's also the name that is used to
43*60517a1eSAndroid Build Coastguard Workerrefer to the package in other packages' dependencies.
44*60517a1eSAndroid Build Coastguard Worker
45*60517a1eSAndroid Build Coastguard WorkerWorkspace status keys are expanded using `{NAME}` format, for example:
46*60517a1eSAndroid Build Coastguard Worker - `distribution = "package.{CLASSIFIER}"`
47*60517a1eSAndroid Build Coastguard Worker - `distribution = "{DISTRIBUTION}"`
48*60517a1eSAndroid Build Coastguard Worker
49*60517a1eSAndroid Build Coastguard WorkerFor the available keys, see https://bazel.build/docs/user-manual#workspace-status
50*60517a1eSAndroid Build Coastguard Worker""",
51*60517a1eSAndroid Build Coastguard Worker    ),
52*60517a1eSAndroid Build Coastguard Worker    "platform": attr.string(
53*60517a1eSAndroid Build Coastguard Worker        default = "any",
54*60517a1eSAndroid Build Coastguard Worker        doc = """\
55*60517a1eSAndroid Build Coastguard WorkerSupported platform. Use 'any' for pure-Python wheel.
56*60517a1eSAndroid Build Coastguard Worker
57*60517a1eSAndroid Build Coastguard WorkerIf you have included platform-specific data, such as a .pyd or .so
58*60517a1eSAndroid Build Coastguard Workerextension module, you will need to specify the platform in standard
59*60517a1eSAndroid Build Coastguard Workerpip format. If you support multiple platforms, you can define
60*60517a1eSAndroid Build Coastguard Workerplatform constraints, then use a select() to specify the appropriate
61*60517a1eSAndroid Build Coastguard Workerspecifier, eg:
62*60517a1eSAndroid Build Coastguard Worker
63*60517a1eSAndroid Build Coastguard Worker`
64*60517a1eSAndroid Build Coastguard Workerplatform = select({
65*60517a1eSAndroid Build Coastguard Worker    "//platforms:windows_x86_64": "win_amd64",
66*60517a1eSAndroid Build Coastguard Worker    "//platforms:macos_x86_64": "macosx_10_7_x86_64",
67*60517a1eSAndroid Build Coastguard Worker    "//platforms:linux_x86_64": "manylinux2014_x86_64",
68*60517a1eSAndroid Build Coastguard Worker})
69*60517a1eSAndroid Build Coastguard Worker`
70*60517a1eSAndroid Build Coastguard Worker""",
71*60517a1eSAndroid Build Coastguard Worker    ),
72*60517a1eSAndroid Build Coastguard Worker    "python_tag": attr.string(
73*60517a1eSAndroid Build Coastguard Worker        default = "py3",
74*60517a1eSAndroid Build Coastguard Worker        doc = "Supported Python version(s), eg `py3`, `cp35.cp36`, etc",
75*60517a1eSAndroid Build Coastguard Worker    ),
76*60517a1eSAndroid Build Coastguard Worker    "stamp": attr.int(
77*60517a1eSAndroid Build Coastguard Worker        doc = """\
78*60517a1eSAndroid Build Coastguard WorkerWhether to encode build information into the wheel. Possible values:
79*60517a1eSAndroid Build Coastguard Worker
80*60517a1eSAndroid Build Coastguard Worker- `stamp = 1`: Always stamp the build information into the wheel, even in \
81*60517a1eSAndroid Build Coastguard Worker[--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
82*60517a1eSAndroid Build Coastguard WorkerThis setting should be avoided, since it potentially kills remote caching for the target and \
83*60517a1eSAndroid Build Coastguard Workerany downstream actions that depend on it.
84*60517a1eSAndroid Build Coastguard Worker
85*60517a1eSAndroid Build Coastguard Worker- `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
86*60517a1eSAndroid Build Coastguard Worker
87*60517a1eSAndroid Build Coastguard Worker- `stamp = -1`: Embedding of build information is controlled by the \
88*60517a1eSAndroid Build Coastguard Worker[--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
89*60517a1eSAndroid Build Coastguard Worker
90*60517a1eSAndroid Build Coastguard WorkerStamped targets are not rebuilt unless their dependencies change.
91*60517a1eSAndroid Build Coastguard Worker        """,
92*60517a1eSAndroid Build Coastguard Worker        default = -1,
93*60517a1eSAndroid Build Coastguard Worker        values = [1, 0, -1],
94*60517a1eSAndroid Build Coastguard Worker    ),
95*60517a1eSAndroid Build Coastguard Worker    "version": attr.string(
96*60517a1eSAndroid Build Coastguard Worker        mandatory = True,
97*60517a1eSAndroid Build Coastguard Worker        doc = """\
98*60517a1eSAndroid Build Coastguard WorkerVersion number of the package.
99*60517a1eSAndroid Build Coastguard Worker
100*60517a1eSAndroid Build Coastguard WorkerNote that this attribute supports stamp format strings as well as 'make variables'.
101*60517a1eSAndroid Build Coastguard WorkerFor example:
102*60517a1eSAndroid Build Coastguard Worker  - `version = "1.2.3-{BUILD_TIMESTAMP}"`
103*60517a1eSAndroid Build Coastguard Worker  - `version = "{BUILD_EMBED_LABEL}"`
104*60517a1eSAndroid Build Coastguard Worker  - `version = "$(VERSION)"`
105*60517a1eSAndroid Build Coastguard Worker
106*60517a1eSAndroid Build Coastguard WorkerNote that Bazel's output filename cannot include the stamp information, as outputs must be known
107*60517a1eSAndroid Build Coastguard Workerduring the analysis phase and the stamp data is available only during the action execution.
108*60517a1eSAndroid Build Coastguard Worker
109*60517a1eSAndroid Build Coastguard WorkerThe [`py_wheel`](#py_wheel) macro produces a `.dist`-suffix target which creates a
110*60517a1eSAndroid Build Coastguard Worker`dist/` folder containing the wheel with the stamped name, suitable for publishing.
111*60517a1eSAndroid Build Coastguard Worker
112*60517a1eSAndroid Build Coastguard WorkerSee [`py_wheel_dist`](#py_wheel_dist) for more info.
113*60517a1eSAndroid Build Coastguard Worker""",
114*60517a1eSAndroid Build Coastguard Worker    ),
115*60517a1eSAndroid Build Coastguard Worker    "_stamp_flag": attr.label(
116*60517a1eSAndroid Build Coastguard Worker        doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
117*60517a1eSAndroid Build Coastguard Worker        default = Label("//python/private:stamp"),
118*60517a1eSAndroid Build Coastguard Worker    ),
119*60517a1eSAndroid Build Coastguard Worker}
120*60517a1eSAndroid Build Coastguard Worker
121*60517a1eSAndroid Build Coastguard Worker_feature_flags = {}
122*60517a1eSAndroid Build Coastguard Worker
123*60517a1eSAndroid Build Coastguard WorkerALLOWED_DATA_FILE_PREFIX = ("purelib", "platlib", "headers", "scripts", "data")
124*60517a1eSAndroid Build Coastguard Worker_requirement_attrs = {
125*60517a1eSAndroid Build Coastguard Worker    "extra_requires": attr.string_list_dict(
126*60517a1eSAndroid Build Coastguard Worker        doc = ("A mapping of [extras](https://peps.python.org/pep-0508/#extras) options to lists of requirements (similar to `requires`). This attribute " +
127*60517a1eSAndroid Build Coastguard Worker               "is mutually exclusive with `extra_requires_file`."),
128*60517a1eSAndroid Build Coastguard Worker    ),
129*60517a1eSAndroid Build Coastguard Worker    "extra_requires_files": attr.label_keyed_string_dict(
130*60517a1eSAndroid Build Coastguard Worker        doc = ("A mapping of requirements files (similar to `requires_file`) to the name of an [extras](https://peps.python.org/pep-0508/#extras) option " +
131*60517a1eSAndroid Build Coastguard Worker               "This attribute is mutually exclusive with `extra_requires`."),
132*60517a1eSAndroid Build Coastguard Worker        allow_files = True,
133*60517a1eSAndroid Build Coastguard Worker    ),
134*60517a1eSAndroid Build Coastguard Worker    "requires": attr.string_list(
135*60517a1eSAndroid Build Coastguard Worker        doc = ("List of requirements for this package. See the section on " +
136*60517a1eSAndroid Build Coastguard Worker               "[Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) " +
137*60517a1eSAndroid Build Coastguard Worker               "for details and examples of the format of this argument. This " +
138*60517a1eSAndroid Build Coastguard Worker               "attribute is mutually exclusive with `requires_file`."),
139*60517a1eSAndroid Build Coastguard Worker    ),
140*60517a1eSAndroid Build Coastguard Worker    "requires_file": attr.label(
141*60517a1eSAndroid Build Coastguard Worker        doc = ("A file containing a list of requirements for this package. See the section on " +
142*60517a1eSAndroid Build Coastguard Worker               "[Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) " +
143*60517a1eSAndroid Build Coastguard Worker               "for details and examples of the format of this argument. This " +
144*60517a1eSAndroid Build Coastguard Worker               "attribute is mutually exclusive with `requires`."),
145*60517a1eSAndroid Build Coastguard Worker        allow_single_file = True,
146*60517a1eSAndroid Build Coastguard Worker    ),
147*60517a1eSAndroid Build Coastguard Worker}
148*60517a1eSAndroid Build Coastguard Worker
149*60517a1eSAndroid Build Coastguard Worker_entrypoint_attrs = {
150*60517a1eSAndroid Build Coastguard Worker    "console_scripts": attr.string_dict(
151*60517a1eSAndroid Build Coastguard Worker        doc = """\
152*60517a1eSAndroid Build Coastguard WorkerDeprecated console_script entry points, e.g. `{'main': 'examples.wheel.main:main'}`.
153*60517a1eSAndroid Build Coastguard Worker
154*60517a1eSAndroid Build Coastguard WorkerDeprecated: prefer the `entry_points` attribute, which supports `console_scripts` as well as other entry points.
155*60517a1eSAndroid Build Coastguard Worker""",
156*60517a1eSAndroid Build Coastguard Worker    ),
157*60517a1eSAndroid Build Coastguard Worker    "entry_points": attr.string_list_dict(
158*60517a1eSAndroid Build Coastguard Worker        doc = """\
159*60517a1eSAndroid Build Coastguard Workerentry_points, e.g. `{'console_scripts': ['main = examples.wheel.main:main']}`.
160*60517a1eSAndroid Build Coastguard Worker""",
161*60517a1eSAndroid Build Coastguard Worker    ),
162*60517a1eSAndroid Build Coastguard Worker}
163*60517a1eSAndroid Build Coastguard Worker
164*60517a1eSAndroid Build Coastguard Worker_other_attrs = {
165*60517a1eSAndroid Build Coastguard Worker    "author": attr.string(
166*60517a1eSAndroid Build Coastguard Worker        doc = "A string specifying the author of the package.",
167*60517a1eSAndroid Build Coastguard Worker        default = "",
168*60517a1eSAndroid Build Coastguard Worker    ),
169*60517a1eSAndroid Build Coastguard Worker    "author_email": attr.string(
170*60517a1eSAndroid Build Coastguard Worker        doc = "A string specifying the email address of the package author.",
171*60517a1eSAndroid Build Coastguard Worker        default = "",
172*60517a1eSAndroid Build Coastguard Worker    ),
173*60517a1eSAndroid Build Coastguard Worker    "classifiers": attr.string_list(
174*60517a1eSAndroid Build Coastguard Worker        doc = "A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers",
175*60517a1eSAndroid Build Coastguard Worker    ),
176*60517a1eSAndroid Build Coastguard Worker    "data_files": attr.label_keyed_string_dict(
177*60517a1eSAndroid Build Coastguard Worker        doc = ("Any file that is not normally installed inside site-packages goes into the .data directory, named " +
178*60517a1eSAndroid Build Coastguard Worker               "as the .dist-info directory but with the .data/ extension.  Allowed paths: {prefixes}".format(prefixes = ALLOWED_DATA_FILE_PREFIX)),
179*60517a1eSAndroid Build Coastguard Worker        allow_files = True,
180*60517a1eSAndroid Build Coastguard Worker    ),
181*60517a1eSAndroid Build Coastguard Worker    "description_content_type": attr.string(
182*60517a1eSAndroid Build Coastguard Worker        doc = ("The type of contents in description_file. " +
183*60517a1eSAndroid Build Coastguard Worker               "If not provided, the type will be inferred from the extension of description_file. " +
184*60517a1eSAndroid Build Coastguard Worker               "Also see https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type"),
185*60517a1eSAndroid Build Coastguard Worker    ),
186*60517a1eSAndroid Build Coastguard Worker    "description_file": attr.label(
187*60517a1eSAndroid Build Coastguard Worker        doc = "A file containing text describing the package.",
188*60517a1eSAndroid Build Coastguard Worker        allow_single_file = True,
189*60517a1eSAndroid Build Coastguard Worker    ),
190*60517a1eSAndroid Build Coastguard Worker    "extra_distinfo_files": attr.label_keyed_string_dict(
191*60517a1eSAndroid Build Coastguard Worker        doc = "Extra files to add to distinfo directory in the archive.",
192*60517a1eSAndroid Build Coastguard Worker        allow_files = True,
193*60517a1eSAndroid Build Coastguard Worker    ),
194*60517a1eSAndroid Build Coastguard Worker    "homepage": attr.string(
195*60517a1eSAndroid Build Coastguard Worker        doc = "A string specifying the URL for the package homepage.",
196*60517a1eSAndroid Build Coastguard Worker        default = "",
197*60517a1eSAndroid Build Coastguard Worker    ),
198*60517a1eSAndroid Build Coastguard Worker    "license": attr.string(
199*60517a1eSAndroid Build Coastguard Worker        doc = "A string specifying the license of the package.",
200*60517a1eSAndroid Build Coastguard Worker        default = "",
201*60517a1eSAndroid Build Coastguard Worker    ),
202*60517a1eSAndroid Build Coastguard Worker    "project_urls": attr.string_dict(
203*60517a1eSAndroid Build Coastguard Worker        doc = ("A string dict specifying additional browsable URLs for the project and corresponding labels, " +
204*60517a1eSAndroid Build Coastguard Worker               "where label is the key and url is the value. " +
205*60517a1eSAndroid Build Coastguard Worker               'e.g `{{"Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/"}}`'),
206*60517a1eSAndroid Build Coastguard Worker    ),
207*60517a1eSAndroid Build Coastguard Worker    "python_requires": attr.string(
208*60517a1eSAndroid Build Coastguard Worker        doc = (
209*60517a1eSAndroid Build Coastguard Worker            "Python versions required by this distribution, e.g. '>=3.5,<3.7'"
210*60517a1eSAndroid Build Coastguard Worker        ),
211*60517a1eSAndroid Build Coastguard Worker        default = "",
212*60517a1eSAndroid Build Coastguard Worker    ),
213*60517a1eSAndroid Build Coastguard Worker    "strip_path_prefixes": attr.string_list(
214*60517a1eSAndroid Build Coastguard Worker        default = [],
215*60517a1eSAndroid Build Coastguard Worker        doc = "path prefixes to strip from files added to the generated package",
216*60517a1eSAndroid Build Coastguard Worker    ),
217*60517a1eSAndroid Build Coastguard Worker    "summary": attr.string(
218*60517a1eSAndroid Build Coastguard Worker        doc = "A one-line summary of what the distribution does",
219*60517a1eSAndroid Build Coastguard Worker    ),
220*60517a1eSAndroid Build Coastguard Worker}
221*60517a1eSAndroid Build Coastguard Worker
222*60517a1eSAndroid Build Coastguard Worker_PROJECT_URL_LABEL_LENGTH_LIMIT = 32
223*60517a1eSAndroid Build Coastguard Worker_DESCRIPTION_FILE_EXTENSION_TO_TYPE = {
224*60517a1eSAndroid Build Coastguard Worker    "md": "text/markdown",
225*60517a1eSAndroid Build Coastguard Worker    "rst": "text/x-rst",
226*60517a1eSAndroid Build Coastguard Worker}
227*60517a1eSAndroid Build Coastguard Worker_DEFAULT_DESCRIPTION_FILE_TYPE = "text/plain"
228*60517a1eSAndroid Build Coastguard Worker
229*60517a1eSAndroid Build Coastguard Workerdef _escape_filename_distribution_name(name):
230*60517a1eSAndroid Build Coastguard Worker    """Escape the distribution name component of a filename.
231*60517a1eSAndroid Build Coastguard Worker
232*60517a1eSAndroid Build Coastguard Worker    See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
233*60517a1eSAndroid Build Coastguard Worker    and https://packaging.python.org/en/latest/specifications/name-normalization/.
234*60517a1eSAndroid Build Coastguard Worker
235*60517a1eSAndroid Build Coastguard Worker    Apart from the valid names according to the above, we also accept
236*60517a1eSAndroid Build Coastguard Worker    '{' and '}', which may be used as placeholders for stamping.
237*60517a1eSAndroid Build Coastguard Worker    """
238*60517a1eSAndroid Build Coastguard Worker    escaped = ""
239*60517a1eSAndroid Build Coastguard Worker    _inside_stamp_var = False
240*60517a1eSAndroid Build Coastguard Worker    for character in name.elems():
241*60517a1eSAndroid Build Coastguard Worker        if character == "{":
242*60517a1eSAndroid Build Coastguard Worker            _inside_stamp_var = True
243*60517a1eSAndroid Build Coastguard Worker            escaped += character
244*60517a1eSAndroid Build Coastguard Worker        elif character == "}":
245*60517a1eSAndroid Build Coastguard Worker            _inside_stamp_var = False
246*60517a1eSAndroid Build Coastguard Worker            escaped += character
247*60517a1eSAndroid Build Coastguard Worker        elif character.isalnum():
248*60517a1eSAndroid Build Coastguard Worker            escaped += character if _inside_stamp_var else character.lower()
249*60517a1eSAndroid Build Coastguard Worker        elif character in ["-", "_", "."]:
250*60517a1eSAndroid Build Coastguard Worker            if escaped == "":
251*60517a1eSAndroid Build Coastguard Worker                fail(
252*60517a1eSAndroid Build Coastguard Worker                    "A valid name must start with a letter or number.",
253*60517a1eSAndroid Build Coastguard Worker                    "Name '%s' does not." % name,
254*60517a1eSAndroid Build Coastguard Worker                )
255*60517a1eSAndroid Build Coastguard Worker            elif escaped.endswith("_"):
256*60517a1eSAndroid Build Coastguard Worker                pass
257*60517a1eSAndroid Build Coastguard Worker            else:
258*60517a1eSAndroid Build Coastguard Worker                escaped += "_"
259*60517a1eSAndroid Build Coastguard Worker        else:
260*60517a1eSAndroid Build Coastguard Worker            fail(
261*60517a1eSAndroid Build Coastguard Worker                "A valid name consists only of ASCII letters ",
262*60517a1eSAndroid Build Coastguard Worker                "and numbers, period, underscore and hyphen.",
263*60517a1eSAndroid Build Coastguard Worker                "Name '%s' has bad character '%s'." % (name, character),
264*60517a1eSAndroid Build Coastguard Worker            )
265*60517a1eSAndroid Build Coastguard Worker    if escaped.endswith("_"):
266*60517a1eSAndroid Build Coastguard Worker        fail(
267*60517a1eSAndroid Build Coastguard Worker            "A valid name must end with a letter or number.",
268*60517a1eSAndroid Build Coastguard Worker            "Name '%s' does not." % name,
269*60517a1eSAndroid Build Coastguard Worker        )
270*60517a1eSAndroid Build Coastguard Worker    return escaped
271*60517a1eSAndroid Build Coastguard Worker
272*60517a1eSAndroid Build Coastguard Workerdef _escape_filename_segment(segment):
273*60517a1eSAndroid Build Coastguard Worker    """Escape a segment of the wheel filename.
274*60517a1eSAndroid Build Coastguard Worker
275*60517a1eSAndroid Build Coastguard Worker    See https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode
276*60517a1eSAndroid Build Coastguard Worker    """
277*60517a1eSAndroid Build Coastguard Worker
278*60517a1eSAndroid Build Coastguard Worker    # TODO: this is wrong, isalnum replaces non-ascii letters, while we should
279*60517a1eSAndroid Build Coastguard Worker    # not replace them.
280*60517a1eSAndroid Build Coastguard Worker    # TODO: replace this with a regexp once starlark supports them.
281*60517a1eSAndroid Build Coastguard Worker    escaped = ""
282*60517a1eSAndroid Build Coastguard Worker    for character in segment.elems():
283*60517a1eSAndroid Build Coastguard Worker        # isalnum doesn't handle unicode characters properly.
284*60517a1eSAndroid Build Coastguard Worker        if character.isalnum() or character == ".":
285*60517a1eSAndroid Build Coastguard Worker            escaped += character
286*60517a1eSAndroid Build Coastguard Worker        elif not escaped.endswith("_"):
287*60517a1eSAndroid Build Coastguard Worker            escaped += "_"
288*60517a1eSAndroid Build Coastguard Worker    return escaped
289*60517a1eSAndroid Build Coastguard Worker
290*60517a1eSAndroid Build Coastguard Workerdef _replace_make_variables(flag, ctx):
291*60517a1eSAndroid Build Coastguard Worker    """Replace $(VERSION) etc make variables in flag"""
292*60517a1eSAndroid Build Coastguard Worker    if "$" in flag:
293*60517a1eSAndroid Build Coastguard Worker        for varname, varsub in ctx.var.items():
294*60517a1eSAndroid Build Coastguard Worker            flag = flag.replace("$(%s)" % varname, varsub)
295*60517a1eSAndroid Build Coastguard Worker    return flag
296*60517a1eSAndroid Build Coastguard Worker
297*60517a1eSAndroid Build Coastguard Workerdef _input_file_to_arg(input_file):
298*60517a1eSAndroid Build Coastguard Worker    """Converts a File object to string for --input_file argument to wheelmaker"""
299*60517a1eSAndroid Build Coastguard Worker    return "%s;%s" % (py_package_lib.path_inside_wheel(input_file), input_file.path)
300*60517a1eSAndroid Build Coastguard Worker
301*60517a1eSAndroid Build Coastguard Workerdef _py_wheel_impl(ctx):
302*60517a1eSAndroid Build Coastguard Worker    abi = _replace_make_variables(ctx.attr.abi, ctx)
303*60517a1eSAndroid Build Coastguard Worker    python_tag = _replace_make_variables(ctx.attr.python_tag, ctx)
304*60517a1eSAndroid Build Coastguard Worker    version = _replace_make_variables(ctx.attr.version, ctx)
305*60517a1eSAndroid Build Coastguard Worker
306*60517a1eSAndroid Build Coastguard Worker    filename_segments = [
307*60517a1eSAndroid Build Coastguard Worker        _escape_filename_distribution_name(ctx.attr.distribution),
308*60517a1eSAndroid Build Coastguard Worker        normalize_pep440(version),
309*60517a1eSAndroid Build Coastguard Worker        _escape_filename_segment(python_tag),
310*60517a1eSAndroid Build Coastguard Worker        _escape_filename_segment(abi),
311*60517a1eSAndroid Build Coastguard Worker        _escape_filename_segment(ctx.attr.platform),
312*60517a1eSAndroid Build Coastguard Worker    ]
313*60517a1eSAndroid Build Coastguard Worker
314*60517a1eSAndroid Build Coastguard Worker    outfile = ctx.actions.declare_file("-".join(filename_segments) + ".whl")
315*60517a1eSAndroid Build Coastguard Worker
316*60517a1eSAndroid Build Coastguard Worker    name_file = ctx.actions.declare_file(ctx.label.name + ".name")
317*60517a1eSAndroid Build Coastguard Worker
318*60517a1eSAndroid Build Coastguard Worker    inputs_to_package = depset(
319*60517a1eSAndroid Build Coastguard Worker        direct = ctx.files.deps,
320*60517a1eSAndroid Build Coastguard Worker    )
321*60517a1eSAndroid Build Coastguard Worker
322*60517a1eSAndroid Build Coastguard Worker    # Inputs to this rule which are not to be packaged.
323*60517a1eSAndroid Build Coastguard Worker    # Currently this is only the description file (if used).
324*60517a1eSAndroid Build Coastguard Worker    other_inputs = []
325*60517a1eSAndroid Build Coastguard Worker
326*60517a1eSAndroid Build Coastguard Worker    # Wrap the inputs into a file to reduce command line length.
327*60517a1eSAndroid Build Coastguard Worker    packageinputfile = ctx.actions.declare_file(ctx.attr.name + "_target_wrapped_inputs.txt")
328*60517a1eSAndroid Build Coastguard Worker    content = ""
329*60517a1eSAndroid Build Coastguard Worker    for input_file in inputs_to_package.to_list():
330*60517a1eSAndroid Build Coastguard Worker        content += _input_file_to_arg(input_file) + "\n"
331*60517a1eSAndroid Build Coastguard Worker    ctx.actions.write(output = packageinputfile, content = content)
332*60517a1eSAndroid Build Coastguard Worker    other_inputs.append(packageinputfile)
333*60517a1eSAndroid Build Coastguard Worker
334*60517a1eSAndroid Build Coastguard Worker    args = ctx.actions.args()
335*60517a1eSAndroid Build Coastguard Worker    args.add("--name", ctx.attr.distribution)
336*60517a1eSAndroid Build Coastguard Worker    args.add("--version", version)
337*60517a1eSAndroid Build Coastguard Worker    args.add("--python_tag", python_tag)
338*60517a1eSAndroid Build Coastguard Worker    args.add("--abi", abi)
339*60517a1eSAndroid Build Coastguard Worker    args.add("--platform", ctx.attr.platform)
340*60517a1eSAndroid Build Coastguard Worker    args.add("--out", outfile)
341*60517a1eSAndroid Build Coastguard Worker    args.add("--name_file", name_file)
342*60517a1eSAndroid Build Coastguard Worker    args.add_all(ctx.attr.strip_path_prefixes, format_each = "--strip_path_prefix=%s")
343*60517a1eSAndroid Build Coastguard Worker
344*60517a1eSAndroid Build Coastguard Worker    # Pass workspace status files if stamping is enabled
345*60517a1eSAndroid Build Coastguard Worker    if is_stamping_enabled(ctx.attr):
346*60517a1eSAndroid Build Coastguard Worker        args.add("--volatile_status_file", ctx.version_file)
347*60517a1eSAndroid Build Coastguard Worker        args.add("--stable_status_file", ctx.info_file)
348*60517a1eSAndroid Build Coastguard Worker        other_inputs.extend([ctx.version_file, ctx.info_file])
349*60517a1eSAndroid Build Coastguard Worker
350*60517a1eSAndroid Build Coastguard Worker    args.add("--input_file_list", packageinputfile)
351*60517a1eSAndroid Build Coastguard Worker
352*60517a1eSAndroid Build Coastguard Worker    # Note: Description file and version are not embedded into metadata.txt yet,
353*60517a1eSAndroid Build Coastguard Worker    # it will be done later by wheelmaker script.
354*60517a1eSAndroid Build Coastguard Worker    metadata_file = ctx.actions.declare_file(ctx.attr.name + ".metadata.txt")
355*60517a1eSAndroid Build Coastguard Worker    metadata_contents = ["Metadata-Version: 2.1"]
356*60517a1eSAndroid Build Coastguard Worker    metadata_contents.append("Name: %s" % ctx.attr.distribution)
357*60517a1eSAndroid Build Coastguard Worker
358*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.author:
359*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Author: %s" % ctx.attr.author)
360*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.author_email:
361*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Author-email: %s" % ctx.attr.author_email)
362*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.homepage:
363*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Home-page: %s" % ctx.attr.homepage)
364*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.license:
365*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("License: %s" % ctx.attr.license)
366*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.description_content_type:
367*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Description-Content-Type: %s" % ctx.attr.description_content_type)
368*60517a1eSAndroid Build Coastguard Worker    elif ctx.attr.description_file:
369*60517a1eSAndroid Build Coastguard Worker        # infer the content type from description file extension.
370*60517a1eSAndroid Build Coastguard Worker        description_file_type = _DESCRIPTION_FILE_EXTENSION_TO_TYPE.get(
371*60517a1eSAndroid Build Coastguard Worker            ctx.file.description_file.extension,
372*60517a1eSAndroid Build Coastguard Worker            _DEFAULT_DESCRIPTION_FILE_TYPE,
373*60517a1eSAndroid Build Coastguard Worker        )
374*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Description-Content-Type: %s" % description_file_type)
375*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.summary:
376*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Summary: %s" % ctx.attr.summary)
377*60517a1eSAndroid Build Coastguard Worker
378*60517a1eSAndroid Build Coastguard Worker    for label, url in sorted(ctx.attr.project_urls.items()):
379*60517a1eSAndroid Build Coastguard Worker        if len(label) > _PROJECT_URL_LABEL_LENGTH_LIMIT:
380*60517a1eSAndroid Build Coastguard Worker            fail("`label` {} in `project_urls` is too long. It is limited to {} characters.".format(len(label), _PROJECT_URL_LABEL_LENGTH_LIMIT))
381*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Project-URL: %s, %s" % (label, url))
382*60517a1eSAndroid Build Coastguard Worker
383*60517a1eSAndroid Build Coastguard Worker    for c in ctx.attr.classifiers:
384*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Classifier: %s" % c)
385*60517a1eSAndroid Build Coastguard Worker
386*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.python_requires:
387*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Requires-Python: %s" % ctx.attr.python_requires)
388*60517a1eSAndroid Build Coastguard Worker
389*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.requires and ctx.attr.requires_file:
390*60517a1eSAndroid Build Coastguard Worker        fail("`requires` and `requires_file` are mutually exclusive. Please update {}".format(ctx.label))
391*60517a1eSAndroid Build Coastguard Worker
392*60517a1eSAndroid Build Coastguard Worker    for requires in ctx.attr.requires:
393*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Requires-Dist: %s" % requires)
394*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.requires_file:
395*60517a1eSAndroid Build Coastguard Worker        # The @ prefixed paths will be resolved by the PyWheel action.
396*60517a1eSAndroid Build Coastguard Worker        # Expanding each line containing a constraint in place of this
397*60517a1eSAndroid Build Coastguard Worker        # directive.
398*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Requires-Dist: @%s" % ctx.file.requires_file.path)
399*60517a1eSAndroid Build Coastguard Worker        other_inputs.append(ctx.file.requires_file)
400*60517a1eSAndroid Build Coastguard Worker
401*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.extra_requires and ctx.attr.extra_requires_files:
402*60517a1eSAndroid Build Coastguard Worker        fail("`extra_requires` and `extra_requires_files` are mutually exclusive. Please update {}".format(ctx.label))
403*60517a1eSAndroid Build Coastguard Worker    for option, option_requirements in sorted(ctx.attr.extra_requires.items()):
404*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Provides-Extra: %s" % option)
405*60517a1eSAndroid Build Coastguard Worker        for requirement in option_requirements:
406*60517a1eSAndroid Build Coastguard Worker            metadata_contents.append(
407*60517a1eSAndroid Build Coastguard Worker                "Requires-Dist: %s; extra == '%s'" % (requirement, option),
408*60517a1eSAndroid Build Coastguard Worker            )
409*60517a1eSAndroid Build Coastguard Worker    extra_requires_files = {}
410*60517a1eSAndroid Build Coastguard Worker    for option_requires_target, option in ctx.attr.extra_requires_files.items():
411*60517a1eSAndroid Build Coastguard Worker        if option in extra_requires_files:
412*60517a1eSAndroid Build Coastguard Worker            fail("Duplicate `extra_requires_files` option '{}' found on target {}".format(option, ctx.label))
413*60517a1eSAndroid Build Coastguard Worker        option_requires_files = option_requires_target[DefaultInfo].files.to_list()
414*60517a1eSAndroid Build Coastguard Worker        if len(option_requires_files) != 1:
415*60517a1eSAndroid Build Coastguard Worker            fail("Labels in `extra_requires_files` must result in a single file, but {label} provides {files} from {owner}".format(
416*60517a1eSAndroid Build Coastguard Worker                label = ctx.label,
417*60517a1eSAndroid Build Coastguard Worker                files = option_requires_files,
418*60517a1eSAndroid Build Coastguard Worker                owner = option_requires_target.label,
419*60517a1eSAndroid Build Coastguard Worker            ))
420*60517a1eSAndroid Build Coastguard Worker        extra_requires_files.update({option: option_requires_files[0]})
421*60517a1eSAndroid Build Coastguard Worker
422*60517a1eSAndroid Build Coastguard Worker    for option, option_requires_file in sorted(extra_requires_files.items()):
423*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append("Provides-Extra: %s" % option)
424*60517a1eSAndroid Build Coastguard Worker        metadata_contents.append(
425*60517a1eSAndroid Build Coastguard Worker            # The @ prefixed paths will be resolved by the PyWheel action.
426*60517a1eSAndroid Build Coastguard Worker            # Expanding each line containing a constraint in place of this
427*60517a1eSAndroid Build Coastguard Worker            # directive and appending the extra option.
428*60517a1eSAndroid Build Coastguard Worker            "Requires-Dist: @%s; extra == '%s'" % (option_requires_file.path, option),
429*60517a1eSAndroid Build Coastguard Worker        )
430*60517a1eSAndroid Build Coastguard Worker        other_inputs.append(option_requires_file)
431*60517a1eSAndroid Build Coastguard Worker
432*60517a1eSAndroid Build Coastguard Worker    ctx.actions.write(
433*60517a1eSAndroid Build Coastguard Worker        output = metadata_file,
434*60517a1eSAndroid Build Coastguard Worker        content = "\n".join(metadata_contents) + "\n",
435*60517a1eSAndroid Build Coastguard Worker    )
436*60517a1eSAndroid Build Coastguard Worker    other_inputs.append(metadata_file)
437*60517a1eSAndroid Build Coastguard Worker    args.add("--metadata_file", metadata_file)
438*60517a1eSAndroid Build Coastguard Worker
439*60517a1eSAndroid Build Coastguard Worker    # Merge console_scripts into entry_points.
440*60517a1eSAndroid Build Coastguard Worker    entrypoints = dict(ctx.attr.entry_points)  # Copy so we can mutate it
441*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.console_scripts:
442*60517a1eSAndroid Build Coastguard Worker        # Copy a console_scripts group that may already exist, so we can mutate it.
443*60517a1eSAndroid Build Coastguard Worker        console_scripts = list(entrypoints.get("console_scripts", []))
444*60517a1eSAndroid Build Coastguard Worker        entrypoints["console_scripts"] = console_scripts
445*60517a1eSAndroid Build Coastguard Worker        for name, ref in ctx.attr.console_scripts.items():
446*60517a1eSAndroid Build Coastguard Worker            console_scripts.append("{name} = {ref}".format(name = name, ref = ref))
447*60517a1eSAndroid Build Coastguard Worker
448*60517a1eSAndroid Build Coastguard Worker    # If any entry_points are provided, construct the file here and add it to the files to be packaged.
449*60517a1eSAndroid Build Coastguard Worker    # see: https://packaging.python.org/specifications/entry-points/
450*60517a1eSAndroid Build Coastguard Worker    if entrypoints:
451*60517a1eSAndroid Build Coastguard Worker        lines = []
452*60517a1eSAndroid Build Coastguard Worker        for group, entries in sorted(entrypoints.items()):
453*60517a1eSAndroid Build Coastguard Worker            if lines:
454*60517a1eSAndroid Build Coastguard Worker                # Blank line between groups
455*60517a1eSAndroid Build Coastguard Worker                lines.append("")
456*60517a1eSAndroid Build Coastguard Worker            lines.append("[{group}]".format(group = group))
457*60517a1eSAndroid Build Coastguard Worker            lines += sorted(entries)
458*60517a1eSAndroid Build Coastguard Worker        entry_points_file = ctx.actions.declare_file(ctx.attr.name + "_entry_points.txt")
459*60517a1eSAndroid Build Coastguard Worker        content = "\n".join(lines)
460*60517a1eSAndroid Build Coastguard Worker        ctx.actions.write(output = entry_points_file, content = content)
461*60517a1eSAndroid Build Coastguard Worker        other_inputs.append(entry_points_file)
462*60517a1eSAndroid Build Coastguard Worker        args.add("--entry_points_file", entry_points_file)
463*60517a1eSAndroid Build Coastguard Worker
464*60517a1eSAndroid Build Coastguard Worker    if ctx.attr.description_file:
465*60517a1eSAndroid Build Coastguard Worker        description_file = ctx.file.description_file
466*60517a1eSAndroid Build Coastguard Worker        args.add("--description_file", description_file)
467*60517a1eSAndroid Build Coastguard Worker        other_inputs.append(description_file)
468*60517a1eSAndroid Build Coastguard Worker
469*60517a1eSAndroid Build Coastguard Worker    for target, filename in ctx.attr.extra_distinfo_files.items():
470*60517a1eSAndroid Build Coastguard Worker        target_files = target.files.to_list()
471*60517a1eSAndroid Build Coastguard Worker        if len(target_files) != 1:
472*60517a1eSAndroid Build Coastguard Worker            fail(
473*60517a1eSAndroid Build Coastguard Worker                "Multi-file target listed in extra_distinfo_files %s",
474*60517a1eSAndroid Build Coastguard Worker                filename,
475*60517a1eSAndroid Build Coastguard Worker            )
476*60517a1eSAndroid Build Coastguard Worker        other_inputs.extend(target_files)
477*60517a1eSAndroid Build Coastguard Worker        args.add(
478*60517a1eSAndroid Build Coastguard Worker            "--extra_distinfo_file",
479*60517a1eSAndroid Build Coastguard Worker            filename + ";" + target_files[0].path,
480*60517a1eSAndroid Build Coastguard Worker        )
481*60517a1eSAndroid Build Coastguard Worker
482*60517a1eSAndroid Build Coastguard Worker    for target, filename in ctx.attr.data_files.items():
483*60517a1eSAndroid Build Coastguard Worker        target_files = target.files.to_list()
484*60517a1eSAndroid Build Coastguard Worker        if len(target_files) != 1:
485*60517a1eSAndroid Build Coastguard Worker            fail(
486*60517a1eSAndroid Build Coastguard Worker                "Multi-file target listed in data_files %s",
487*60517a1eSAndroid Build Coastguard Worker                filename,
488*60517a1eSAndroid Build Coastguard Worker            )
489*60517a1eSAndroid Build Coastguard Worker
490*60517a1eSAndroid Build Coastguard Worker        if filename.partition("/")[0] not in ALLOWED_DATA_FILE_PREFIX:
491*60517a1eSAndroid Build Coastguard Worker            fail(
492*60517a1eSAndroid Build Coastguard Worker                "The target data file must start with one of these prefixes: '%s'.  Target filepath: '%s'" %
493*60517a1eSAndroid Build Coastguard Worker                (
494*60517a1eSAndroid Build Coastguard Worker                    ",".join(ALLOWED_DATA_FILE_PREFIX),
495*60517a1eSAndroid Build Coastguard Worker                    filename,
496*60517a1eSAndroid Build Coastguard Worker                ),
497*60517a1eSAndroid Build Coastguard Worker            )
498*60517a1eSAndroid Build Coastguard Worker        other_inputs.extend(target_files)
499*60517a1eSAndroid Build Coastguard Worker        args.add(
500*60517a1eSAndroid Build Coastguard Worker            "--data_files",
501*60517a1eSAndroid Build Coastguard Worker            filename + ";" + target_files[0].path,
502*60517a1eSAndroid Build Coastguard Worker        )
503*60517a1eSAndroid Build Coastguard Worker
504*60517a1eSAndroid Build Coastguard Worker    ctx.actions.run(
505*60517a1eSAndroid Build Coastguard Worker        mnemonic = "PyWheel",
506*60517a1eSAndroid Build Coastguard Worker        inputs = depset(direct = other_inputs, transitive = [inputs_to_package]),
507*60517a1eSAndroid Build Coastguard Worker        outputs = [outfile, name_file],
508*60517a1eSAndroid Build Coastguard Worker        arguments = [args],
509*60517a1eSAndroid Build Coastguard Worker        executable = ctx.executable._wheelmaker,
510*60517a1eSAndroid Build Coastguard Worker        progress_message = "Building wheel {}".format(ctx.label),
511*60517a1eSAndroid Build Coastguard Worker    )
512*60517a1eSAndroid Build Coastguard Worker    return [
513*60517a1eSAndroid Build Coastguard Worker        DefaultInfo(
514*60517a1eSAndroid Build Coastguard Worker            files = depset([outfile]),
515*60517a1eSAndroid Build Coastguard Worker            runfiles = ctx.runfiles(files = [outfile]),
516*60517a1eSAndroid Build Coastguard Worker        ),
517*60517a1eSAndroid Build Coastguard Worker        PyWheelInfo(
518*60517a1eSAndroid Build Coastguard Worker            wheel = outfile,
519*60517a1eSAndroid Build Coastguard Worker            name_file = name_file,
520*60517a1eSAndroid Build Coastguard Worker        ),
521*60517a1eSAndroid Build Coastguard Worker    ]
522*60517a1eSAndroid Build Coastguard Worker
523*60517a1eSAndroid Build Coastguard Workerdef _concat_dicts(*dicts):
524*60517a1eSAndroid Build Coastguard Worker    result = {}
525*60517a1eSAndroid Build Coastguard Worker    for d in dicts:
526*60517a1eSAndroid Build Coastguard Worker        result.update(d)
527*60517a1eSAndroid Build Coastguard Worker    return result
528*60517a1eSAndroid Build Coastguard Worker
529*60517a1eSAndroid Build Coastguard Workerpy_wheel_lib = struct(
530*60517a1eSAndroid Build Coastguard Worker    implementation = _py_wheel_impl,
531*60517a1eSAndroid Build Coastguard Worker    attrs = _concat_dicts(
532*60517a1eSAndroid Build Coastguard Worker        {
533*60517a1eSAndroid Build Coastguard Worker            "deps": attr.label_list(
534*60517a1eSAndroid Build Coastguard Worker                doc = """\
535*60517a1eSAndroid Build Coastguard WorkerTargets to be included in the distribution.
536*60517a1eSAndroid Build Coastguard Worker
537*60517a1eSAndroid Build Coastguard WorkerThe targets to package are usually `py_library` rules or filesets (for packaging data files).
538*60517a1eSAndroid Build Coastguard Worker
539*60517a1eSAndroid Build Coastguard WorkerNote it's usually better to package `py_library` targets and use
540*60517a1eSAndroid Build Coastguard Worker`entry_points` attribute to specify `console_scripts` than to package
541*60517a1eSAndroid Build Coastguard Worker`py_binary` rules. `py_binary` targets would wrap a executable script that
542*60517a1eSAndroid Build Coastguard Workertries to locate `.runfiles` directory which is not packaged in the wheel.
543*60517a1eSAndroid Build Coastguard Worker""",
544*60517a1eSAndroid Build Coastguard Worker            ),
545*60517a1eSAndroid Build Coastguard Worker            "_wheelmaker": attr.label(
546*60517a1eSAndroid Build Coastguard Worker                executable = True,
547*60517a1eSAndroid Build Coastguard Worker                cfg = "exec",
548*60517a1eSAndroid Build Coastguard Worker                default = "//tools:wheelmaker",
549*60517a1eSAndroid Build Coastguard Worker            ),
550*60517a1eSAndroid Build Coastguard Worker        },
551*60517a1eSAndroid Build Coastguard Worker        _distribution_attrs,
552*60517a1eSAndroid Build Coastguard Worker        _feature_flags,
553*60517a1eSAndroid Build Coastguard Worker        _requirement_attrs,
554*60517a1eSAndroid Build Coastguard Worker        _entrypoint_attrs,
555*60517a1eSAndroid Build Coastguard Worker        _other_attrs,
556*60517a1eSAndroid Build Coastguard Worker    ),
557*60517a1eSAndroid Build Coastguard Worker)
558*60517a1eSAndroid Build Coastguard Worker
559*60517a1eSAndroid Build Coastguard Workerpy_wheel = rule(
560*60517a1eSAndroid Build Coastguard Worker    implementation = py_wheel_lib.implementation,
561*60517a1eSAndroid Build Coastguard Worker    doc = """\
562*60517a1eSAndroid Build Coastguard WorkerInternal rule used by the [py_wheel macro](#py_wheel).
563*60517a1eSAndroid Build Coastguard Worker
564*60517a1eSAndroid Build Coastguard WorkerThese intentionally have the same name to avoid sharp edges with Bazel macros.
565*60517a1eSAndroid Build Coastguard WorkerFor example, a `bazel query` for a user's `py_wheel` macro expands to `py_wheel` targets,
566*60517a1eSAndroid Build Coastguard Workerin the way they expect.
567*60517a1eSAndroid Build Coastguard Worker""",
568*60517a1eSAndroid Build Coastguard Worker    attrs = py_wheel_lib.attrs,
569*60517a1eSAndroid Build Coastguard Worker)
570