1import sys 2 3vi = sys.version_info 4if vi < (3, 5): 5 raise RuntimeError('httptools require Python 3.5 or greater') 6else: 7 import os.path 8 import pathlib 9 10 from setuptools import setup, Extension 11 from setuptools.command.build_ext import build_ext as build_ext 12 13 14CFLAGS = ['-O2'] 15 16ROOT = pathlib.Path(__file__).parent 17 18CYTHON_DEPENDENCY = 'Cython(>=0.29.24,<0.30.0)' 19 20 21class httptools_build_ext(build_ext): 22 user_options = build_ext.user_options + [ 23 ('cython-always', None, 24 'run cythonize() even if .c files are present'), 25 ('cython-annotate', None, 26 'Produce a colorized HTML version of the Cython source.'), 27 ('cython-directives=', None, 28 'Cythion compiler directives'), 29 ('use-system-llhttp', None, 30 'Use the system provided llhttp, instead of the bundled one'), 31 ('use-system-http-parser', None, 32 'Use the system provided http-parser, instead of the bundled one'), 33 ] 34 35 boolean_options = build_ext.boolean_options + [ 36 'cython-always', 37 'cython-annotate', 38 'use-system-llhttp', 39 'use-system-http-parser', 40 ] 41 42 def initialize_options(self): 43 # initialize_options() may be called multiple times on the 44 # same command object, so make sure not to override previously 45 # set options. 46 if getattr(self, '_initialized', False): 47 return 48 49 super().initialize_options() 50 self.use_system_llhttp = False 51 self.use_system_http_parser = True 52 self.cython_always = False 53 self.cython_annotate = None 54 self.cython_directives = None 55 56 def finalize_options(self): 57 # finalize_options() may be called multiple times on the 58 # same command object, so make sure not to override previously 59 # set options. 60 if getattr(self, '_initialized', False): 61 return 62 63 need_cythonize = self.cython_always 64 cfiles = {} 65 66 for extension in self.distribution.ext_modules: 67 for i, sfile in enumerate(extension.sources): 68 if sfile.endswith('.pyx'): 69 prefix, ext = os.path.splitext(sfile) 70 cfile = prefix + '.c' 71 72 if os.path.exists(cfile) and not self.cython_always: 73 extension.sources[i] = cfile 74 else: 75 if os.path.exists(cfile): 76 cfiles[cfile] = os.path.getmtime(cfile) 77 else: 78 cfiles[cfile] = 0 79 need_cythonize = True 80 81 if need_cythonize: 82 try: 83 import Cython 84 except ImportError: 85 raise RuntimeError( 86 'please install Cython to compile httptools from source') 87 88 if Cython.__version__ < '0.29': 89 raise RuntimeError( 90 'httptools requires Cython version 0.29 or greater') 91 92 from Cython.Build import cythonize 93 94 directives = {} 95 if self.cython_directives: 96 for directive in self.cython_directives.split(','): 97 k, _, v = directive.partition('=') 98 if v.lower() == 'false': 99 v = False 100 if v.lower() == 'true': 101 v = True 102 103 directives[k] = v 104 105 self.distribution.ext_modules[:] = cythonize( 106 self.distribution.ext_modules, 107 compiler_directives=directives, 108 annotate=self.cython_annotate) 109 110 super().finalize_options() 111 112 self._initialized = True 113 114 def build_extensions(self): 115 mod_parser, mod_url_parser = self.distribution.ext_modules 116 if self.use_system_llhttp: 117 mod_parser.libraries.append('llhttp') 118 119 if sys.platform == 'darwin' and \ 120 os.path.exists('/opt/local/include'): 121 # Support macports on Mac OS X. 122 mod_parser.include_dirs.append('/opt/local/include') 123 else: 124 mod_parser.include_dirs.append( 125 str(ROOT / 'vendor' / 'llhttp' / 'include')) 126 mod_parser.include_dirs.append( 127 str(ROOT / 'vendor' / 'llhttp' / 'src')) 128 mod_parser.sources.append('vendor/llhttp/src/api.c') 129 mod_parser.sources.append('vendor/llhttp/src/http.c') 130 mod_parser.sources.append('vendor/llhttp/src/llhttp.c') 131 132 if self.use_system_http_parser: 133 mod_url_parser.libraries.append('http_parser') 134 135 if sys.platform == 'darwin' and \ 136 os.path.exists('/opt/local/include'): 137 # Support macports on Mac OS X. 138 mod_url_parser.include_dirs.append('/opt/local/include') 139 else: 140 mod_url_parser.include_dirs.append( 141 str(ROOT / 'vendor' / 'http-parser')) 142 mod_url_parser.sources.append( 143 'vendor/http-parser/http_parser.c') 144 145 super().build_extensions() 146 147 148with open(str(ROOT / 'README.md')) as f: 149 long_description = f.read() 150 151 152with open(str(ROOT / 'httptools' / '_version.py')) as f: 153 for line in f: 154 if line.startswith('__version__ ='): 155 _, _, version = line.partition('=') 156 VERSION = version.strip(" \n'\"") 157 break 158 else: 159 raise RuntimeError( 160 'unable to read the version from httptools/_version.py') 161 162 163setup_requires = [] 164 165if (not (ROOT / 'httptools' / 'parser' / 'parser.c').exists() or 166 '--cython-always' in sys.argv): 167 # No Cython output, require Cython to build. 168 setup_requires.append(CYTHON_DEPENDENCY) 169 170 171setup( 172 name='httptools', 173 version=VERSION, 174 description='A collection of framework independent HTTP protocol utils.', 175 long_description=long_description, 176 long_description_content_type='text/markdown', 177 url='https://github.com/MagicStack/httptools', 178 classifiers=[ 179 'License :: OSI Approved :: MIT License', 180 'Intended Audience :: Developers', 181 'Programming Language :: Python :: 3', 182 'Operating System :: POSIX', 183 'Operating System :: MacOS :: MacOS X', 184 'Environment :: Web Environment', 185 'Development Status :: 5 - Production/Stable', 186 ], 187 platforms=['macOS', 'POSIX', 'Windows'], 188 python_requires='>=3.5.0', 189 zip_safe=False, 190 author='Yury Selivanov', 191 author_email='yury@magic.io', 192 license='MIT', 193 packages=['httptools', 'httptools.parser'], 194 cmdclass={ 195 'build_ext': httptools_build_ext, 196 }, 197 ext_modules=[ 198 Extension( 199 "httptools.parser.parser", 200 sources=[ 201 "httptools/parser/parser.pyx", 202 ], 203 extra_compile_args=CFLAGS, 204 ), 205 Extension( 206 "httptools.parser.url_parser", 207 sources=[ 208 "httptools/parser/url_parser.pyx", 209 ], 210 extra_compile_args=CFLAGS, 211 ), 212 ], 213 include_package_data=True, 214 test_suite='tests.suite', 215 setup_requires=setup_requires, 216 extras_require={ 217 'test': [ 218 CYTHON_DEPENDENCY 219 ] 220 } 221) 222