77import platform
88import re
99import unittest
10+ import sys
1011
1112try :
1213 from setuptools import Extension
1516 from distutils .core import Extension
1617 from distutils .core import setup
1718from distutils .command .build_ext import build_ext
18- from distutils import errors
1919from distutils import dep_util
2020from distutils import log
2121
2424CURR_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
6060def get_test_suite ():
@@ -64,20 +64,7 @@ def get_test_suite():
6464
6565
6666class 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
14488NAME = 'Brotli'
@@ -183,9 +127,20 @@ def build_extension(self, ext):
183127
184128PACKAGE_DIR = {'' : 'python' }
185129
186- PY_MODULES = ['brotli' ]
187-
188130USE_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
190145if 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 ]
221176else :
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+
326298with 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