Change check for Cython to attempt fallback to setuptools on supporte… (#5998)

* Change check for Cython to attempt fallback to setuptools on supported platforms.

This attempts to solve Issue #5984 without causing more problems for Issue #5558.

* Correct argument to extras_require.

* Refactor the declaration and usage of Cython

- Use setuptools.setup by default if available for import.
- The objective for that complicated import/cython is commented.
- Also have more specific variable names, and have their usage be
  clarified.

- Remove Cython from install_requires as it already is declared under
  setup_requires, and that it conflicts with install_requires due to
  issues in setuptools.

  - https://github.com/pypa/setuptools/issues/209
  - https://github.com/pypa/setuptools/issues/391

- This commit goes back to breaking installation in environments without
  Cython, but should be rectified in the next commit.

* Actually fix the specific usage of Cython

- In order for setup.py to make use of the Cython installed during by
  the setup_requires to be usable, it must be imported after it is
  installed by that step, thus it cannot be done at the top level and
  this can be achieved by importing it when it's actually called. A
  build_ext factory function is created for this.
- Still allow whatever Cython already present on the current environment
  be attempted to be used.  This may end up being unnecessary as if it
  is always declared in setup_requires (which isn't currently the case
  due to the complicated/documented block), it _should_ pull in the
  exact/supported version of Cython for usage.  This should be
  investigated so that this complicated logic may be avoided.

* Make distutils happy by not using factory function

- As it turns out, calling setup.py build_ext will result in a code path
  within setuptools that will invoke distutils in a way that forces an
  issubclass check, which ultimately results in an exception due to that
  function not being a class.
- To fix this, we go back to constructing a base class except also using
  the "new" style class (to make Python 2 happy) so that the __new__
  method will actually be called so that the logic to select the Cython
  version of build_ext will be invoked.
- Also use the super class protocol to invoke parent methods.

* Declare version bounds directly on setup_requires

- This allows us to fully remove the brittle version checks
- Also this allows us to directly declare setup_requires if Cython is
  definitely required, as this would allow the correct version to be
  installed directly by setuptools during the execution of the setup
  step.
- No need to check for failed Cython imports due to the presence of the
  setup_requires declaration.

* Add comment explaining the initialisation routine for KivyBuildExt.

Details of how setuptools deals with both cmdclass and when setup_requires
dependencies come in to scope are both relevant.

* Bring comment in to line with earlier changes.

The cython checks are significantly simpler now, and the rationale is also slightly different.
This commit is contained in:
Justin Marsh (:flaviusb) 2019-03-04 23:49:42 +13:00 committed by Mathieu Virbel
parent 900237424e
commit a6a276b0a7
1 changed files with 64 additions and 43 deletions

107
setup.py
View File

@ -14,17 +14,18 @@ from copy import deepcopy
import os
from os.path import join, dirname, sep, exists, basename, isdir
from os import walk, environ, makedirs
from distutils.command.build_ext import build_ext
from distutils.version import LooseVersion
from distutils.sysconfig import get_python_inc
from collections import OrderedDict
from time import sleep, time
from time import time
from subprocess import check_output, CalledProcessError
from datetime import datetime
if environ.get('KIVY_USE_SETUPTOOLS'):
try:
from setuptools import setup, Extension
print('Using setuptools')
else:
except ImportError:
from distutils.core import setup
from distutils.extension import Extension
print('Using distutils')
@ -83,6 +84,13 @@ CYTHON_UNSUPPORTED = (
# ref https://github.com/cython/cython/issues/1968
'0.27', '0.27.2'
)
CYTHON_REQUIRES_STRING = (
'cython>={min_version},<={max_version},{exclusion}'.format(
min_version=MIN_CYTHON_STRING,
max_version=MAX_CYTHON_STRING,
exclusion=','.join('!=%s' % excl for excl in CYTHON_UNSUPPORTED),
)
)
def getoutput(cmd, env=None):
@ -231,35 +239,24 @@ cython_unsupported = '''\
'''.format(MIN_CYTHON_STRING, MAX_CYTHON_STRING,
cython_unsupported_append)
have_cython = False
skip_cython = False
if platform in ('ios', 'android'):
print('\nCython check avoided.')
skip_cython = True
else:
try:
# check for cython
from Cython.Distutils import build_ext
have_cython = True
import Cython
cy_version_str = Cython.__version__
cy_ver = LooseVersion(cy_version_str)
print('\nDetected Cython version {}'.format(cy_version_str))
if cy_ver < MIN_CYTHON_VERSION:
print(cython_min)
raise ImportError('Incompatible Cython Version')
if cy_ver in CYTHON_UNSUPPORTED:
print(cython_unsupported)
raise ImportError('Incompatible Cython Version')
if cy_ver > MAX_CYTHON_VERSION:
print(cython_max)
sleep(1)
except ImportError:
print("\nCython is missing, it's required for compiling kivy !\n\n")
raise
# We want to be able to install kivy as a wheel without a dependency
# on cython, but we also want to use cython where possible as a setup
# time dependency through `setup_requires` if building from source.
if not have_cython:
from distutils.command.build_ext import build_ext
# There are issues with using cython at all on some platforms;
# exclude them from using or declaring cython.
# This determines whether Cython specific functionality may be used.
can_use_cython = True
# This sets whether or not Cython gets added to setup_requires.
declare_cython = False
if platform in ('ios', 'android'):
# NEVER use or declare cython on these platforms
print('Not using cython on %s' % platform)
can_use_cython = False
else:
declare_cython = True
# -----------------------------------------------------------------------------
# Setup classes
@ -268,10 +265,31 @@ if not have_cython:
src_path = build_path = dirname(__file__)
class KivyBuildExt(build_ext):
class KivyBuildExt(build_ext, object):
def __new__(cls, *a, **kw):
# Note how this class is declared as a subclass of distutils
# build_ext as the Cython version may not be available in the
# environment it is initially started in. However, if Cython
# can be used, setuptools will bring Cython into the environment
# thus its version of build_ext will become available.
# The reason why this is done as a __new__ rather than through a
# factory function is because there are distutils functions that check
# the values provided by cmdclass with issublcass, and so it would
# result in an exception.
# The following essentially supply a dynamically generated subclass
# that mix in the cython version of build_ext so that the
# functionality provided will also be executed.
if can_use_cython:
from Cython.Distutils import build_ext as cython_build_ext
build_ext_cls = type(
'KivyBuildExt', (KivyBuildExt, cython_build_ext), {})
return super(KivyBuildExt, cls).__new__(build_ext_cls)
else:
return super(KivyBuildExt, cls).__new__(cls)
def finalize_options(self):
retval = build_ext.finalize_options(self)
retval = super(KivyBuildExt, self).finalize_options()
global build_path
if (self.build_lib is not None and exists(self.build_lib) and
not self.inplace):
@ -328,7 +346,7 @@ class KivyBuildExt(build_ext):
for e in self.extensions:
e.extra_link_args += ['-lm']
build_ext.build_extensions(self)
super(KivyBuildExt, self).build_extensions()
def update_if_changed(self, fn, content):
need_update = True
@ -949,7 +967,8 @@ def get_extensions_from_sources(sources):
pyx = expand(src_path, pyx)
depends = [expand(src_path, x) for x in flags.pop('depends', [])]
c_depends = [expand(src_path, x) for x in flags.pop('c_depends', [])]
if not have_cython:
if not can_use_cython:
# can't use cython, so use the .c files instead.
pyx = '%s.c' % pyx[:-4]
if is_graphics:
depends = resolve_dependencies(pyx, depends)
@ -998,6 +1017,12 @@ if isdir(binary_deps_path):
# -----------------------------------------------------------------------------
# setup !
if not build_examples:
install_requires = [
'Kivy-Garden>=0.1.4', 'docutils', 'pygments'
]
setup_requires = []
if declare_cython:
setup_requires.append(CYTHON_REQUIRES_STRING)
setup(
name='Kivy',
version=get_version(),
@ -1127,15 +1152,11 @@ if not build_examples:
'Topic :: Software Development :: User Interfaces'],
dependency_links=[
'https://github.com/kivy-garden/garden/archive/master.zip'],
install_requires=[
'Kivy-Garden>=0.1.4', 'docutils', 'pygments'
],
extra_requires={
install_requires=install_requires,
setup_requires=setup_requires,
extras_require={
'tuio': ['oscpy']
},
setup_requires=[
'cython>=' + MIN_CYTHON_STRING
] if not skip_cython else [])
})
else:
setup(
name='Kivy-examples',