@@ -161,9 +161,13 @@ Remember to call `use_repo()` to make repos visible to your module:
161161
162162#### Toolchain usage in other rules
163163
164- Python toolchains can be utilized in other bazel rules, such as ` genrule() ` , by adding the ` toolchains=["@rules_python//python:current_py_toolchain"] ` attribute. You can obtain the path to the Python interpreter using the ` $(PYTHON2) ` and ` $(PYTHON3) ` [ "Make" Variables] ( https://bazel.build/reference/be/make-variables ) . See the
165- {gh-path}` test_current_py_toolchain <tests/load_from_macro/BUILD.bazel> ` target for an example.
166-
164+ Python toolchains can be utilized in other bazel rules, such as ` genrule() ` , by
165+ adding the ` toolchains=["@rules_python//python:current_py_toolchain"] `
166+ attribute. You can obtain the path to the Python interpreter using the
167+ ` $(PYTHON2) ` and ` $(PYTHON3) ` [ "Make"
168+ Variables] ( https://bazel.build/reference/be/make-variables ) . See the
169+ {gh-path}` test_current_py_toolchain <tests/load_from_macro/BUILD.bazel> ` target
170+ for an example.
167171
168172## Workspace configuration
169173
@@ -242,3 +246,193 @@ there is a toolchain misconfiguration somewhere.
242246To aid migration off the Bazel-builtin toolchain, rules_python provides
243247{obj}` @rules_python//python/runtime_env_toolchains:all ` . This is an equivalent
244248toolchain, but is implemented using rules_python's objects.
249+
250+
251+ ## Custom toolchains
252+
253+ While rules_python provides toolchains by default, it is not required to use
254+ them, and you can define your own toolchains to use instead. This section
255+ gives an introduction for how to define them yourself.
256+
257+ :::{note}
258+ * Defining your own toolchains is an advanced feature.
259+ * APIs used for defining them are less stable and may change more often.
260+ :::
261+
262+ Under the hood, there are multiple toolchains that comprise the different
263+ information necessary to build Python targets. Each one has an
264+ associated _ toolchain type_ that identifies it. We call the collection of these
265+ toolchains a "toolchain suite".
266+
267+ One of the underlying design goals of the toolchains is to support complex and
268+ bespoke environments. Such environments may use an arbitrary combination of
269+ {obj}` RBE ` , cross-platform building, multiple Python versions,
270+ building Python from source, embeding Python (as opposed to building separate
271+ interpreters), using prebuilt binaries, or using binaries built from source. To
272+ that end, many of the attributes they accept, and fields they provide, are
273+ optional.
274+
275+ ### Target toolchain type
276+
277+ The target toolchain type is {obj}` //python:toolchain_type ` , and it
278+ is for _ target configuration_ runtime information, e.g., the Python version
279+ and interpreter binary that a program will use.
280+
281+ The is typically implemented using {obj}` py_runtime() ` , which
282+ provides the {obj}` PyRuntimeInfo ` provider. For historical reasons from the
283+ Python 2 transition, ` py_runtime ` is wrapped in {obj}` py_runtime_pair ` ,
284+ which provides {obj}` ToolchainInfo ` with the field ` py3_runtime ` , which is an
285+ instance of ` PyRuntimeInfo ` .
286+
287+ This toolchain type is intended to hold only _ target configuration_ values. As
288+ such, when defining its associated {external:bzl: obj }` toolchain ` target, only
289+ set {external:bzl: obj }` toolchain.target_compatible_with ` and/or
290+ {external:bzl: obj }` toolchain.target_settings ` constraints; there is no need to
291+ set {external:bzl: obj }` toolchain.exec_compatible_with ` .
292+
293+ ### Python C toolchain type
294+
295+ The Python C toolchain type ("py cc") is {obj}` //python/cc:toolchain_type ` , and
296+ it has C/C++ information for the _ target configuration_ , e.g. the C headers that
297+ provide ` Python.h ` .
298+
299+ This is typically implemented using {obj}` py_cc_toolchain() ` , which provides
300+ {obj}` ToolchainInfo ` with the field ` py_cc_toolchain ` set, which is a
301+ {obj}` PyCcToolchainInfo ` provider instance.
302+
303+ This toolchain type is intended to hold only _ target configuration_ values
304+ relating to the C/C++ information for the Python runtime. As such, when defining
305+ its associated {external: obj }` toolchain ` target, only set
306+ {external:bzl: obj }` toolchain.target_compatible_with ` and/or
307+ {external:bzl: obj }` toolchain.target_settings ` constraints; there is no need to
308+ set {external:bzl: obj }` toolchain.exec_compatible_with ` .
309+
310+ ### Exec tools toolchain type
311+
312+ The exec tools toolchain type is {obj}` //python:exec_tools_toolchain_type ` ,
313+ and it is for supporting tools for _ building_ programs, e.g. the binary to
314+ precompile code at build time.
315+
316+ This toolchain type is intended to hold only _ exec configuration_ values --
317+ usually tools (prebuilt or from-source) used to build Python targets.
318+
319+ This is typically implemented using {obj}` py_exec_tools_toolchain ` , which
320+ provides {obj}` ToolchainInfo ` with the field ` exec_tools ` set, which is an
321+ instance of {obj}` PyExecToolsInfo ` .
322+
323+ The toolchain constraints of this toolchain type can be a bit more nuanced than
324+ the other toolchain types. Typically, you set
325+ {external:bzl: obj }` toolchain.target_settings ` to the Python version the tools
326+ are for, and {external:bzl: obj }` toolchain.exec_compatible_with ` to the platform
327+ they can run on. This allows the toolchain to first be considered based on the
328+ target configuration (e.g. Python version), then for one to be chosen based on
329+ finding one compatible with the available host platforms to run the tool on.
330+
331+ However, what ` target_compatible_with ` /` target_settings ` and
332+ ` exec_compatible_with ` values to use depend on details of the tools being used.
333+ For example:
334+ * If you had a precompiler that supported any version of Python, then
335+ putting the Python version in ` target_settings ` is unnecessary.
336+ * If you had a prebuilt polyglot precompiler binary that could run on any
337+ platform, then setting ` exec_compatible_with ` is unnecessary.
338+
339+ This can work because, when the rules invoke these build tools, they pass along
340+ all necessary information so that the tool can be entirely independent of the
341+ target configuration being built for.
342+
343+ Alternatively, if you had a precompiler that only ran on linux, and only
344+ produced valid output for programs intended to run on linux, then _ both_
345+ ` exec_compatible_with ` and ` target_compatible_with ` must be set to linux.
346+
347+ ### Custom toolchain example
348+
349+ Here, we show an example for a semi-complicated toolchain suite, one that is:
350+
351+ * A CPython-based interpreter
352+ * For Python version 3.12.0
353+ * Using an in-build interpreter built from source
354+ * That only runs on Linux
355+ * Using a prebuilt precompiler that only runs on Linux, and only produces byte
356+ code valid for 3.12
357+ * With the exec tools interpreter disabled (unnecessary with a prebuild
358+ precompiler)
359+ * Providing C headers and libraries
360+
361+ Defining toolchains for this might look something like this:
362+
363+ ```
364+ # File: toolchain_impls/BUILD
365+ load("@rules_python//python:py_cc_toolchain.bzl", "py_cc_toolchain")
366+ load("@rules_python//python:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
367+ load("@rules_python//python:py_runtime.bzl", "py_runtime")
368+ load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
369+
370+ MAJOR = 3
371+ MINOR = 12
372+ MICRO = 0
373+
374+ py_runtime(
375+ name = "runtime",
376+ interpreter = ":python",
377+ interpreter_version_info = {
378+ "major": str(MAJOR),
379+ "minor": str(MINOR),
380+ "micro": str(MICRO),
381+ }
382+ implementation = "cpython"
383+ )
384+ py_runtime_pair(
385+ name = "runtime_pair",
386+ py3_runtime = ":runtime"
387+ )
388+
389+ py_cc_toolchain(
390+ name = "py_cc_toolchain_impl",
391+ headers = ":headers",
392+ libs = ":libs",
393+ python_version = "{}.{}".format(MAJOR, MINOR)
394+ )
395+
396+ py_exec_tools_toolchain(
397+ name = "exec_tools_toolchain_impl",
398+ exec_interpreter = "@rules_python/python:null_target",
399+ precompiler = "precompiler-cpython-3.12"
400+ )
401+
402+ cc_binary(name = "python3.12", ...)
403+ cc_library(name = "headers", ...)
404+ cc_library(name = "libs", ...)
405+
406+ # File: toolchains/BUILD
407+ # Putting toolchain() calls in a separate package from the toolchain
408+ # implementations minimizes Bazel loading overhead
409+
410+ toolchain(
411+ name = "runtime_toolchain",
412+ toolchain = "//toolchain_impl:runtime_pair",
413+ toolchain_type = "@rules_python//python:toolchain_type",
414+ target_compatible_with = ["@platforms/os:linux"]
415+ )
416+ toolchain(
417+ name = "py_cc_toolchain",
418+ toolchain = "//toolchain_impl:py_cc_toolchain_impl",
419+ toolchain_type = "@rules_python//python/cc:toolchain_type",
420+ target_compatible_with = ["@platforms/os:linux"]
421+ )
422+
423+ toolchain(
424+ name = "exec_tools_toolchain",
425+ toolchain = "//toolchain_impl:exec_tools_toolchain_impl",
426+ toolchain_type = "@rules_python//python:exec_tools_toolchain_type",
427+ target_settings = [
428+ "@rules_python//python/config_settings:is_python_3.12",
429+ ],
430+ exec_comaptible_with = ["@platforms/os:linux"]
431+ )
432+ ```
433+
434+ :::{note}
435+ The toolchain() calls should be in a separate BUILD file from everything else.
436+ This avoids Bazel having to perform unnecessary work when it discovers the list
437+ of available toolchains.
438+ :::
0 commit comments