Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a93799e

Browse files
committed
Simplify extension setup.
setupext currently has a complex machinery to define extension modules. Essentially, the problem is that numpy may not be installed at the time the extensions are declared, so we can't call np.get_include() to get the include paths. So we use a DelayedExtension class and a hook mechanism to inject the numpy include path later, once it becomes available. Instead, we can just declare a dummy extension (so that setuptools doesn't elide the call to build_ext), then swap in the correct extensions in build_ext.finalize_options(): at that point, numpy will have been installed and we don't need any crazy machinery.
1 parent 32ad86f commit a93799e

File tree

2 files changed

+38
-110
lines changed

2 files changed

+38
-110
lines changed

setup.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# and/or pip.
99
from string import Template
1010
import sys
11-
from setuptools import setup
11+
from setuptools import setup, Extension
1212
from setuptools.command.test import test as TestCommand
1313
from setuptools.command.build_ext import build_ext as BuildExtCommand
1414

@@ -108,6 +108,12 @@ def __init__(self, dist):
108108

109109

110110
class BuildExtraLibraries(BuildExtCommand):
111+
def finalize_options(self):
112+
self.distribution.ext_modules[:] = [
113+
*filter(None, (package.get_extension()
114+
for package in good_packages))]
115+
super().finalize_options()
116+
111117
def run(self):
112118
for package in good_packages:
113119
package.do_custom_build()
@@ -128,7 +134,9 @@ def run(self):
128134
packages = []
129135
namespace_packages = []
130136
py_modules = []
131-
ext_modules = []
137+
# Dummy extension to trigger build_ext, which will swap it out with real
138+
# extensions that can depend on numpy for the build.
139+
ext_modules = [Extension('', [])]
132140
package_data = {}
133141
package_dir = {'': 'lib'}
134142
install_requires = []
@@ -192,9 +200,8 @@ def run(self):
192200
packages.extend(package.get_packages())
193201
namespace_packages.extend(package.get_namespace_packages())
194202
py_modules.extend(package.get_py_modules())
195-
ext = package.get_extension()
196-
if ext is not None:
197-
ext_modules.append(ext)
203+
# Extension modules only get added in build_ext, as numpy will have
204+
# been installed (as setup_requires) at that point.
198205
data = package.get_package_data()
199206
for key, val in data.items():
200207
package_data.setdefault(key, [])
@@ -212,11 +219,6 @@ def run(self):
212219
with open('lib/matplotlib/mpl-data/matplotlibrc', 'w') as fd:
213220
fd.write(template)
214221

215-
# Finalize the extension modules so they can get the Numpy include
216-
# dirs
217-
for mod in ext_modules:
218-
mod.finalize()
219-
220222
# Finally, pass this all along to distutils to do the heavy lifting.
221223
setup(
222224
name="matplotlib",

setupext.py

Lines changed: 26 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def make_extension(name, files, *args, **kwargs):
231231
Any additional arguments are passed to the
232232
`distutils.core.Extension` constructor.
233233
"""
234-
ext = DelayedExtension(name, files, *args, **kwargs)
234+
ext = Extension(name, files, *args, **kwargs)
235235
for dir in get_base_dirs():
236236
include_dir = os.path.join(dir, 'include')
237237
if os.path.exists(include_dir):
@@ -241,7 +241,6 @@ def make_extension(name, files, *args, **kwargs):
241241
if os.path.exists(lib_dir):
242242
ext.library_dirs.append(lib_dir)
243243
ext.include_dirs.append('.')
244-
245244
return ext
246245

247246

@@ -800,77 +799,9 @@ def get_namespace_packages(self):
800799
return ['mpl_toolkits']
801800

802801

803-
class DelayedExtension(Extension, object):
804-
"""
805-
A distutils Extension subclass where some of its members
806-
may have delayed computation until reaching the build phase.
807-
808-
This is so we can, for example, get the Numpy include dirs
809-
after pip has installed Numpy for us if it wasn't already
810-
on the system.
811-
"""
812-
def __init__(self, *args, **kwargs):
813-
super().__init__(*args, **kwargs)
814-
self._finalized = False
815-
self._hooks = {}
816-
817-
def add_hook(self, member, func):
818-
"""
819-
Add a hook to dynamically compute a member.
820-
821-
Parameters
822-
----------
823-
member : string
824-
The name of the member
825-
826-
func : callable
827-
The function to call to get dynamically-computed values
828-
for the member.
829-
"""
830-
self._hooks[member] = func
831-
832-
def finalize(self):
833-
self._finalized = True
834-
835-
class DelayedMember(property):
836-
def __init__(self, name):
837-
self._name = name
838-
839-
def __get__(self, obj, objtype=None):
840-
result = getattr(obj, '_' + self._name, [])
841-
842-
if obj._finalized:
843-
if self._name in obj._hooks:
844-
result = obj._hooks[self._name]() + result
845-
846-
return result
847-
848-
def __set__(self, obj, value):
849-
setattr(obj, '_' + self._name, value)
850-
851-
include_dirs = DelayedMember('include_dirs')
852-
853-
854802
class Numpy(SetupPackage):
855803
name = "numpy"
856804

857-
@staticmethod
858-
def include_dirs_hook():
859-
if hasattr(builtins, '__NUMPY_SETUP__'):
860-
del builtins.__NUMPY_SETUP__
861-
import numpy
862-
importlib.reload(numpy)
863-
864-
ext = Extension('test', [])
865-
ext.include_dirs.append(numpy.get_include())
866-
if not has_include_file(
867-
ext.include_dirs, os.path.join("numpy", "arrayobject.h")):
868-
warnings.warn(
869-
"The C headers for numpy could not be found. "
870-
"You may need to install the development package")
871-
872-
return [numpy.get_include()]
873-
874805
def check(self):
875806
min_version = extract_versions()['__version__numpy__']
876807
try:
@@ -886,16 +817,14 @@ def check(self):
886817
return 'version %s' % numpy.__version__
887818

888819
def add_flags(self, ext):
820+
import numpy as np
821+
ext.include_dirs.append(np.get_include())
889822
# Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for
890823
# each extension
891824
array_api_name = 'MPL_' + ext.name.replace('.', '_') + '_ARRAY_API'
892-
893825
ext.define_macros.append(('PY_ARRAY_UNIQUE_SYMBOL', array_api_name))
894-
ext.add_hook('include_dirs', self.include_dirs_hook)
895-
896826
ext.define_macros.append(('NPY_NO_DEPRECATED_API',
897827
'NPY_1_7_API_VERSION'))
898-
899828
# Allow NumPy's printf format specifiers in C++.
900829
ext.define_macros.append(('__STDC_FORMAT_MACROS', 1))
901830

@@ -910,32 +839,23 @@ class LibAgg(SetupPackage):
910839
name = 'libagg'
911840

912841
def check(self):
913-
self.__class__.found_external = True
914-
try:
915-
return self._check_for_pkg_config(
916-
'libagg', 'agg2/agg_basics.h', min_version='PATCH')
917-
except CheckFailed as e:
918-
self.__class__.found_external = False
919-
return str(e) + ' Using local copy.'
842+
return 'requires patched version; using local copy.'
920843

921844
def add_flags(self, ext, add_sources=True):
922-
if self.found_external:
923-
pkg_config.setup_extension(ext, 'libagg')
924-
else:
925-
ext.include_dirs.insert(0, 'extern/agg24-svn/include')
926-
if add_sources:
927-
agg_sources = [
928-
'agg_bezier_arc.cpp',
929-
'agg_curves.cpp',
930-
'agg_image_filters.cpp',
931-
'agg_trans_affine.cpp',
932-
'agg_vcgen_contour.cpp',
933-
'agg_vcgen_dash.cpp',
934-
'agg_vcgen_stroke.cpp',
935-
'agg_vpgen_segmentator.cpp'
936-
]
937-
ext.sources.extend(
938-
os.path.join('extern', 'agg24-svn', 'src', x) for x in agg_sources)
845+
ext.include_dirs.insert(0, 'extern/agg24-svn/include')
846+
if add_sources:
847+
agg_sources = [
848+
'agg_bezier_arc.cpp',
849+
'agg_curves.cpp',
850+
'agg_image_filters.cpp',
851+
'agg_trans_affine.cpp',
852+
'agg_vcgen_contour.cpp',
853+
'agg_vcgen_dash.cpp',
854+
'agg_vcgen_stroke.cpp',
855+
'agg_vpgen_segmentator.cpp'
856+
]
857+
ext.sources.extend(
858+
os.path.join('extern', 'agg24-svn', 'src', x) for x in agg_sources)
939859

940860

941861
class FreeType(SetupPackage):
@@ -1211,7 +1131,14 @@ def get_extension(self):
12111131
pkg_config.setup_extension(
12121132
ext, 'libpng', default_libraries=['png', 'z'],
12131133
alt_exec='libpng-config --ldflags')
1214-
Numpy().add_flags(ext)
1134+
# We call this twice: once early, to check whether png exists (from
1135+
# _check_for_pkg_config), and once late, to build the real extension.
1136+
# Only at the second time is numpy guaranteed to be installed, so don't
1137+
# error out if it isn't the first time.
1138+
try:
1139+
Numpy().add_flags(ext)
1140+
except ImportError:
1141+
pass
12151142
return ext
12161143

12171144

@@ -1224,7 +1151,6 @@ def check(self):
12241151
return self._check_for_pkg_config(
12251152
'libqhull', 'libqhull/qhull_a.h', min_version='2015.2')
12261153
except CheckFailed as e:
1227-
self.__class__.found_pkgconfig = False
12281154
self.__class__.found_external = False
12291155
return str(e) + ' Using local copy.'
12301156

0 commit comments

Comments
 (0)