xref: /aosp_15_r20/external/bazelbuild-rules_python/examples/wheel/BUILD.bazel (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//rules:build_test.bzl", "build_test")
16load("@bazel_skylib//rules:write_file.bzl", "write_file")
17load("//examples/wheel/private:wheel_utils.bzl", "directory_writer", "make_variable_tags")
18load("//python:defs.bzl", "py_library", "py_test")
19load("//python:packaging.bzl", "py_package", "py_wheel")
20load("//python:pip.bzl", "compile_pip_requirements")
21load("//python:versions.bzl", "gen_python_config_settings")
22load("//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
23load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")  # buildifier: disable=bzl-visibility
24
25package(default_visibility = ["//visibility:public"])
26
27licenses(["notice"])  # Apache 2.0
28
29py_library(
30    name = "main",
31    srcs = ["main.py"],
32    deps = [
33        "//examples/wheel/lib:simple_module",
34        "//examples/wheel/lib:module_with_data",
35        # Example dependency which is not packaged in the wheel
36        # due to "packages" filter on py_package rule.
37        "//tests/load_from_macro:foo",
38    ],
39)
40
41py_library(
42    name = "main_with_gen_data",
43    srcs = ["main.py"],
44    data = [
45        ":gen_dir",
46    ],
47)
48
49directory_writer(
50    name = "gen_dir",
51    out = "someDir",
52    files = {"foo.py": ""},
53)
54
55# Package just a specific py_libraries, without their dependencies
56py_wheel(
57    name = "minimal_with_py_library",
58    testonly = True,  # Set this to verify the generated .dist target doesn't break things
59    # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
60    distribution = "example_minimal_library",
61    python_tag = "py3",
62    # NOTE: twine_binary = "//tools/publish:twine" does not work on non-bzlmod
63    # setups because the `//tools/publish:twine` produces multiple files and is
64    # unsuitable as the `src` to the underlying native_binary rule.
65    twine = None if BZLMOD_ENABLED else "@rules_python_publish_deps_twine//:pkg",
66    version = "0.0.1",
67    deps = [
68        "//examples/wheel/lib:module_with_data",
69        "//examples/wheel/lib:simple_module",
70    ],
71)
72
73# Populate a rule with "Make Variable" arguments for
74# abi, python_tag and version. You might want to do this
75# for the following use cases:
76#  - abi, python_tag: introspect a toolchain to map to appropriate cpython tags
77#  - version: populate given this or a dependent module's version
78make_variable_tags(
79    name = "make_variable_tags",
80)
81
82py_wheel(
83    name = "minimal_with_py_library_with_make_variables",
84    testonly = True,
85    abi = "$(ABI)",
86    distribution = "example_minimal_library",
87    python_tag = "$(PYTHON_TAG)",
88    toolchains = ["//examples/wheel:make_variable_tags"],
89    version = "$(VERSION)",
90    deps = [
91        "//examples/wheel/lib:module_with_data",
92        "//examples/wheel/lib:simple_module",
93    ],
94)
95
96build_test(
97    name = "dist_build_tests",
98    targets = [":minimal_with_py_library.dist"],
99)
100
101# Package just a specific py_libraries, without their dependencies
102py_wheel(
103    name = "minimal_with_py_library_with_stamp",
104    # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl"
105    distribution = "example_minimal_library{BUILD_USER}",
106    python_tag = "py3",
107    stamp = 1,
108    version = "0.1.{BUILD_TIMESTAMP}",
109    deps = [
110        "//examples/wheel/lib:module_with_data",
111        "//examples/wheel/lib:simple_module",
112    ],
113)
114
115# Use py_package to collect all transitive dependencies of a target,
116# selecting just the files within a specific python package.
117py_package(
118    name = "example_pkg",
119    # Only include these Python packages.
120    packages = ["examples.wheel"],
121    deps = [":main"],
122)
123
124py_package(
125    name = "example_pkg_with_data",
126    packages = ["examples.wheel"],
127    deps = [":main_with_gen_data"],
128)
129
130py_wheel(
131    name = "minimal_with_py_package",
132    # Package data. We're building "example_minimal_package-0.0.1-py3-none-any.whl"
133    distribution = "example_minimal_package",
134    python_tag = "py3",
135    version = "0.0.1",
136    deps = [":example_pkg"],
137)
138
139# An example that uses all features provided by py_wheel.
140py_wheel(
141    name = "customized",
142    author = "Example Author with non-ascii characters: żółw",
143    author_email = "[email protected]",
144    classifiers = [
145        "License :: OSI Approved :: Apache Software License",
146        "Intended Audience :: Developers",
147    ],
148    console_scripts = {
149        "customized_wheel": "examples.wheel.main:main",
150    },
151    description_file = "README.md",
152    # Package data. We're building "example_customized-0.0.1-py3-none-any.whl"
153    distribution = "example_customized",
154    entry_points = {
155        "console_scripts": ["another = foo.bar:baz"],
156        "group2": [
157            "second = second.main:s",
158            "first = first.main:f",
159        ],
160    },
161    extra_distinfo_files = {
162        "//examples/wheel:NOTICE": "NOTICE",
163        # Rename the file when packaging to show we can.
164        "//examples/wheel:README.md": "README",
165    },
166    homepage = "www.example.com",
167    license = "Apache 2.0",
168    project_urls = {
169        "Bug Tracker": "www.example.com/issues",
170        "Documentation": "www.example.com/docs",
171    },
172    python_tag = "py3",
173    # Requirements embedded into the wheel metadata.
174    requires = ["pytest"],
175    summary = "A one-line summary of this test package",
176    version = "0.0.1",
177    deps = [":example_pkg"],
178)
179
180# An example of how to change the wheel package root directory using 'strip_path_prefixes'.
181py_wheel(
182    name = "custom_package_root",
183    # Package data. We're building "examples_custom_package_root-0.0.1-py3-none-any.whl"
184    distribution = "examples_custom_package_root",
185    entry_points = {
186        "console_scripts": ["main = foo.bar:baz"],
187    },
188    python_tag = "py3",
189    strip_path_prefixes = [
190        "examples",
191    ],
192    version = "0.0.1",
193    deps = [
194        ":example_pkg",
195    ],
196)
197
198py_wheel(
199    name = "custom_package_root_multi_prefix",
200    # Package data. We're building "custom_custom_package_root_multi_prefix-0.0.1-py3-none-any.whl"
201    distribution = "example_custom_package_root_multi_prefix",
202    python_tag = "py3",
203    strip_path_prefixes = [
204        "examples/wheel/lib",
205        "examples/wheel",
206    ],
207    version = "0.0.1",
208    deps = [
209        ":example_pkg",
210    ],
211)
212
213py_wheel(
214    name = "custom_package_root_multi_prefix_reverse_order",
215    # Package data. We're building "custom_custom_package_root_multi_prefix_reverse_order-0.0.1-py3-none-any.whl"
216    distribution = "example_custom_package_root_multi_prefix_reverse_order",
217    python_tag = "py3",
218    strip_path_prefixes = [
219        "examples/wheel",
220        "examples/wheel/lib",  # this is not effective, because the first prefix takes priority
221    ],
222    version = "0.0.1",
223    deps = [
224        ":example_pkg",
225    ],
226)
227
228py_wheel(
229    name = "python_requires_in_a_package",
230    distribution = "example_python_requires_in_a_package",
231    python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
232    python_tag = "py3",
233    version = "0.0.1",
234    deps = [
235        ":example_pkg",
236    ],
237)
238
239py_wheel(
240    name = "use_rule_with_dir_in_outs",
241    distribution = "use_rule_with_dir_in_outs",
242    python_tag = "py3",
243    version = "0.0.1",
244    deps = [
245        ":example_pkg_with_data",
246    ],
247)
248
249gen_python_config_settings()
250
251py_wheel(
252    name = "python_abi3_binary_wheel",
253    abi = "abi3",
254    distribution = "example_python_abi3_binary_wheel",
255    # these platform strings must line up with test_python_abi3_binary_wheel() in wheel_test.py
256    platform = select({
257        ":aarch64-apple-darwin": "macosx_11_0_arm64",
258        ":aarch64-unknown-linux-gnu": "manylinux2014_aarch64",
259        ":x86_64-apple-darwin": "macosx_11_0_x86_64",  # this is typically macosx_10_9_x86_64?
260        ":x86_64-pc-windows-msvc": "win_amd64",
261        ":x86_64-unknown-linux-gnu": "manylinux2014_x86_64",
262    }),
263    python_requires = ">=3.8",
264    python_tag = "cp38",
265    version = "0.0.1",
266)
267
268py_wheel(
269    name = "filename_escaping",
270    # Per https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
271    # runs of "-", "_" and "." should be replaced with a single underscore.
272    # Unicode non-ascii letters aren't allowed according to
273    # https://packaging.python.org/en/latest/specifications/name-normalization/.
274    distribution = "File--Name-Escaping",
275    python_tag = "py3",
276    version = "v0.0.1.RC1+ubuntu-r7",
277    deps = [":example_pkg"],
278)
279
280write_file(
281    name = "requires_file",
282    out = "requires.txt",
283    content = """\
284# Requirements file
285--index-url https://pypi.com
286
287tomli>=2.0.0
288starlark  # Example comment
289""".splitlines(),
290)
291
292write_file(
293    name = "extra_requires_file",
294    out = "extra_requires.txt",
295    content = """\
296# Extras Requirements file
297--index-url https://pypi.com
298
299pyyaml>=6.0.0,!=6.0.1
300toml; (python_version == "3.11" or python_version == "3.12") and python_version != "3.8"
301wheel; python_version == "3.11" or python_version == "3.12"  # Example comment
302""".splitlines(),
303)
304
305# py_wheel can use text files to specify their requirements. This
306# can be convenient for users of `compile_pip_requirements` who have
307# granular `requirements.in` files per package. This target shows
308# how to provide this file.
309py_wheel(
310    name = "requires_files",
311    distribution = "requires_files",
312    extra_requires_files = {":extra_requires.txt": "example"},
313    python_tag = "py3",
314    # py_wheel can use text files to specify their requirements. This
315    # can be convenient for users of `compile_pip_requirements` who have
316    # granular `requirements.in` files per package.
317    requires_file = ":requires.txt",
318    version = "0.0.1",
319    deps = [":example_pkg"],
320)
321
322# Package just a specific py_libraries, without their dependencies
323py_wheel(
324    name = "minimal_data_files",
325    testonly = True,  # Set this to verify the generated .dist target doesn't break things
326
327    # Re-using some files already checked into the repo.
328    data_files = {
329        "//examples/wheel:NOTICE": "scripts/NOTICE",
330        "README.md": "data/target/path/README.md",
331    },
332    distribution = "minimal_data_files",
333    version = "0.0.1",
334)
335
336py_wheel(
337    name = "extra_requires",
338    distribution = "extra_requires",
339    extra_requires = {"example": [
340        "pyyaml>=6.0.0,!=6.0.1",
341        'toml; (python_version == "3.11" or python_version == "3.12") and python_version != "3.8"',
342        'wheel; python_version == "3.11" or python_version == "3.12" ',
343    ]},
344    python_tag = "py3",
345    # py_wheel can use text files to specify their requirements. This
346    # can be convenient for users of `compile_pip_requirements` who have
347    # granular `requirements.in` files per package.
348    requires = [
349        "tomli>=2.0.0",
350        "starlark",
351        'pytest; python_version != "3.8"',
352    ],
353    version = "0.0.1",
354    deps = [":example_pkg"],
355)
356
357py_test(
358    name = "wheel_test",
359    srcs = ["wheel_test.py"],
360    data = [
361        ":custom_package_root",
362        ":custom_package_root_multi_prefix",
363        ":custom_package_root_multi_prefix_reverse_order",
364        ":customized",
365        ":extra_requires",
366        ":filename_escaping",
367        ":minimal_data_files",
368        ":minimal_with_py_library",
369        ":minimal_with_py_library_with_stamp",
370        ":minimal_with_py_package",
371        ":python_abi3_binary_wheel",
372        ":python_requires_in_a_package",
373        ":requires_files",
374        ":use_rule_with_dir_in_outs",
375    ],
376    deps = [
377        "//python/runfiles",
378    ],
379)
380
381# Test wheel publishing
382
383compile_pip_requirements(
384    name = "requirements_server",
385    src = "requirements_server.in",
386)
387
388py_test(
389    name = "test_publish",
390    srcs = ["test_publish.py"],
391    data = [
392        ":minimal_with_py_library",
393        ":minimal_with_py_library.publish",
394        ":pypiserver",
395    ],
396    env = {
397        "PUBLISH_PATH": "$(location :minimal_with_py_library.publish)",
398        "SERVER_PATH": "$(location :pypiserver)",
399        "WHEEL_PATH": "$(rootpath :minimal_with_py_library)",
400    },
401    target_compatible_with = select({
402        "@platforms//os:linux": [],
403        "@platforms//os:macos": [],
404        "//conditions:default": ["@platforms//:incompatible"],
405    }),
406    deps = [
407        "@pypiserver//pypiserver",
408    ],
409)
410
411py_console_script_binary(
412    name = "pypiserver",
413    pkg = "@pypiserver//pypiserver",
414    script = "pypi-server",
415)
416