Skip to content

Commit b9c8823

Browse files
committed
feat(python): make setup.py conditional abi3 compatible, and python2 compatible again
1 parent 30ba19b commit b9c8823

File tree

2 files changed

+44
-72
lines changed

2 files changed

+44
-72
lines changed

python/tests/_test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
test_dir = os.path.join(project_dir, 'tests')
1919
if BRO_ARGS[0] is None:
2020
python_exe = sys.executable or 'python'
21-
bro_path = os.path.join(project_dir, 'python', 'bro.py')
21+
bro_path = os.path.join(project_dir, 'python', 'brotli', '__main__.py')
2222
BRO_ARGS = [python_exe, bro_path]
2323

2424
# Get the platform/version-specific build folder.

setup.py

Lines changed: 43 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import platform
88
import re
99
import unittest
10+
import sys
1011

1112
try:
1213
from setuptools import Extension
@@ -15,7 +16,6 @@
1516
from distutils.core import Extension
1617
from distutils.core import setup
1718
from distutils.command.build_ext import build_ext
18-
from distutils import errors
1919
from distutils import dep_util
2020
from distutils import log
2121

@@ -24,7 +24,7 @@
2424
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
2525

2626

27-
def bool_from_environ(key: str):
27+
def bool_from_environ(key):
2828
value = os.environ.get(key)
2929
if not value:
3030
return False
@@ -39,7 +39,7 @@ def read_define(path, macro):
3939
""" Return macro value from the given file. """
4040
with open(path, 'r') as f:
4141
for line in f:
42-
m = re.match(rf'#define\s{macro}\s+(.+)', line)
42+
m = re.match('#define\\s{macro}\\s+(.+)'.format(macro=macro), line)
4343
if m:
4444
return m.group(1)
4545

@@ -54,7 +54,7 @@ def get_version():
5454
patch = read_define(version_file_path, 'BROTLI_VERSION_PATCH')
5555
if not major or not minor or not patch:
5656
return ''
57-
return f'{major}.{minor}.{patch}'
57+
return '{major}.{minor}.{patch}'.format(major=major, minor=minor, patch=patch)
5858

5959

6060
def get_test_suite():
@@ -64,20 +64,7 @@ def get_test_suite():
6464

6565

6666
class BuildExt(build_ext):
67-
68-
def get_source_files(self):
69-
filenames = build_ext.get_source_files(self)
70-
for ext in self.extensions:
71-
filenames.extend(ext.depends)
72-
return filenames
73-
7467
def build_extension(self, ext):
75-
if ext.sources is None or not isinstance(ext.sources, (list, tuple)):
76-
raise errors.DistutilsSetupError(
77-
"in 'ext_modules' option (extension '%s'), "
78-
"'sources' must be present and must be "
79-
"a list of source filenames" % ext.name)
80-
8168
ext_path = self.get_ext_fullpath(ext.name)
8269
depends = ext.sources + ext.depends
8370
if not (self.force or dep_util.newer_group(depends, ext_path, 'newer')):
@@ -86,59 +73,16 @@ def build_extension(self, ext):
8673
else:
8774
log.info("building '%s' extension", ext.name)
8875

89-
c_sources = []
90-
for source in ext.sources:
91-
if source.endswith('.c'):
92-
c_sources.append(source)
93-
extra_args = ext.extra_compile_args or []
94-
95-
objects = []
96-
97-
macros = ext.define_macros[:]
9876
if platform.system() == 'Darwin':
99-
macros.append(('OS_MACOSX', '1'))
77+
ext.define_macros.append(('OS_MACOSX', '1'))
10078
elif self.compiler.compiler_type == 'mingw32':
10179
# On Windows Python 2.7, pyconfig.h defines "hypot" as "_hypot",
10280
# This clashes with GCC's cmath, and causes compilation errors when
10381
# building under MinGW: http://bugs.python.org/issue11566
104-
macros.append(('_hypot', 'hypot'))
105-
for undef in ext.undef_macros:
106-
macros.append((undef,))
107-
108-
objs = self.compiler.compile(
109-
c_sources,
110-
output_dir=self.build_temp,
111-
macros=macros,
112-
include_dirs=ext.include_dirs,
113-
debug=self.debug,
114-
extra_postargs=extra_args,
115-
depends=ext.depends)
116-
objects.extend(objs)
117-
118-
self._built_objects = objects[:]
119-
if ext.extra_objects:
120-
objects.extend(ext.extra_objects)
121-
extra_args = ext.extra_link_args or []
122-
# when using GCC on Windows, we statically link libgcc and libstdc++,
123-
# so that we don't need to package extra DLLs
124-
if self.compiler.compiler_type == 'mingw32':
125-
extra_args.extend(['-static-libgcc', '-static-libstdc++'])
82+
ext.define_macros.append(('_hypot', 'hypot'))
83+
ext.extra_link_args.extend(['-static-libgcc', '-static-libstdc++'])
12684

127-
ext_path = self.get_ext_fullpath(ext.name)
128-
# Detect target language, if not provided
129-
language = ext.language or self.compiler.detect_language(c_sources)
130-
131-
self.compiler.link_shared_object(
132-
objects,
133-
ext_path,
134-
libraries=self.get_libraries(ext),
135-
library_dirs=ext.library_dirs,
136-
runtime_library_dirs=ext.runtime_library_dirs,
137-
extra_postargs=extra_args,
138-
export_symbols=self.get_export_symbols(ext),
139-
debug=self.debug,
140-
build_temp=self.build_temp,
141-
target_lang=language)
85+
build_ext.build_extension(self, ext)
14286

14387

14488
NAME = 'Brotli'
@@ -183,9 +127,20 @@ def build_extension(self, ext):
183127

184128
PACKAGE_DIR = {'': 'python'}
185129

186-
PY_MODULES = ['brotli']
187-
188130
USE_SYSTEM_BROTLI = bool_from_environ('USE_SYSTEM_BROTLI')
131+
BROTLI_PYTHON3_LIMITED_API = bool_from_environ('BROTLI_PYTHON3_LIMITED_API')
132+
IS_PYTHON3 = sys.version_info[0] == 3
133+
134+
class VersionedExtension(Extension):
135+
def __init__(self, *args, **kwargs):
136+
define_macros = []
137+
138+
if IS_PYTHON3 and BROTLI_PYTHON3_LIMITED_API:
139+
kwargs['py_limited_api'] = True
140+
define_macros.append(('Py_LIMITED_API', '0x03060000'))
141+
142+
kwargs['define_macros'] = kwargs.get('define_macros', []) + define_macros
143+
Extension.__init__(self, *args, **kwargs)
189144

190145
if USE_SYSTEM_BROTLI:
191146
import pkgconfig
@@ -205,8 +160,8 @@ def build_extension(self, ext):
205160
libraries += package_configuration["libraries"]
206161
library_dirs += package_configuration["library_dirs"]
207162

208-
brotli_extension = Extension(
209-
'_brotli',
163+
brotli_extension = VersionedExtension(
164+
'brotli._brotli',
210165
sources=[
211166
'python/_brotli.c'
212167
],
@@ -220,8 +175,8 @@ def build_extension(self, ext):
220175
EXT_MODULES = [brotli_extension]
221176
else:
222177
EXT_MODULES = [
223-
Extension(
224-
'_brotli',
178+
VersionedExtension(
179+
name='brotli._brotli',
225180
sources=[
226181
'python/_brotli.c',
227182
'c/common/constants.c',
@@ -323,6 +278,23 @@ def build_extension(self, ext):
323278
'build_ext': BuildExt,
324279
}
325280

281+
if IS_PYTHON3 and BROTLI_PYTHON3_LIMITED_API:
282+
from wheel.bdist_wheel import bdist_wheel
283+
# adopted from:
284+
# https://github.com/joerick/python-abi3-package-sample/blob/7f05b22b9e0cfb4e60293bc85252e95278a80720/setup.py
285+
class bdist_wheel_abi3(bdist_wheel):
286+
def get_tag(self):
287+
python, abi, plat = super().get_tag()
288+
289+
if python.startswith("cp"):
290+
# on CPython, our wheels are abi3 and compatible back to 3.6
291+
return "cp36", "abi3", plat
292+
293+
return python, abi, plat
294+
295+
CMD_CLASS["bdist_wheel"] = bdist_wheel_abi3
296+
297+
326298
with open("README.md", "r") as f:
327299
README = f.read()
328300

@@ -338,7 +310,7 @@ def build_extension(self, ext):
338310
platforms=PLATFORMS,
339311
classifiers=CLASSIFIERS,
340312
package_dir=PACKAGE_DIR,
341-
py_modules=PY_MODULES,
313+
packages=["brotli"],
342314
ext_modules=EXT_MODULES,
343315
test_suite=TEST_SUITE,
344316
cmdclass=CMD_CLASS)

0 commit comments

Comments
 (0)