1
- import contextlib
2
1
import os
3
2
import platform
4
3
import shutil
5
- import sysconfig
6
4
from pathlib import Path
7
- from typing import Generator
5
+ from typing import Any
8
6
9
7
import setuptools
10
8
from setuptools .command import build_ext
11
9
12
- PYTHON_INCLUDE_PATH_PLACEHOLDER = "<PYTHON_INCLUDE_PATH>"
13
-
14
10
IS_WINDOWS = platform .system () == "Windows"
15
11
IS_MAC = platform .system () == "Darwin"
16
12
17
-
18
- @contextlib .contextmanager
19
- def temp_fill_include_path (fp : str ) -> Generator [None , None , None ]:
20
- """Temporarily set the Python include path in a file."""
21
- with open (fp , "r+" ) as f :
22
- try :
23
- content = f .read ()
24
- replaced = content .replace (
25
- PYTHON_INCLUDE_PATH_PLACEHOLDER ,
26
- Path (sysconfig .get_paths ()["include" ]).as_posix (),
27
- )
28
- f .seek (0 )
29
- f .write (replaced )
30
- f .truncate ()
31
- yield
32
- finally :
33
- # revert to the original content after exit
34
- f .seek (0 )
35
- f .write (content )
36
- f .truncate ()
13
+ # hardcoded SABI-related options. Requires that each Python interpreter
14
+ # (hermetic or not) participating is of the same major-minor version.
15
+ version_tuple = tuple (int (i ) for i in platform .python_version_tuple ())
16
+ py_limited_api = version_tuple >= (3 , 12 )
17
+ options = {"bdist_wheel" : {"py_limited_api" : "cp312" }} if py_limited_api else {}
37
18
38
19
39
20
class BazelExtension (setuptools .Extension ):
40
21
"""A C/C++ extension that is defined as a Bazel BUILD target."""
41
22
42
- def __init__ (self , name : str , bazel_target : str ):
43
- super ().__init__ (name = name , sources = [])
23
+ def __init__ (self , name : str , bazel_target : str , ** kwargs : Any ):
24
+ super ().__init__ (name = name , sources = [], ** kwargs )
44
25
45
26
self .bazel_target = bazel_target
46
27
stripped_target = bazel_target .split ("//" )[- 1 ]
@@ -67,49 +48,58 @@ def copy_extensions_to_source(self):
67
48
68
49
def bazel_build (self , ext : BazelExtension ) -> None :
69
50
"""Runs the bazel build to create the package."""
70
- with temp_fill_include_path ("WORKSPACE" ):
71
- temp_path = Path (self .build_temp )
72
-
73
- bazel_argv = [
74
- "bazel" ,
75
- "build" ,
76
- ext .bazel_target ,
77
- "--enable_bzlmod=false" ,
78
- f"--symlink_prefix={ temp_path / 'bazel-' } " ,
79
- f"--compilation_mode={ 'dbg' if self .debug else 'opt' } " ,
80
- # C++17 is required by nanobind
81
- f"--cxxopt={ '/std:c++17' if IS_WINDOWS else '-std=c++17' } " ,
82
- ]
83
-
84
- if IS_WINDOWS :
85
- # Link with python*.lib.
86
- for library_dir in self .library_dirs :
87
- bazel_argv .append ("--linkopt=/LIBPATH:" + library_dir )
88
- elif IS_MAC :
89
- if platform .machine () == "x86_64" :
90
- # C++17 needs macOS 10.14 at minimum
91
- bazel_argv .append ("--macos_minimum_os=10.14" )
92
-
93
- # cross-compilation for Mac ARM64 on GitHub Mac x86 runners.
94
- # ARCHFLAGS is set by cibuildwheel before macOS wheel builds.
95
- archflags = os .getenv ("ARCHFLAGS" , "" )
96
- if "arm64" in archflags :
97
- bazel_argv .append ("--cpu=darwin_arm64" )
98
- bazel_argv .append ("--macos_cpus=arm64" )
99
-
100
- elif platform .machine () == "arm64" :
101
- bazel_argv .append ("--macos_minimum_os=11.0" )
102
-
103
- self .spawn (bazel_argv )
104
-
105
- shared_lib_suffix = ".dll" if IS_WINDOWS else ".so"
106
- ext_name = ext .target_name + shared_lib_suffix
107
- ext_bazel_bin_path = (
108
- temp_path / "bazel-bin" / ext .relpath / ext_name
109
- )
110
-
111
- ext_dest_path = Path (self .get_ext_fullpath (ext .name ))
112
- shutil .copyfile (ext_bazel_bin_path , ext_dest_path )
51
+ temp_path = Path (self .build_temp )
52
+ # omit the patch version to avoid build errors if the toolchain is not
53
+ # yet registered in the current @rules_python version.
54
+ # patch version differences should be fine.
55
+ python_version = "." .join (platform .python_version_tuple ()[:2 ])
56
+
57
+ bazel_argv = [
58
+ "bazel" ,
59
+ "build" ,
60
+ ext .bazel_target ,
61
+ f"--symlink_prefix={ temp_path / 'bazel-' } " ,
62
+ f"--compilation_mode={ 'dbg' if self .debug else 'opt' } " ,
63
+ # C++17 is required by nanobind
64
+ f"--cxxopt={ '/std:c++17' if IS_WINDOWS else '-std=c++17' } " ,
65
+ f"--@rules_python//python/config_settings:python_version={ python_version } " ,
66
+ ]
67
+
68
+ if ext .py_limited_api :
69
+ bazel_argv += ["--@nanobind_bazel//:py-limited-api=cp312" ]
70
+
71
+ if IS_WINDOWS :
72
+ # Link with python*.lib.
73
+ for library_dir in self .library_dirs :
74
+ bazel_argv .append ("--linkopt=/LIBPATH:" + library_dir )
75
+ elif IS_MAC :
76
+ if platform .machine () == "x86_64" :
77
+ # C++17 needs macOS 10.14 at minimum
78
+ bazel_argv .append ("--macos_minimum_os=10.14" )
79
+
80
+ # cross-compilation for Mac ARM64 on GitHub Mac x86 runners.
81
+ # ARCHFLAGS is set by cibuildwheel before macOS wheel builds.
82
+ archflags = os .getenv ("ARCHFLAGS" , "" )
83
+ if "arm64" in archflags :
84
+ bazel_argv .append ("--cpu=darwin_arm64" )
85
+ bazel_argv .append ("--macos_cpus=arm64" )
86
+
87
+ elif platform .machine () == "arm64" :
88
+ bazel_argv .append ("--macos_minimum_os=11.0" )
89
+
90
+ self .spawn (bazel_argv )
91
+
92
+ if IS_WINDOWS :
93
+ suffix = ".pyd"
94
+ else :
95
+ suffix = ".abi3.so" if ext .py_limited_api else ".so"
96
+
97
+ ext_name = ext .target_name + suffix
98
+ ext_bazel_bin_path = temp_path / "bazel-bin" / ext .relpath / ext_name
99
+ ext_dest_path = Path (self .get_ext_fullpath (ext .name )).with_name (
100
+ ext_name
101
+ )
102
+ shutil .copyfile (ext_bazel_bin_path , ext_dest_path )
113
103
114
104
115
105
setuptools .setup (
@@ -118,6 +108,8 @@ def bazel_build(self, ext: BazelExtension) -> None:
118
108
BazelExtension (
119
109
name = "google_benchmark._benchmark" ,
120
110
bazel_target = "//bindings/python/google_benchmark:_benchmark" ,
111
+ py_limited_api = py_limited_api ,
121
112
)
122
113
],
114
+ options = options ,
123
115
)
0 commit comments