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