@@ -161,9 +161,13 @@ Remember to call `use_repo()` to make repos visible to your module:
161
161
162
162
#### Toolchain usage in other rules
163
163
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.
167
171
168
172
## Workspace configuration
169
173
@@ -242,3 +246,193 @@ there is a toolchain misconfiguration somewhere.
242
246
To aid migration off the Bazel-builtin toolchain, rules_python provides
243
247
{obj}` @rules_python//python/runtime_env_toolchains:all ` . This is an equivalent
244
248
toolchain, 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