From c327dc6f0d80e2afe75ed3dc682af8f7f7e5cbf9 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 2 Apr 2020 13:23:58 +0100 Subject: [PATCH] setup.py: optionally build cython extension If Cython is already pre-installed, we opportunistically try to compile the cu2qu extension module, and if it fails (e.g. compiler is not available), then we silently fall back to pure-python mode. If user explicitly requests --with-cython (or sets 'FONTTOOLS_WITH_CYTHON=1' env variable), then we add cython to setup_requires, and raise an error if compilation fails. --- setup.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 669f3fb60..7f9532bb2 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,8 @@ import sys import os from os.path import isfile, join as pjoin from glob import glob -from setuptools import setup, find_packages, Command +from setuptools import setup, find_packages, Command, Extension +from setuptools.command.build_ext import build_ext as _build_ext from distutils import log from distutils.util import convert_path import subprocess as sp @@ -23,10 +24,50 @@ def doraise_py_compile(file, cfile=None, dfile=None, doraise=False): py_compile.compile = doraise_py_compile -needs_wheel = {'bdist_wheel'}.intersection(sys.argv) -wheel = ['wheel'] if needs_wheel else [] -needs_bumpversion = {'release'}.intersection(sys.argv) -bumpversion = ['bump2version'] if needs_bumpversion else [] +setup_requires = [] + +if {'bdist_wheel'}.intersection(sys.argv): + setup_requires.append('wheel') + +if {'release'}.intersection(sys.argv): + setup_requires.append('bump2version') + +try: + __import__("cython") +except ImportError: + has_cython = False +else: + has_cython = True + +env_with_cython = os.environ.get("FONTTOOLS_WITH_CYTHON") +with_cython = ( + True if env_with_cython in {"1", "true", "yes"} + else False if env_with_cython in {"0", "false", "no"} + else None +) +# --with-cython/--without-cython options override environment variables +opt_with_cython = {'--with-cython'}.intersection(sys.argv) +opt_without_cython = {'--without-cython'}.intersection(sys.argv) +if opt_with_cython and opt_without_cython: + sys.exit( + "error: the options '--with-cython' and '--without-cython' are " + "mutually exclusive" + ) +elif opt_with_cython: + sys.argv.remove("--with-cython") + with_cython = True +elif opt_without_cython: + sys.argv.remove("--without-cython") + with_cython = False + +if with_cython and not has_cython: + setup_requires.append("cython") + +ext_modules = [] +if with_cython is True or (with_cython is None and has_cython): + ext_modules.append( + Extension("fontTools.cu2qu.cu2qu", ["Lib/fontTools/cu2qu/cu2qu.py"]), + ) extras_require = { # for fontTools.ufoLib: to read/write UFO fonts @@ -343,7 +384,53 @@ def find_data_files(manpath="share/man"): return data_files -setup( +class cython_build_ext(_build_ext): + """Compile *.pyx source files to *.c using cythonize if Cython is + installed and there is a working C compiler, else fall back to pure python dist. + """ + + def finalize_options(self): + from Cython.Build import cythonize + + # optionally enable line tracing for test coverage support + linetrace = os.environ.get("CYTHON_TRACE") == "1" + + self.distribution.ext_modules[:] = cythonize( + self.distribution.ext_modules, + force=linetrace or self.force, + annotate=os.environ.get("CYTHON_ANNOTATE") == "1", + quiet=not self.verbose, + compiler_directives={ + "linetrace": linetrace, + "language_level": 3, + "embedsignature": True, + }, + ) + + _build_ext.finalize_options(self) + + def build_extensions(self): + try: + _build_ext.build_extensions(self) + except Exception as e: + if with_cython: + raise + from distutils.errors import DistutilsModuleError + + # optional compilation failed: we delete 'ext_modules' and make sure + # the generated wheel is 'pure' + del self.distribution.ext_modules[:] + try: + bdist_wheel = self.get_finalized_command("bdist_wheel") + except DistutilsModuleError: + # 'bdist_wheel' command not available as wheel is not installed + pass + else: + bdist_wheel.root_is_pure = True + log.error('error: building extensions failed: %s' % e) + + +setup_params = dict( name="fonttools", version="4.6.1.dev0", description="Tools to manipulate font files", @@ -360,7 +447,8 @@ setup( packages=find_packages("Lib"), include_package_data=True, data_files=find_data_files(), - setup_requires=wheel + bumpversion, + ext_modules=ext_modules, + setup_requires=setup_requires, extras_require=extras_require, entry_points={ 'console_scripts': [ @@ -372,6 +460,11 @@ setup( }, cmdclass={ "release": release, + "build_ext": cython_build_ext, }, **classifiers ) + + +if __name__ == "__main__": + setup(**setup_params)