xref: /aosp_15_r20/external/google-benchmark/setup.py (revision dbb99499c3810fa1611fa2242a2fc446be01a57c)
1*dbb99499SAndroid Build Coastguard Workerimport contextlib
2*dbb99499SAndroid Build Coastguard Workerimport os
3*dbb99499SAndroid Build Coastguard Workerimport platform
4*dbb99499SAndroid Build Coastguard Workerimport re
5*dbb99499SAndroid Build Coastguard Workerimport shutil
6*dbb99499SAndroid Build Coastguard Workerfrom pathlib import Path
7*dbb99499SAndroid Build Coastguard Workerfrom typing import Any, Generator
8*dbb99499SAndroid Build Coastguard Worker
9*dbb99499SAndroid Build Coastguard Workerimport setuptools
10*dbb99499SAndroid Build Coastguard Workerfrom setuptools.command import build_ext
11*dbb99499SAndroid Build Coastguard Worker
12*dbb99499SAndroid Build Coastguard WorkerIS_WINDOWS = platform.system() == "Windows"
13*dbb99499SAndroid Build Coastguard WorkerIS_MAC = platform.system() == "Darwin"
14*dbb99499SAndroid Build Coastguard WorkerIS_LINUX = platform.system() == "Linux"
15*dbb99499SAndroid Build Coastguard Worker
16*dbb99499SAndroid Build Coastguard Worker# hardcoded SABI-related options. Requires that each Python interpreter
17*dbb99499SAndroid Build Coastguard Worker# (hermetic or not) participating is of the same major-minor version.
18*dbb99499SAndroid Build Coastguard Workerversion_tuple = tuple(int(i) for i in platform.python_version_tuple())
19*dbb99499SAndroid Build Coastguard Workerpy_limited_api = version_tuple >= (3, 12)
20*dbb99499SAndroid Build Coastguard Workeroptions = {"bdist_wheel": {"py_limited_api": "cp312"}} if py_limited_api else {}
21*dbb99499SAndroid Build Coastguard Worker
22*dbb99499SAndroid Build Coastguard Worker
23*dbb99499SAndroid Build Coastguard Workerdef is_cibuildwheel() -> bool:
24*dbb99499SAndroid Build Coastguard Worker    return os.getenv("CIBUILDWHEEL") is not None
25*dbb99499SAndroid Build Coastguard Worker
26*dbb99499SAndroid Build Coastguard Worker
27*dbb99499SAndroid Build Coastguard Worker@contextlib.contextmanager
28*dbb99499SAndroid Build Coastguard Workerdef _maybe_patch_toolchains() -> Generator[None, None, None]:
29*dbb99499SAndroid Build Coastguard Worker    """
30*dbb99499SAndroid Build Coastguard Worker    Patch rules_python toolchains to ignore root user error
31*dbb99499SAndroid Build Coastguard Worker    when run in a Docker container on Linux in cibuildwheel.
32*dbb99499SAndroid Build Coastguard Worker    """
33*dbb99499SAndroid Build Coastguard Worker
34*dbb99499SAndroid Build Coastguard Worker    def fmt_toolchain_args(matchobj):
35*dbb99499SAndroid Build Coastguard Worker        suffix = "ignore_root_user_error = True"
36*dbb99499SAndroid Build Coastguard Worker        callargs = matchobj.group(1)
37*dbb99499SAndroid Build Coastguard Worker        # toolchain def is broken over multiple lines
38*dbb99499SAndroid Build Coastguard Worker        if callargs.endswith("\n"):
39*dbb99499SAndroid Build Coastguard Worker            callargs = callargs + "    " + suffix + ",\n"
40*dbb99499SAndroid Build Coastguard Worker        # toolchain def is on one line.
41*dbb99499SAndroid Build Coastguard Worker        else:
42*dbb99499SAndroid Build Coastguard Worker            callargs = callargs + ", " + suffix
43*dbb99499SAndroid Build Coastguard Worker        return "python.toolchain(" + callargs + ")"
44*dbb99499SAndroid Build Coastguard Worker
45*dbb99499SAndroid Build Coastguard Worker    CIBW_LINUX = is_cibuildwheel() and IS_LINUX
46*dbb99499SAndroid Build Coastguard Worker    try:
47*dbb99499SAndroid Build Coastguard Worker        if CIBW_LINUX:
48*dbb99499SAndroid Build Coastguard Worker            module_bazel = Path("MODULE.bazel")
49*dbb99499SAndroid Build Coastguard Worker            content: str = module_bazel.read_text()
50*dbb99499SAndroid Build Coastguard Worker            module_bazel.write_text(
51*dbb99499SAndroid Build Coastguard Worker                re.sub(
52*dbb99499SAndroid Build Coastguard Worker                    r"python.toolchain\(([\w\"\s,.=]*)\)",
53*dbb99499SAndroid Build Coastguard Worker                    fmt_toolchain_args,
54*dbb99499SAndroid Build Coastguard Worker                    content,
55*dbb99499SAndroid Build Coastguard Worker                )
56*dbb99499SAndroid Build Coastguard Worker            )
57*dbb99499SAndroid Build Coastguard Worker        yield
58*dbb99499SAndroid Build Coastguard Worker    finally:
59*dbb99499SAndroid Build Coastguard Worker        if CIBW_LINUX:
60*dbb99499SAndroid Build Coastguard Worker            module_bazel.write_text(content)
61*dbb99499SAndroid Build Coastguard Worker
62*dbb99499SAndroid Build Coastguard Worker
63*dbb99499SAndroid Build Coastguard Workerclass BazelExtension(setuptools.Extension):
64*dbb99499SAndroid Build Coastguard Worker    """A C/C++ extension that is defined as a Bazel BUILD target."""
65*dbb99499SAndroid Build Coastguard Worker
66*dbb99499SAndroid Build Coastguard Worker    def __init__(self, name: str, bazel_target: str, **kwargs: Any):
67*dbb99499SAndroid Build Coastguard Worker        super().__init__(name=name, sources=[], **kwargs)
68*dbb99499SAndroid Build Coastguard Worker
69*dbb99499SAndroid Build Coastguard Worker        self.bazel_target = bazel_target
70*dbb99499SAndroid Build Coastguard Worker        stripped_target = bazel_target.split("//")[-1]
71*dbb99499SAndroid Build Coastguard Worker        self.relpath, self.target_name = stripped_target.split(":")
72*dbb99499SAndroid Build Coastguard Worker
73*dbb99499SAndroid Build Coastguard Worker
74*dbb99499SAndroid Build Coastguard Workerclass BuildBazelExtension(build_ext.build_ext):
75*dbb99499SAndroid Build Coastguard Worker    """A command that runs Bazel to build a C/C++ extension."""
76*dbb99499SAndroid Build Coastguard Worker
77*dbb99499SAndroid Build Coastguard Worker    def run(self):
78*dbb99499SAndroid Build Coastguard Worker        for ext in self.extensions:
79*dbb99499SAndroid Build Coastguard Worker            self.bazel_build(ext)
80*dbb99499SAndroid Build Coastguard Worker        super().run()
81*dbb99499SAndroid Build Coastguard Worker        # explicitly call `bazel shutdown` for graceful exit
82*dbb99499SAndroid Build Coastguard Worker        self.spawn(["bazel", "shutdown"])
83*dbb99499SAndroid Build Coastguard Worker
84*dbb99499SAndroid Build Coastguard Worker    def copy_extensions_to_source(self):
85*dbb99499SAndroid Build Coastguard Worker        """
86*dbb99499SAndroid Build Coastguard Worker        Copy generated extensions into the source tree.
87*dbb99499SAndroid Build Coastguard Worker        This is done in the ``bazel_build`` method, so it's not necessary to
88*dbb99499SAndroid Build Coastguard Worker        do again in the `build_ext` base class.
89*dbb99499SAndroid Build Coastguard Worker        """
90*dbb99499SAndroid Build Coastguard Worker        pass
91*dbb99499SAndroid Build Coastguard Worker
92*dbb99499SAndroid Build Coastguard Worker    def bazel_build(self, ext: BazelExtension) -> None:
93*dbb99499SAndroid Build Coastguard Worker        """Runs the bazel build to create the package."""
94*dbb99499SAndroid Build Coastguard Worker        temp_path = Path(self.build_temp)
95*dbb99499SAndroid Build Coastguard Worker        # omit the patch version to avoid build errors if the toolchain is not
96*dbb99499SAndroid Build Coastguard Worker        # yet registered in the current @rules_python version.
97*dbb99499SAndroid Build Coastguard Worker        # patch version differences should be fine.
98*dbb99499SAndroid Build Coastguard Worker        python_version = ".".join(platform.python_version_tuple()[:2])
99*dbb99499SAndroid Build Coastguard Worker
100*dbb99499SAndroid Build Coastguard Worker        bazel_argv = [
101*dbb99499SAndroid Build Coastguard Worker            "bazel",
102*dbb99499SAndroid Build Coastguard Worker            "run",
103*dbb99499SAndroid Build Coastguard Worker            ext.bazel_target,
104*dbb99499SAndroid Build Coastguard Worker            f"--symlink_prefix={temp_path / 'bazel-'}",
105*dbb99499SAndroid Build Coastguard Worker            f"--compilation_mode={'dbg' if self.debug else 'opt'}",
106*dbb99499SAndroid Build Coastguard Worker            # C++17 is required by nanobind
107*dbb99499SAndroid Build Coastguard Worker            f"--cxxopt={'/std:c++17' if IS_WINDOWS else '-std=c++17'}",
108*dbb99499SAndroid Build Coastguard Worker            f"--@rules_python//python/config_settings:python_version={python_version}",
109*dbb99499SAndroid Build Coastguard Worker        ]
110*dbb99499SAndroid Build Coastguard Worker
111*dbb99499SAndroid Build Coastguard Worker        if ext.py_limited_api:
112*dbb99499SAndroid Build Coastguard Worker            bazel_argv += ["--@nanobind_bazel//:py-limited-api=cp312"]
113*dbb99499SAndroid Build Coastguard Worker
114*dbb99499SAndroid Build Coastguard Worker        if IS_WINDOWS:
115*dbb99499SAndroid Build Coastguard Worker            # Link with python*.lib.
116*dbb99499SAndroid Build Coastguard Worker            for library_dir in self.library_dirs:
117*dbb99499SAndroid Build Coastguard Worker                bazel_argv.append("--linkopt=/LIBPATH:" + library_dir)
118*dbb99499SAndroid Build Coastguard Worker        elif IS_MAC:
119*dbb99499SAndroid Build Coastguard Worker            # C++17 needs macOS 10.14 at minimum
120*dbb99499SAndroid Build Coastguard Worker            bazel_argv.append("--macos_minimum_os=10.14")
121*dbb99499SAndroid Build Coastguard Worker
122*dbb99499SAndroid Build Coastguard Worker        with _maybe_patch_toolchains():
123*dbb99499SAndroid Build Coastguard Worker            self.spawn(bazel_argv)
124*dbb99499SAndroid Build Coastguard Worker
125*dbb99499SAndroid Build Coastguard Worker        if IS_WINDOWS:
126*dbb99499SAndroid Build Coastguard Worker            suffix = ".pyd"
127*dbb99499SAndroid Build Coastguard Worker        else:
128*dbb99499SAndroid Build Coastguard Worker            suffix = ".abi3.so" if ext.py_limited_api else ".so"
129*dbb99499SAndroid Build Coastguard Worker
130*dbb99499SAndroid Build Coastguard Worker        # copy the Bazel build artifacts into setuptools' libdir,
131*dbb99499SAndroid Build Coastguard Worker        # from where the wheel is built.
132*dbb99499SAndroid Build Coastguard Worker        pkgname = "google_benchmark"
133*dbb99499SAndroid Build Coastguard Worker        pythonroot = Path("bindings") / "python" / "google_benchmark"
134*dbb99499SAndroid Build Coastguard Worker        srcdir = temp_path / "bazel-bin" / pythonroot
135*dbb99499SAndroid Build Coastguard Worker        libdir = Path(self.build_lib) / pkgname
136*dbb99499SAndroid Build Coastguard Worker        for root, dirs, files in os.walk(srcdir, topdown=True):
137*dbb99499SAndroid Build Coastguard Worker            # exclude runfiles directories and children.
138*dbb99499SAndroid Build Coastguard Worker            dirs[:] = [d for d in dirs if "runfiles" not in d]
139*dbb99499SAndroid Build Coastguard Worker
140*dbb99499SAndroid Build Coastguard Worker            for f in files:
141*dbb99499SAndroid Build Coastguard Worker                print(f)
142*dbb99499SAndroid Build Coastguard Worker                fp = Path(f)
143*dbb99499SAndroid Build Coastguard Worker                should_copy = False
144*dbb99499SAndroid Build Coastguard Worker                # we do not want the bare .so file included
145*dbb99499SAndroid Build Coastguard Worker                # when building for ABI3, so we require a
146*dbb99499SAndroid Build Coastguard Worker                # full and exact match on the file extension.
147*dbb99499SAndroid Build Coastguard Worker                if "".join(fp.suffixes) == suffix:
148*dbb99499SAndroid Build Coastguard Worker                    should_copy = True
149*dbb99499SAndroid Build Coastguard Worker                elif fp.suffix == ".pyi":
150*dbb99499SAndroid Build Coastguard Worker                    should_copy = True
151*dbb99499SAndroid Build Coastguard Worker                elif Path(root) == srcdir and f == "py.typed":
152*dbb99499SAndroid Build Coastguard Worker                    # copy py.typed, but only at the package root.
153*dbb99499SAndroid Build Coastguard Worker                    should_copy = True
154*dbb99499SAndroid Build Coastguard Worker
155*dbb99499SAndroid Build Coastguard Worker                if should_copy:
156*dbb99499SAndroid Build Coastguard Worker                    shutil.copyfile(root / fp, libdir / fp)
157*dbb99499SAndroid Build Coastguard Worker
158*dbb99499SAndroid Build Coastguard Worker
159*dbb99499SAndroid Build Coastguard Workersetuptools.setup(
160*dbb99499SAndroid Build Coastguard Worker    cmdclass=dict(build_ext=BuildBazelExtension),
161*dbb99499SAndroid Build Coastguard Worker    package_data={"google_benchmark": ["py.typed", "*.pyi"]},
162*dbb99499SAndroid Build Coastguard Worker    ext_modules=[
163*dbb99499SAndroid Build Coastguard Worker        BazelExtension(
164*dbb99499SAndroid Build Coastguard Worker            name="google_benchmark._benchmark",
165*dbb99499SAndroid Build Coastguard Worker            bazel_target="//bindings/python/google_benchmark:benchmark_stubgen",
166*dbb99499SAndroid Build Coastguard Worker            py_limited_api=py_limited_api,
167*dbb99499SAndroid Build Coastguard Worker        )
168*dbb99499SAndroid Build Coastguard Worker    ],
169*dbb99499SAndroid Build Coastguard Worker    options=options,
170*dbb99499SAndroid Build Coastguard Worker)
171