Skip to content

Commit bed8c1b

Browse files
f0rmigaalexeagleUebelAndre
authored
feat: cpython toolchains (bazel-contrib#618)
* feat: cpython toolchains for linux and macos Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: compile zstd if missing Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: buildifier Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: make python_repositories reproducible Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * rename: python_repositories -> python_repository Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: linter Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: make interpreter files publicly visible Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: add files to py_runtime Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Account for some platforms not having all versions * Added windows support to hermetic toolchains (bazel-contrib#628) * Added windows support to hermetic toolchains * Update python/repositories.bzl Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Update python/repositories.bzl Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Update python/repositories.bzl Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Update python/repositories.bzl Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * refactor: simplify logic for release urls Also, added a helper target to print the release hashes. Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: Provide a host platform alias (bazel-contrib#635) * feat: Provide a host platform alias This lets users and repository rules access the interpreter for whatever host the repository is running on. * Apply suggestions from code review Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: files excludes Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: macOS dislikes --recursive Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: buildifier issues Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Allow previous indygreg releases (bazel-contrib#636) This gives us more python patch versions * fix: put back zstd support for older releases Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: hash calculator Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: use hermetic interpreter with pip_parse and pip_install Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: add missing attrs back for zstd Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: expose zstd attributes Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: normalize OS names Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: linting issues Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: support windows in the aliases Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: linting issues Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: windows python.exe instead of python3.exe Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: use consts for OS names Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: always use latest toolchain for test Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: expose versions.bzl Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * refactor: move toolchain tests out of private Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: acceptance tests for the toolchains Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: rewrite test in py to work on windows Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: README example Co-authored-by: UebelAndre <github@uebelandre.com> * fix: use toolchain to run acceptance tests Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: use matrix for acceptance tests Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: support acceptance_tests on windows Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: alias for pip Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix?: include call to windows cmd Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Fix windows acceptance tests (bazel-contrib#641) * Fix windows acceptance tests * test * todo: remove Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * refactor: polishing Windows testing support Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: unset py2_runtime Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * rename: host -> resolved_interpreter Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * doc: add reference to quirks in python-build-standalone Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * feat: allow a distutils.cfg to be passed Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: buildifier (again) Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * Minor code review suggestions (bazel-contrib#642) * Minor code review suggestions * Apply suggestions from code review Co-authored-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> * fix: depset concat Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com> Co-authored-by: Alex Eagle <alex@aspect.dev> Co-authored-by: UebelAndre <github@uebelandre.com>
1 parent 79cebad commit bed8c1b

19 files changed

+1023
-11
lines changed

.bazelrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ build --incompatible_default_to_explicit_init_py
1818

1919
# Windows makes use of runfiles for some rules
2020
build --enable_runfiles
21+
# TODO(f0rmiga): remove this so that other features don't start relying on it.
22+
startup --windows_enable_symlinks

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,31 @@ http_archive(
5454
)
5555
```
5656

57+
To register a hermetic Python toolchain rather than rely on whatever is already on the machine, you can add to the `WORKSPACE` file:
58+
59+
```python
60+
load("@rules_python//python:repositories.bzl", "python_register_toolchains")
61+
62+
python_register_toolchains(
63+
name = "python310",
64+
# Available versions are listed in @rules_python//python:versions.bzl.
65+
python_version = "3.10",
66+
)
67+
68+
load("@python310_resolved_interpreter//:defs.bzl", "interpreter")
69+
70+
load("@rules_python//python:pip.bzl", "pip_parse")
71+
72+
pip_parse(
73+
...
74+
python_interpreter_target = interpreter,
75+
...
76+
)
77+
```
78+
79+
> You may find some quirks while using this toolchain.
80+
> Please refer to [this link](https://python-build-standalone.readthedocs.io/en/latest/quirks.html) for details.
81+
5782
Once you've imported the rule set into your `WORKSPACE` using any of these
5883
methods, you can then load the core rules in your `BUILD` files with:
5984

@@ -109,8 +134,8 @@ one another, and may result in downloading the same wheels multiple times.
109134

110135
As with any repository rule, if you would like to ensure that `pip_install` is
111136
re-executed in order to pick up a non-hermetic change to your environment (e.g.,
112-
updating your system `python` interpreter), you can completely flush out your
113-
repo cache with `bazel clean --expunge`.
137+
updating your system `python` interpreter), you can force it to re-execute by running
138+
`bazel sync --only [pip_install name]`.
114139

115140
### Fetch `pip` dependencies lazily
116141

WORKSPACE

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ load("//:internal_setup.bzl", "rules_python_internal_setup")
2525

2626
rules_python_internal_setup()
2727

28+
load("//python:repositories.bzl", "python_register_toolchains")
29+
load("//python:versions.bzl", "MINOR_MAPPING")
30+
31+
python_register_toolchains(
32+
name = "python",
33+
# We always use the latest Python internally.
34+
python_version = MINOR_MAPPING.values()[-1],
35+
)
36+
2837
load("//gazelle:deps.bzl", "gazelle_deps")
2938

3039
# gazelle:repository_macro gazelle/deps.bzl%gazelle_deps

examples/pip_install/WORKSPACE

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ local_repository(
1616
path = "../..",
1717
)
1818

19+
load("@rules_python//python:repositories.bzl", "python_register_toolchains")
20+
21+
python_register_toolchains(
22+
name = "python39",
23+
python_version = "3.9",
24+
)
25+
26+
load("@python39_resolved_interpreter//:defs.bzl", "interpreter")
1927
load("@rules_python//python:pip.bzl", "pip_install")
2028

2129
pip_install(
@@ -32,7 +40,9 @@ pip_install(
3240
# 1. Python interpreter that you compile in the build file (as above in @python_interpreter).
3341
# 2. Pre-compiled python interpreter included with http_archive
3442
# 3. Wrapper script, like in the autodetecting python toolchain.
35-
#python_interpreter_target = "@python_interpreter//:python_bin",
43+
#
44+
# Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
45+
python_interpreter_target = interpreter,
3646

3747
# (Optional) You can set quiet to False if you want to see pip output.
3848
#quiet = False,

examples/pip_parse/WORKSPACE

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,23 @@ local_repository(
55
path = "../..",
66
)
77

8+
load("@rules_python//python:repositories.bzl", "python_register_toolchains")
9+
10+
python_register_toolchains(
11+
name = "python39",
12+
python_version = "3.9",
13+
)
14+
15+
load("@python39_resolved_interpreter//:defs.bzl", "interpreter")
816
load("@rules_python//python:pip.bzl", "pip_parse")
917

1018
pip_parse(
19+
# (Optional) You can set an environment in the pip process to control its
20+
# behavior. Note that pip is run in "isolated" mode so no PIP_<VAR>_<NAME>
21+
# style env vars are read, but env vars that control requests and urllib3
22+
# can be passed
23+
# environment = {"HTTPS_PROXY": "http://my.proxy.fun/"},
24+
name = "pypi",
1125
# (Optional) You can provide extra parameters to pip.
1226
# Here, make pip output verbose (this is usable with `quiet = False`).
1327
# extra_pip_args = ["-v"],
@@ -21,17 +35,12 @@ pip_parse(
2135
# 1. Python interpreter that you compile in the build file (as above in @python_interpreter).
2236
# 2. Pre-compiled python interpreter included with http_archive
2337
# 3. Wrapper script, like in the autodetecting python toolchain.
24-
#python_interpreter_target = "@python_interpreter//:python_bin",
38+
#
39+
# Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
40+
python_interpreter_target = interpreter,
2541

2642
# (Optional) You can set quiet to False if you want to see pip output.
2743
#quiet = False,
28-
29-
# (Optional) You can set an environment in the pip process to control its
30-
# behavior. Note that pip is run in "isolated" mode so no PIP_<VAR>_<NAME>
31-
# style env vars are read, but env vars that control requests and urllib3
32-
# can be passed
33-
# environment = {"HTTPS_PROXY": "http://my.proxy.fun/"},
34-
name = "pypi",
3544
requirements_lock = "//:requirements_lock.txt",
3645
)
3746

internal_setup.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
# Copyright 2022 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+
115
"""Setup for rules_python tests and tools."""
216

317
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
18+
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
419
load("@build_bazel_integration_testing//tools:repositories.bzl", "bazel_binaries")
520
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
621
load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS")
@@ -16,6 +31,8 @@ def rules_python_internal_setup():
1631
# Depend on the Bazel binaries for running bazel-in-bazel tests
1732
bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS)
1833

34+
bazel_skylib_workspace()
35+
1936
# gazelle:repository_macro gazelle/deps.bzl%gazelle_deps
2037
_go_repositories()
2138

python/private/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
load("//python:versions.bzl", "print_toolchains_checksums")
1516
load(":stamp.bzl", "stamp_build_setting")
1617

1718
licenses(["notice"]) # Apache 2.0
@@ -43,3 +44,5 @@ exports_files(
4344

4445
# Used to determine the use of `--stamp` in Starlark rules
4546
stamp_build_setting(name = "stamp")
47+
48+
print_toolchains_checksums(name = "print_toolchains_checksums")

python/private/toolchains_repo.bzl

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copyright 2022 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+
15+
"""Create a repository to hold the toolchains.
16+
17+
This follows guidance here:
18+
https://docs.bazel.build/versions/main/skylark/deploying.html#registering-toolchains
19+
20+
The "complex computation" in our case is simply downloading large artifacts.
21+
This guidance tells us how to avoid that: we put the toolchain targets in the
22+
alias repository with only the toolchain attribute pointing into the
23+
platform-specific repositories.
24+
"""
25+
26+
load(
27+
"//python:versions.bzl",
28+
"LINUX_NAME",
29+
"MACOS_NAME",
30+
"PLATFORMS",
31+
"WINDOWS_NAME",
32+
)
33+
34+
def _toolchains_repo_impl(rctx):
35+
build_content = """\
36+
# Generated by toolchains_repo.bzl
37+
#
38+
# These can be registered in the workspace file or passed to --extra_toolchains
39+
# flag. By default all these toolchains are registered by the
40+
# python_register_toolchains macro so you don't normally need to interact with
41+
# these targets.
42+
43+
"""
44+
45+
for [platform, meta] in PLATFORMS.items():
46+
build_content += """\
47+
# Bazel selects this toolchain to get a Python interpreter
48+
# for executing build actions.
49+
toolchain(
50+
name = "{platform}_toolchain",
51+
exec_compatible_with = {compatible_with},
52+
toolchain = "@{user_repository_name}_{platform}//:python_runtimes",
53+
toolchain_type = "@bazel_tools//tools/python:toolchain_type",
54+
)
55+
""".format(
56+
platform = platform,
57+
name = rctx.attr.name,
58+
user_repository_name = rctx.attr.user_repository_name,
59+
compatible_with = meta.compatible_with,
60+
)
61+
62+
rctx.file("BUILD.bazel", build_content)
63+
64+
toolchains_repo = repository_rule(
65+
_toolchains_repo_impl,
66+
doc = "Creates a repository with toolchain definitions for all known platforms " +
67+
"which can be registered or selected.",
68+
attrs = {
69+
"user_repository_name": attr.string(doc = "what the user chose for the base name"),
70+
},
71+
)
72+
73+
def _resolved_interpreter_os_alias_impl(rctx):
74+
(os_name, arch) = _host_os_arch(rctx)
75+
76+
host_platform = None
77+
for platform, meta in PLATFORMS.items():
78+
if meta.os_name == os_name and meta.arch == arch:
79+
host_platform = platform
80+
if not host_platform:
81+
fail("No platform declared for host OS {} on arch {}".format(os_name, arch))
82+
83+
is_windows = (os_name == WINDOWS_NAME)
84+
python3_binary_path = "python.exe" if is_windows else "bin/python3"
85+
86+
# Base BUILD file for this repository.
87+
build_contents = """\
88+
# Generated by python/repositories.bzl
89+
package(default_visibility = ["//visibility:public"])
90+
alias(name = "files", actual = "@{py_repository}_{host_platform}//:files")
91+
alias(name = "py3_runtime", actual = "@{py_repository}_{host_platform}//:py3_runtime")
92+
alias(name = "python_runtimes", actual = "@{py_repository}_{host_platform}//:python_runtimes")
93+
alias(name = "python3", actual = "@{py_repository}_{host_platform}//:{python3_binary_path}")
94+
""".format(
95+
py_repository = rctx.attr.user_repository_name,
96+
host_platform = host_platform,
97+
python3_binary_path = python3_binary_path,
98+
)
99+
if not is_windows:
100+
build_contents += """\
101+
alias(name = "pip", actual = "@{py_repository}_{host_platform}//:bin/pip")
102+
""".format(
103+
py_repository = rctx.attr.user_repository_name,
104+
host_platform = host_platform,
105+
)
106+
rctx.file("BUILD.bazel", build_contents)
107+
108+
# Expose a Starlark file so rules can know what host platform we used and where to find an interpreter
109+
# when using repository_ctx.path, which doesn't understand aliases.
110+
rctx.file("defs.bzl", content = """\
111+
# Generated by python/repositories.bzl
112+
host_platform = "{host_platform}"
113+
interpreter = "@{py_repository}_{host_platform}//:{python3_binary_path}"
114+
""".format(
115+
py_repository = rctx.attr.user_repository_name,
116+
host_platform = host_platform,
117+
python3_binary_path = python3_binary_path,
118+
))
119+
120+
resolved_interpreter_os_alias = repository_rule(
121+
_resolved_interpreter_os_alias_impl,
122+
doc = """Creates a repository with a shorter name meant for the host platform, which contains
123+
a BUILD.bazel file declaring aliases to the host platform's targets.
124+
""",
125+
attrs = {
126+
"user_repository_name": attr.string(
127+
mandatory = True,
128+
doc = "The base name for all created repositories, like 'python38'.",
129+
),
130+
},
131+
)
132+
133+
def _host_os_arch(rctx):
134+
"""Infer the host OS name and arch from a repository context.
135+
136+
Args:
137+
rctx: Bazel's repository_ctx.
138+
Returns:
139+
A tuple with the host OS name and arch.
140+
"""
141+
os_name = rctx.os.name
142+
143+
# We assume the arch for Windows is always x86_64.
144+
if "windows" in os_name.lower():
145+
arch = "x86_64"
146+
147+
# Normalize the os_name. E.g. os_name could be "OS windows server 2019".
148+
os_name = WINDOWS_NAME
149+
else:
150+
# This is not ideal, but bazel doesn't directly expose arch.
151+
arch = rctx.execute(["uname", "-m"]).stdout.strip()
152+
153+
# Normalize the os_name.
154+
if "mac" in os_name.lower():
155+
os_name = MACOS_NAME
156+
elif "linux" in os_name.lower():
157+
os_name = LINUX_NAME
158+
159+
return (os_name, arch)

0 commit comments

Comments
 (0)