1# Copyright 2015 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15 16from setuptools import setup, find_packages, Extension 17from setuptools.command.build_ext import build_ext as _build_ext 18from setuptools.command.sdist import sdist as _sdist 19import pkg_resources 20from distutils import log 21import sys 22import os 23import re 24from io import open 25 26 27needs_pytest = {'pytest', 'test'}.intersection(sys.argv) 28pytest_runner = ['pytest_runner'] if needs_pytest else [] 29needs_wheel = {'bdist_wheel'}.intersection(sys.argv) 30wheel = ['wheel'] if needs_wheel else [] 31 32# Check if minimum required Cython is available. 33# For consistency, we require the same as our vendored Cython.Shadow module 34cymod = "Lib/cu2qu/cython.py" 35cython_version_re = re.compile('__version__ = ["\']([0-9][0-9\w\.]+)["\']') 36with open(cymod, "r", encoding="utf-8") as fp: 37 for line in fp: 38 m = cython_version_re.match(line) 39 if m: 40 cython_min_version = m.group(1) 41 break 42 else: 43 sys.exit("error: failed to parse cython version in '%s'" % cymod) 44 45required_cython = "cython >= %s" % cython_min_version 46try: 47 pkg_resources.require(required_cython) 48except pkg_resources.ResolutionError: 49 has_cython = False 50else: 51 has_cython = True 52 53# First, check if the CU2QU_WITH_CYTHON environment variable is set. 54# Values "1", "true" or "yes" mean that Cython is required and will be used 55# to regenerate the *.c sources from which the native extension is built; 56# "0", "false" or "no" mean that Cython is not required and no extension 57# module will be compiled (i.e. the wheel is pure-python and universal). 58# If the variable is not set, then the pre-generated *.c sources that 59# are included in the sdist package will be used to try build the extension. 60# However, if any error occurs during compilation (e.g. the host 61# machine doesn't have the required compiler toolchain installed), the 62# installation proceeds without the compiled extensions, but will only have 63# the pure-python module. 64env_with_cython = os.environ.get("CU2QU_WITH_CYTHON") 65with_cython = ( 66 True if env_with_cython in {"1", "true", "yes"} 67 else False if env_with_cython in {"0", "false", "no"} 68 else None 69) 70 71# command line options --with-cython and --without-cython are also supported. 72# They override the environment variable 73opt_with_cython = {'--with-cython'}.intersection(sys.argv) 74opt_without_cython = {'--without-cython'}.intersection(sys.argv) 75if opt_with_cython and opt_without_cython: 76 sys.exit( 77 "error: the options '--with-cython' and '--without-cython' are " 78 "mutually exclusive" 79 ) 80elif opt_with_cython: 81 sys.argv.remove("--with-cython") 82 with_cython = True 83elif opt_without_cython: 84 sys.argv.remove("--without-cython") 85 with_cython = False 86 87 88class cython_build_ext(_build_ext): 89 """Compile *.pyx source files to *.c using cythonize if Cython is 90 installed, else use the pre-generated *.c sources. 91 """ 92 93 def finalize_options(self): 94 if with_cython: 95 if not has_cython: 96 from distutils.errors import DistutilsSetupError 97 98 raise DistutilsSetupError( 99 "%s is required when using --with-cython" % required_cython 100 ) 101 102 from Cython.Build import cythonize 103 104 # optionally enable line tracing for test coverage support 105 linetrace = os.environ.get("CYTHON_TRACE") == "1" 106 107 self.distribution.ext_modules[:] = cythonize( 108 self.distribution.ext_modules, 109 force=linetrace or self.force, 110 annotate=os.environ.get("CYTHON_ANNOTATE") == "1", 111 quiet=not self.verbose, 112 compiler_directives={ 113 "linetrace": linetrace, 114 "language_level": 3, 115 "embedsignature": True, 116 }, 117 ) 118 else: 119 # replace *.py/.pyx sources with their pre-generated *.c versions 120 for ext in self.distribution.ext_modules: 121 ext.sources = [re.sub("\.pyx?$", ".c", n) for n in ext.sources] 122 123 _build_ext.finalize_options(self) 124 125 def build_extensions(self): 126 if not has_cython: 127 log.info( 128 "%s is not installed. Pre-generated *.c sources will be " 129 "will be used to build the extensions." % required_cython 130 ) 131 132 try: 133 _build_ext.build_extensions(self) 134 except Exception as e: 135 if with_cython: 136 raise 137 from distutils.errors import DistutilsModuleError 138 139 # optional compilation failed: we delete 'ext_modules' and make sure 140 # the generated wheel is 'pure' 141 del self.distribution.ext_modules[:] 142 try: 143 bdist_wheel = self.get_finalized_command("bdist_wheel") 144 except DistutilsModuleError: 145 # 'bdist_wheel' command not available as wheel is not installed 146 pass 147 else: 148 bdist_wheel.root_is_pure = True 149 log.error('error: building extensions failed: %s' % e) 150 151 def get_source_files(self): 152 filenames = _build_ext.get_source_files(self) 153 154 # include pre-generated *.c sources inside sdist, but only if cython is 155 # installed (and hence they will be updated upon making the sdist) 156 if has_cython: 157 for ext in self.extensions: 158 filenames.extend( 159 [re.sub("\.pyx?$", ".c", n) for n in ext.sources] 160 ) 161 return filenames 162 163 164class cython_sdist(_sdist): 165 """ Run 'cythonize' on *.pyx sources to ensure the *.c files included 166 in the source distribution are up-to-date. 167 """ 168 169 def run(self): 170 if with_cython and not has_cython: 171 from distutils.errors import DistutilsSetupError 172 173 raise DistutilsSetupError( 174 "%s is required when creating sdist --with-cython" 175 % required_cython 176 ) 177 178 if has_cython: 179 from Cython.Build import cythonize 180 181 cythonize( 182 self.distribution.ext_modules, 183 force=True, # always regenerate *.c sources 184 quiet=not self.verbose, 185 compiler_directives={ 186 "language_level": 3, 187 "embedsignature": True 188 }, 189 ) 190 191 _sdist.run(self) 192 193 194# don't build extensions if user explicitly requested --without-cython 195if with_cython is False: 196 extensions = [] 197else: 198 extensions = [ 199 Extension("cu2qu.cu2qu", ["Lib/cu2qu/cu2qu.py"]), 200 ] 201 202with open('README.rst', 'r') as f: 203 long_description = f.read() 204 205setup( 206 name='cu2qu', 207 use_scm_version={"write_to": "Lib/cu2qu/_version.py"}, 208 description='Cubic-to-quadratic bezier curve conversion', 209 author="James Godfrey-Kittle, Behdad Esfahbod", 210 author_email="jamesgk@google.com", 211 url="https://github.com/googlei18n", 212 license="Apache License, Version 2.0", 213 long_description=long_description, 214 packages=find_packages('Lib'), 215 package_dir={'': 'Lib'}, 216 ext_modules=extensions, 217 include_package_data=True, 218 setup_requires=pytest_runner + wheel + ["setuptools_scm"], 219 tests_require=[ 220 'pytest>=2.8', 221 ], 222 install_requires=[ 223 "fonttools[ufo] >= 3.32.0", 224 ], 225 extras_require={"cli": ["defcon >= 0.6.0"]}, 226 entry_points={"console_scripts": ["cu2qu = cu2qu.cli:main [cli]"]}, 227 classifiers=[ 228 'Development Status :: 4 - Beta', 229 'Intended Audience :: Developers', 230 'License :: OSI Approved :: Apache Software License', 231 'Operating System :: OS Independent', 232 'Programming Language :: Python', 233 'Programming Language :: Python :: 2', 234 'Programming Language :: Python :: 3', 235 'Topic :: Scientific/Engineering :: Mathematics', 236 'Topic :: Multimedia :: Graphics :: Graphics Conversion', 237 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', 238 'Topic :: Software Development :: Libraries :: Python Modules', 239 ], 240 cmdclass={"build_ext": cython_build_ext, "sdist": cython_sdist}, 241) 242