Skip to content

Commit cdfa93e

Browse files
feat(toolchains): ABI3 Python headers target (#3274)
Until now, we silently link extensions with both stable and unstable ABI libs, with the latter taking precedence in symbol resolution, because it appears first in the linker command AND, crucially, contains all CPython symbols present in the stable ABI library, thus overriding them. This has the effect that stable ABI extensions on Windows are usable only with the Python distribution that they were built on. To fix, a separate ABI3 header target is introduced, and should be used for C++ extensions on Windows if stable ABI builds are requested. Idea as formulated by `@dgrunwald-qt` in nicholasjng/nanobind-bazel#72 (comment). This is motivated by nicholasjng/nanobind-bazel#72. This change shifts stable ABI selection on Windows to the extension developer, where it has arguably always been (they had to set the `Py_LIMITED_API` macro). An upside of this approach is that with a separate target, the question "stable ABI or not" can be decided on an extension-by-extension basis, giving maximum flexibility to developers. This should not influence the wheel platform target, because a wheel is marked ABI3 if and only if all of its extensions are marked as ABI3. --------- Co-authored-by: Richard Levasseur <[email protected]> Co-authored-by: Richard Levasseur <[email protected]>
1 parent 3b48bf8 commit cdfa93e

26 files changed

+456
-55
lines changed

AGENTS.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ using a grandoise title.
1919
When tasks complete successfully, quote Monty Python, but work it naturally
2020
into the sentence, not verbatim.
2121

22+
When adding `{versionadded}` or `{versionchanged}` sections, add them add the
23+
end of the documentation text.
24+
25+
### Starlark style
26+
27+
For doc strings, using triple quoted strings when the doc string is more than
28+
three lines. Do not use a trailing backslack (`\`) for the opening triple-quote.
29+
2230
### bzl_library targets for bzl source files
2331

2432
* A `bzl_library` target should be defined for every `.bzl` file outside
@@ -78,7 +86,17 @@ When modifying documentation
7886
* Act as an expert in tech writing, Sphinx, MyST, and markdown.
7987
* Wrap lines at 80 columns
8088
* Use hyphens (`-`) in file names instead of underscores (`_`).
81-
89+
* In Sphinx MyST markup, outer directives must have more colons than inner
90+
directives. For example:
91+
```
92+
::::{outerdirective}
93+
outer text
94+
95+
:::{innertdirective}
96+
inner text
97+
:::
98+
::::
99+
```
82100

83101
Generated API references can be found by:
84102
* Running `bazel build //docs:docs` and inspecting the generated files

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ END_UNRELEASED_TEMPLATE
9696
`WORKSPACE` files. See the
9797
{ref}`common-deps-with-multiple-pypi-versions` guide on using common
9898
dependencies with multiple PyPI versions` for an example.
99+
* (toolchains) Stable ABI headers support added. To use, depend on
100+
{obj}`//python/cc:current_py_cc_headers_abi3`. This allows Windows builds
101+
a way to depend on headers without the potentially Python unstable ABI
102+
objects from the regular {obj}`//python/cc:current_py_cc_headers` target
103+
being included.
104+
* Adds {obj}`//python/cc:current_py_cc_headers_abi3`,
105+
{obj}`py_cc_toolchain.headers_abi3`, and {obj}`PyCcToolchainInfo.headers_abi3`.
106+
* {obj}`//python:features.bzl%features.headers_abi3` can be used to
107+
feature-detect the presense of the above.
99108

100109
{#v1-6-2}
101110
## [1.6.2] - 2025-09-21

docs/api/rules_python/python/cc/index.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
:::
55
# //python/cc
66

7-
:::{bzl:target} current_py_cc_headers
7+
::::{bzl:target} current_py_cc_headers
88

99
A convenience target that provides the Python headers. It uses toolchain
1010
resolution to find the headers for the Python runtime matching the interpreter
@@ -14,7 +14,32 @@ that will be used. This basically forwards the underlying
1414
This target provides:
1515

1616
* `CcInfo`: The C++ information about the Python headers.
17+
18+
:::{seealso}
19+
20+
The {obj}`:current_py_cc_headers_abi3` target for explicitly using the
21+
stable ABI.
22+
:::
23+
24+
::::
25+
26+
::::{bzl:target} current_py_cc_headers_abi3
27+
28+
A convenience target that provides the Python ABI3 headers (stable ABI headers).
29+
It uses toolchain resolution to find the headers for the Python runtime matching
30+
the interpreter that will be used. This basically forwards the underlying
31+
`cc_library(name="python_headers_abi3")` target defined in the `@python_X_Y`
32+
repo.
33+
34+
This target provides:
35+
36+
* `CcInfo`: The C++ information about the Python ABI3 headers.
37+
38+
:::{versionadded} VERSION_NEXT_FEATURE
39+
The {obj}`features.headers_abi3` attribute can be used to detect if this target
40+
is available or not.
1741
:::
42+
::::
1843

1944
:::{bzl:target} current_py_cc_libs
2045

docs/howto/python-headers.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ files. This guide shows how to get the necessary include paths from the Python
88
[toolchain](toolchains).
99

1010
The recommended way to get the headers is to depend on the
11-
`@rules_python//python/cc:current_py_cc_headers` target. This is a helper
12-
target that uses toolchain resolution to find the correct headers for the
13-
target platform.
11+
{obj}`@rules_python//python/cc:current_py_cc_headers` or
12+
{obj}`@rules_python//python/cc:current_py_cc_headers_abi3`
13+
targets. These are convenience targets that use toolchain resolution to find
14+
the correct headers for the target platform.
1415

1516
## Using the headers
1617

@@ -27,4 +28,27 @@ cc_library(
2728
```
2829

2930
This setup ensures that your C extension code can find and use the Python
30-
headers during compilation.
31+
headers during compilation.
32+
33+
:::{note}
34+
The `:current_py_cc_headers` target provides all the Python headers. This _may_
35+
include ABI-specific information.
36+
:::
37+
38+
## Using the stable ABI headers
39+
40+
If you're building for the [Python stable ABI](https://docs.python.org/3/c-api/stable.html),
41+
then depend on {obj}`@rules_python//python/cc:current_py_cc_headers_abi3`. This
42+
target contains only objects relevant to the Python stable ABI. Remember to
43+
define
44+
[`Py_LIMITED_API`](https://docs.python.org/3/c-api/stable.html#c.Py_LIMITED_API)
45+
when building such extensions.
46+
47+
```bazel
48+
# BUILD.bazel
49+
cc_library(
50+
name = "my_stable_abi_extension",
51+
srcs = ["my_stable_abi_extension.c"],
52+
deps = ["@rules_python//python/cc:current_py_cc_headers_abi3"],
53+
)
54+
```

docs/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ dependencies = [
1212
"readthedocs-sphinx-ext",
1313
"absl-py",
1414
"typing-extensions",
15-
"sphinx-reredirects"
15+
"sphinx-reredirects",
16+
"pefile"
1617
]

docs/requirements.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ colorama==0.4.6 ; sys_platform == 'win32' \
111111
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
112112
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
113113
# via sphinx
114-
docutils==0.22 \
115-
--hash=sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e \
116-
--hash=sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f
114+
docutils==0.21.2 \
115+
--hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \
116+
--hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2
117117
# via
118118
# myst-parser
119119
# sphinx
@@ -232,6 +232,10 @@ packaging==25.0 \
232232
# via
233233
# readthedocs-sphinx-ext
234234
# sphinx
235+
pefile==2024.8.26 \
236+
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
237+
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
238+
# via rules-python-docs (docs/pyproject.toml)
235239
pygments==2.19.2 \
236240
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
237241
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b

python/cc/BUILD.bazel

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
44
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
5-
load("//python/private:current_py_cc_headers.bzl", "current_py_cc_headers")
5+
load("//python/private:current_py_cc_headers.bzl", "current_py_cc_headers", "current_py_cc_headers_abi3")
66
load("//python/private:current_py_cc_libs.bzl", "current_py_cc_libs")
77

88
package(
@@ -20,6 +20,17 @@ current_py_cc_headers(
2020
visibility = ["//visibility:public"],
2121
)
2222

23+
# This target provides the C ABI3 headers for whatever the current toolchain is
24+
# for the consuming rule. It basically acts like a cc_library by forwarding
25+
# on the providers for the underlying cc_library that the toolchain is using.
26+
current_py_cc_headers_abi3(
27+
name = "current_py_cc_headers_abi3",
28+
# Building this directly will fail unless a py cc toolchain is registered,
29+
# and it's only under bzlmod that one is registered by default.
30+
tags = [] if BZLMOD_ENABLED else ["manual"],
31+
visibility = ["//visibility:public"],
32+
)
33+
2334
# This target provides the C libraries for whatever the current toolchain is for
2435
# the consuming rule. It basically acts like a cc_library by forwarding on the
2536
# providers for the underlying cc_library that the toolchain is using.

python/features.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ _VERSION_PRIVATE = "$Format:%(describe:tags=true)$"
2222
def _features_typedef():
2323
"""Information about features rules_python has implemented.
2424
25+
::::{field} headers_abi3
26+
:type: bool
27+
28+
True if the {obj}`@rules_python//python/cc:current_py_cc_headers_abi3`
29+
target is available.
30+
31+
:::{versionadded} VERSION_NEXT_FEATURE
32+
:::
33+
::::
34+
2535
::::{field} precompile
2636
:type: bool
2737
@@ -60,6 +70,7 @@ def _features_typedef():
6070
features = struct(
6171
TYPEDEF = _features_typedef,
6272
# keep sorted
73+
headers_abi3 = True,
6374
precompile = True,
6475
py_info_venv_symlinks = True,
6576
uses_builtin_rules = not config.enable_pystar,

python/private/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ filegroup(
3131
name = "distribution",
3232
srcs = glob(["**"]) + [
3333
"//python/private/api:distribution",
34+
"//python/private/cc:distribution",
3435
"//python/private/pypi:distribution",
3536
"//python/private/whl_filegroup:distribution",
3637
"//tools/build_defs/python/private:distribution",
@@ -360,6 +361,7 @@ bzl_library(
360361
":common_labels.bzl",
361362
":py_cc_toolchain_info_bzl",
362363
":rules_cc_srcs_bzl",
364+
":sentinel_bzl",
363365
":util_bzl",
364366
"@bazel_skylib//rules:common_settings",
365367
],

python/private/cc/BUILD.bazel

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
load("@rules_cc//cc:cc_library.bzl", "cc_library")
2+
load("//python/private:visibility.bzl", "NOT_ACTUALLY_PUBLIC")
3+
4+
package(
5+
default_visibility = ["//:__subpackages__"],
6+
)
7+
8+
licenses(["notice"])
9+
10+
filegroup(
11+
name = "distribution",
12+
srcs = glob(["**"]),
13+
)
14+
15+
# An empty cc target for use when a cc target is needed to satisfy
16+
# Bazel, but its contents don't matter.
17+
cc_library(
18+
name = "empty",
19+
visibility = NOT_ACTUALLY_PUBLIC,
20+
)

0 commit comments

Comments
 (0)