xref: /aosp_15_r20/external/cronet/third_party/re2/src/python/setup.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2019 The RE2 Authors.  All Rights Reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file.
4
5import os
6import setuptools
7import setuptools.command.build_ext
8import shutil
9import sys
10import sysconfig
11
12long_description = r"""A drop-in replacement for the re module.
13
14It uses RE2 under the hood, of course, so various PCRE features
15(e.g. backreferences, look-around assertions) are not supported.
16See https://github.com/google/re2/wiki/Syntax for the canonical
17reference, but known syntactic "gotchas" relative to Python are:
18
19  * PCRE supports \Z and \z; RE2 supports \z; Python supports \z,
20    but calls it \Z. You must rewrite \Z to \z in pattern strings.
21
22Known differences between this module's API and the re module's API:
23
24  * The error class does not provide any error information as attributes.
25  * The Options class replaces the re module's flags with RE2's options as
26    gettable/settable properties. Please see re2.h for their documentation.
27  * The pattern string and the input string do not have to be the same type.
28    Any str will be encoded to UTF-8.
29  * The pattern string cannot be str if the options specify Latin-1 encoding.
30
31Known issues with regard to building the C++ extension:
32
33  * Building requires RE2 to be installed on your system.
34    On Debian, for example, install the libre2-dev package.
35  * Building requires pybind11 to be installed on your system OR venv.
36    On Debian, for example, install the pybind11-dev package.
37    For a venv, install the pybind11 package from PyPI.
38  * Building on macOS is known to work, but has been known to fail.
39    For example, the system Python may not know which compiler flags
40    to set when building bindings for software installed by Homebrew;
41    see https://docs.brew.sh/Homebrew-and-Python#brewed-python-modules.
42  * Building on Windows has not been tested yet and will probably fail.
43"""
44
45
46class BuildExt(setuptools.command.build_ext.build_ext):
47
48  def build_extension(self, ext):
49    if 'GITHUB_ACTIONS' not in os.environ:
50      return super().build_extension(ext)
51
52    cmd = ['bazel', 'build']
53    try:
54      cpu = os.environ['BAZEL_CPU']
55      cmd.append(f'--cpu={cpu}')
56      cmd.append(f'--platforms=//python:{cpu}')
57      if cpu == 'x64_x86_windows':
58        # Register the local 32-bit C++ toolchain with highest priority.
59        # (This is likely to break in some release of Bazel after 7.0.0,
60        # but this special case can hopefully be entirely removed then.)
61        cmd.append(f'--extra_toolchains=@local_config_cc//:cc-toolchain-{cpu}')
62    except KeyError:
63      pass
64    try:
65      ver = os.environ['MACOSX_DEPLOYMENT_TARGET']
66      cmd.append(f'--macos_minimum_os={ver}')
67    except KeyError:
68      pass
69    # Register the local Python toolchains with highest priority.
70    self.generate_python_toolchains()
71    cmd.append('--extra_toolchains=//python/toolchains:all')
72    # Print debug information during toolchain resolution.
73    cmd.append('--toolchain_resolution_debug=.*')
74    cmd += ['--compilation_mode=opt', '--', ':all']
75    self.spawn(cmd)
76
77    # This ensures that f'_re2.{importlib.machinery.EXTENSION_SUFFIXES[0]}'
78    # is the filename in the destination directory, which is what's needed.
79    shutil.copyfile('../bazel-bin/python/_re2.so',
80                    self.get_ext_fullpath(ext.name))
81
82    cmd = ['bazel', 'clean', '--expunge']
83    self.spawn(cmd)
84
85  def generate_python_toolchains(self):
86    include = sysconfig.get_path('include')
87    libs = os.path.join(include, '../libs')
88
89    os.makedirs('toolchains')
90    shutil.copytree(include, 'toolchains/include')
91    try:
92      shutil.copytree(libs, 'toolchains/libs')
93    except FileNotFoundError:
94      # We must not be running on Windows. :)
95      pass
96
97    with open('toolchains/BUILD.bazel', 'x') as file:
98      file.write(
99          """\
100load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
101load("@rules_python//python:py_runtime.bzl", "py_runtime")
102load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
103
104package(default_visibility = ["//visibility:public"])
105
106toolchain(
107    name = "py",
108    toolchain = ":py_toolchain",
109    toolchain_type = "@rules_python//python:toolchain_type",
110)
111
112py_runtime_pair(
113    name = "py_toolchain",
114    py3_runtime = ":interpreter",
115)
116
117py_runtime(
118    name = "interpreter",
119    interpreter_path = "{interpreter_path}",
120    interpreter_version_info = {{
121        "major": "{major}",
122        "minor": "{minor}",
123    }},
124    python_version = "PY3",
125)
126
127toolchain(
128    name = "py_cc",
129    toolchain = ":py_cc_toolchain",
130    toolchain_type = "@rules_python//python/cc:toolchain_type",
131)
132
133py_cc_toolchain(
134    name = "py_cc_toolchain",
135    headers = ":headers",
136    libs = ":libraries",
137    python_version = "{major}.{minor}",
138)
139
140cc_library(
141    name = "headers",
142    hdrs = glob(["include/**/*.h"]),
143    includes = ["include"],
144    deps = select({{
145        "@platforms//os:windows": [":interface_library"],
146        "//conditions:default": [],
147    }}),
148)
149
150cc_import(
151    name = "interface_library",
152    interface_library = select({{
153        "@platforms//os:windows": "libs/python{major}{minor}.lib",
154        "//conditions:default": None,
155    }}),
156    system_provided = True,
157)
158
159# Not actually necessary for our purposes. :)
160cc_library(
161    name = "libraries",
162)
163""".format(interpreter_path=sys.executable.replace('\\', '/'),
164           major=sys.version_info.major,
165           minor=sys.version_info.minor))
166
167
168def options():
169  bdist_wheel = {}
170  try:
171    bdist_wheel['plat_name'] = os.environ['PLAT_NAME']
172  except KeyError:
173    pass
174  return {'bdist_wheel': bdist_wheel}
175
176
177def include_dirs():
178  try:
179    import pybind11
180    yield pybind11.get_include()
181  except ModuleNotFoundError:
182    pass
183
184
185ext_module = setuptools.Extension(
186    name='_re2',
187    sources=['_re2.cc'],
188    include_dirs=list(include_dirs()),
189    libraries=['re2'],
190    extra_compile_args=['-fvisibility=hidden'],
191)
192
193setuptools.setup(
194    name='google-re2',
195    version='1.1.20240401',
196    description='RE2 Python bindings',
197    long_description=long_description,
198    long_description_content_type='text/plain',
199    author='The RE2 Authors',
200    author_email='[email protected]',
201    url='https://github.com/google/re2',
202    py_modules=['re2'],
203    ext_modules=[ext_module],
204    classifiers=[
205        'Development Status :: 5 - Production/Stable',
206        'Intended Audience :: Developers',
207        'License :: OSI Approved :: BSD License',
208        'Programming Language :: C++',
209        'Programming Language :: Python :: 3.8',
210    ],
211    options=options(),
212    cmdclass={'build_ext': BuildExt},
213    python_requires='~=3.8',
214)
215