Skip to content

Commit 34e433b

Browse files
rickeylevaignas
andauthored
feat(toolchains): create toolchains from locally installed python (#2742)
This adds docs and public APIs for using a locally installed python for a toolchain. Work towards #2070 --------- Co-authored-by: Ignas Anikevicius <[email protected]>
1 parent 6821709 commit 34e433b

File tree

8 files changed

+155
-6
lines changed

8 files changed

+155
-6
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ Unreleased changes template.
116116
allow specifying links to create within the venv site packages (only
117117
applicable with {obj}`--bootstrap_impl=script`)
118118
([#2156](https://github.com/bazelbuild/rules_python/issues/2156)).
119+
* (toolchains) Local Python installs can be used to create a toolchain
120+
equivalent to the standard toolchains. See [Local toolchains] docs for how to
121+
configure them.
122+
119123

120124
{#v0-0-0-removed}
121125
### Removed

docs/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ sphinx_stardocs(
108108
"//python/cc:py_cc_toolchain_bzl",
109109
"//python/cc:py_cc_toolchain_info_bzl",
110110
"//python/entry_points:py_console_script_binary_bzl",
111+
"//python/local_toolchains:repos_bzl",
111112
"//python/private:attr_builders_bzl",
112113
"//python/private:builders_util_bzl",
113114
"//python/private:py_binary_rule_bzl",

docs/toolchains.md

+93-4
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,10 @@ Remember to call `use_repo()` to make repos visible to your module:
199199

200200

201201
:::{deprecated} 1.1.0
202-
The toolchain specific `py_binary` and `py_test` symbols are aliases to the regular rules.
202+
The toolchain specific `py_binary` and `py_test` symbols are aliases to the regular rules.
203203
i.e. Deprecated `load("@python_versions//3.11:defs.bzl", "py_binary")` & `load("@python_versions//3.11:defs.bzl", "py_test")`
204204

205-
Usages of them should be changed to load the regular rules directly;
205+
Usages of them should be changed to load the regular rules directly;
206206
i.e. Use `load("@rules_python//python:py_binary.bzl", "py_binary")` & `load("@rules_python//python:py_test.bzl", "py_test")` and then specify the `python_version` when using the rules corresponding to the python version you defined in your toolchain. {ref}`Library modules with version constraints`
207207
:::
208208

@@ -327,7 +327,97 @@ After registration, your Python targets will use the toolchain's interpreter dur
327327
is still used to 'bootstrap' Python targets (see https://github.com/bazel-contrib/rules_python/issues/691).
328328
You may also find some quirks while using this toolchain. Please refer to [python-build-standalone documentation's _Quirks_ section](https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html).
329329

330-
## Autodetecting toolchain
330+
## Local toolchain
331+
332+
It's possible to use a locally installed Python runtime instead of the regular
333+
prebuilt, remotely downloaded ones. A local toolchain contains the Python
334+
runtime metadata (Python version, headers, ABI flags, etc) that the regular
335+
remotely downloaded runtimes contain, which makes it possible to build e.g. C
336+
extensions (unlike the autodetecting and runtime environment toolchains).
337+
338+
For simple cases, some rules are provided that will introspect
339+
a Python installation and create an appropriate Bazel definition from
340+
it. To do this, three pieces need to be wired together:
341+
342+
1. Specify a path or command to a Python interpreter (multiple can be defined).
343+
2. Create toolchains for the runtimes in (1)
344+
3. Register the toolchains created by (2)
345+
346+
The below is an example that will use `python3` from PATH to find the
347+
interpreter, then introspect its installation to generate a full toolchain.
348+
349+
```starlark
350+
# File: MODULE.bazel
351+
352+
local_runtime_repo = use_repo_rule(
353+
"@rules_python//python/local_toolchains:repos.bzl",
354+
"local_runtime_repo",
355+
dev_dependency = True,
356+
)
357+
358+
local_runtime_toolchains_repo = use_repo_rule(
359+
"@rules_python//python/local_toolchains:repos.bzl"
360+
"local_runtime_toolchains_repo"
361+
dev_dependency = True,
362+
)
363+
364+
# Step 1: Define the Python runtime
365+
local_runtime_repo(
366+
name = "local_python3",
367+
interpreter_path = "python3",
368+
on_failure = "fail",
369+
)
370+
371+
# Step 2: Create toolchains for the runtimes
372+
local_runtime_toolchains_repo(
373+
name = "local_toolchains",
374+
runtimes = ["local_python3"],
375+
)
376+
377+
# Step 3: Register the toolchains
378+
register_toolchains("@local_toolchains//:all", dev_dependency = True)
379+
```
380+
381+
Note that `register_toolchains` will insert the local toolchain earlier in the
382+
toolchain ordering, so it will take precedence over other registered toolchains.
383+
384+
:::{important}
385+
Be sure to set `dev_dependency = True`. Using a local toolchain only makes sense
386+
for the root module.
387+
388+
If an intermediate module does it, then the `register_toolchains()` call will
389+
take precedence over the default rules_python toolchains and cause problems for
390+
downstream modules.
391+
:::
392+
393+
Multiple runtimes and/or toolchains can be defined, which allows for multiple
394+
Python versions and/or platforms to be configured in a single `MODULE.bazel`.
395+
396+
## Runtime environment toolchain
397+
398+
The runtime environment toolchain is a minimal toolchain that doesn't provide
399+
information about Python at build time. In particular, this means it is not able
400+
to build C extensions -- doing so requires knowing, at build time, what Python
401+
headers to use.
402+
403+
In effect, all it does is generate a small wrapper script that simply calls e.g.
404+
`/usr/bin/env python3` to run a program. This makes it easy to change what
405+
Python is used to run a program, but also makes it easy to use a Python version
406+
that isn't compatible with build-time assumptions.
407+
408+
```
409+
register_toolchains("@rules_python//python/runtime_env_toolchains:all")
410+
```
411+
412+
Note that this toolchain has no constraints, i.e. it will match any platform,
413+
Python version, etc.
414+
415+
:::{seealso}
416+
[Local toolchain], which creates a more full featured toolchain from a
417+
locally installed Python.
418+
:::
419+
420+
### Autodetecting toolchain
331421

332422
The autodetecting toolchain is a deprecated toolchain that is built into Bazel.
333423
It's name is a bit misleading: it doesn't autodetect anything. All it does is
@@ -345,7 +435,6 @@ To aid migration off the Bazel-builtin toolchain, rules_python provides
345435
{bzl:obj}`@rules_python//python/runtime_env_toolchains:all`. This is an equivalent
346436
toolchain, but is implemented using rules_python's objects.
347437

348-
349438
## Custom toolchains
350439

351440
While rules_python provides toolchains by default, it is not required to use

python/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ filegroup(
4141
"//python/constraints:distribution",
4242
"//python/entry_points:distribution",
4343
"//python/extensions:distribution",
44+
"//python/local_toolchains:distribution",
4445
"//python/pip_install:distribution",
4546
"//python/private:distribution",
4647
"//python/runfiles:distribution",

python/local_toolchains/BUILD.bazel

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
2+
3+
package(default_visibility = ["//:__subpackages__"])
4+
5+
bzl_library(
6+
name = "repos_bzl",
7+
srcs = ["repos.bzl"],
8+
visibility = ["//visibility:public"],
9+
deps = [
10+
"//python/private:local_runtime_repo_bzl",
11+
"//python/private:local_runtime_toolchains_repo_bzl",
12+
],
13+
)
14+
15+
filegroup(
16+
name = "distribution",
17+
srcs = glob(["**"]),
18+
)

python/local_toolchains/repos.bzl

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Rules/macros for repository phase for local toolchains.
2+
3+
:::{versionadded} VERSION_NEXT_FEATURE
4+
:::
5+
"""
6+
7+
load(
8+
"@rules_python//python/private:local_runtime_repo.bzl",
9+
_local_runtime_repo = "local_runtime_repo",
10+
)
11+
load(
12+
"@rules_python//python/private:local_runtime_toolchains_repo.bzl",
13+
_local_runtime_toolchains_repo = "local_runtime_toolchains_repo",
14+
)
15+
16+
local_runtime_repo = _local_runtime_repo
17+
18+
local_runtime_toolchains_repo = _local_runtime_toolchains_repo

python/private/BUILD.bazel

+18
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,24 @@ bzl_library(
205205
],
206206
)
207207

208+
bzl_library(
209+
name = "local_runtime_repo_bzl",
210+
srcs = ["local_runtime_repo.bzl"],
211+
deps = [
212+
":enum_bzl",
213+
":repo_utils.bzl",
214+
],
215+
)
216+
217+
bzl_library(
218+
name = "local_runtime_toolchains_repo_bzl",
219+
srcs = ["local_runtime_toolchains_repo.bzl"],
220+
deps = [
221+
":repo_utils.bzl",
222+
":text_util_bzl",
223+
],
224+
)
225+
208226
bzl_library(
209227
name = "normalize_name_bzl",
210228
srcs = ["normalize_name.bzl"],

tests/integration/local_toolchains/MODULE.bazel

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ local_path_override(
1919
path = "../../..",
2020
)
2121

22-
local_runtime_repo = use_repo_rule("@rules_python//python/private:local_runtime_repo.bzl", "local_runtime_repo")
22+
local_runtime_repo = use_repo_rule("@rules_python//python/local_toolchains:repos.bzl", "local_runtime_repo")
2323

24-
local_runtime_toolchains_repo = use_repo_rule("@rules_python//python/private:local_runtime_toolchains_repo.bzl", "local_runtime_toolchains_repo")
24+
local_runtime_toolchains_repo = use_repo_rule("@rules_python//python/local_toolchains:repos.bzl", "local_runtime_toolchains_repo")
2525

2626
local_runtime_repo(
2727
name = "local_python3",

0 commit comments

Comments
 (0)