Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a93799e

Browse filesBrowse 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
Copy full SHA for a93799e

File tree

Expand file treeCollapse file tree

2 files changed

+38
-110
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+38
-110
lines changed

‎setup.py

Copy file name to clipboardExpand all lines: setup.py
+12-10Lines 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

Copy file name to clipboardExpand all lines: setupext.py
+26-100Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.