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