|
2 | 2 | import configparser
|
3 | 3 | from distutils import sysconfig, version
|
4 | 4 | from distutils.core import Extension
|
5 |
| -from io import BytesIO |
| 5 | +import functools |
6 | 6 | import glob
|
7 | 7 | import hashlib
|
8 | 8 | import importlib
|
| 9 | +from io import BytesIO |
9 | 10 | import logging
|
10 | 11 | import os
|
11 | 12 | import pathlib
|
@@ -214,83 +215,73 @@ def get_buffer_hash(fd):
|
214 | 215 | return hasher.hexdigest()
|
215 | 216 |
|
216 | 217 |
|
217 |
| -class PkgConfig(object): |
218 |
| - """This is a class for communicating with pkg-config.""" |
219 |
| - |
220 |
| - def __init__(self): |
221 |
| - """Determines whether pkg-config exists on this machine.""" |
222 |
| - self.pkg_config = None |
223 |
| - if sys.platform != 'win32': |
224 |
| - pkg_config = os.environ.get('PKG_CONFIG', 'pkg-config') |
225 |
| - if shutil.which(pkg_config) is not None: |
226 |
| - self.pkg_config = pkg_config |
227 |
| - self.set_pkgconfig_path() |
228 |
| - else: |
229 |
| - print("IMPORTANT WARNING:\n" |
230 |
| - " pkg-config is not installed.\n" |
231 |
| - " matplotlib may not be able to find some of its dependencies") |
232 |
| - |
233 |
| - def set_pkgconfig_path(self): |
234 |
| - pkgconfig_path = sysconfig.get_config_var('LIBDIR') |
235 |
| - if pkgconfig_path is None: |
236 |
| - return |
237 |
| - |
238 |
| - pkgconfig_path = os.path.join(pkgconfig_path, 'pkgconfig') |
239 |
| - if not os.path.isdir(pkgconfig_path): |
240 |
| - return |
241 |
| - |
| 218 | +@functools.lru_cache(1) # We only need to compute this once. |
| 219 | +def get_pkg_config(): |
| 220 | + """ |
| 221 | + Get path to pkg-config and set up the PKG_CONFIG environment variable. |
| 222 | + """ |
| 223 | + if sys.platform == 'win32': |
| 224 | + return None |
| 225 | + pkg_config = os.environ.get('PKG_CONFIG', 'pkg-config') |
| 226 | + if shutil.which(pkg_config) is None: |
| 227 | + print("IMPORTANT WARNING:\n" |
| 228 | + " pkg-config is not installed.\n" |
| 229 | + " matplotlib may not be able to find some of its dependencies.") |
| 230 | + return None |
| 231 | + pkg_config_path = sysconfig.get_config_var('LIBDIR') |
| 232 | + if pkg_config_path is not None: |
| 233 | + pkg_config_path = os.path.join(pkg_config_path, 'pkgconfig') |
242 | 234 | try:
|
243 |
| - os.environ['PKG_CONFIG_PATH'] += ':' + pkgconfig_path |
| 235 | + os.environ['PKG_CONFIG_PATH'] += ':' + pkg_config_path |
244 | 236 | except KeyError:
|
245 |
| - os.environ['PKG_CONFIG_PATH'] = pkgconfig_path |
| 237 | + os.environ['PKG_CONFIG_PATH'] = pkg_config_path |
| 238 | + return pkg_config |
246 | 239 |
|
247 |
| - def setup_extension( |
248 |
| - self, ext, package, |
249 |
| - atleast_version=None, alt_exec=None, default_libraries=()): |
250 |
| - """Add parameters to the given *ext* for the given *package*.""" |
251 | 240 |
|
252 |
| - # First, try to get the flags from pkg-config. |
253 |
| - |
254 |
| - cmd = ([self.pkg_config, package] if self.pkg_config else alt_exec) |
255 |
| - if cmd is not None: |
256 |
| - try: |
257 |
| - if self.pkg_config and atleast_version: |
258 |
| - subprocess.check_call( |
259 |
| - [*cmd, f"--atleast-version={atleast_version}"]) |
260 |
| - # Use sys.getfilesystemencoding() to allow round-tripping |
261 |
| - # when passed back to later subprocess calls; do not use |
262 |
| - # locale.getpreferredencoding() which universal_newlines=True |
263 |
| - # would do. |
264 |
| - cflags = shlex.split( |
265 |
| - os.fsdecode(subprocess.check_output([*cmd, "--cflags"]))) |
266 |
| - libs = shlex.split( |
267 |
| - os.fsdecode(subprocess.check_output([*cmd, "--libs"]))) |
268 |
| - except (OSError, subprocess.CalledProcessError): |
269 |
| - pass |
270 |
| - else: |
271 |
| - ext.extra_compile_args.extend(cflags) |
272 |
| - ext.extra_link_args.extend(libs) |
273 |
| - return |
| 241 | +def pkg_config_setup_extension( |
| 242 | + ext, package, |
| 243 | + atleast_version=None, alt_exec=None, default_libraries=()): |
| 244 | + """Add parameters to the given *ext* for the given *package*.""" |
274 | 245 |
|
275 |
| - # If that fails, fall back on the defaults. |
| 246 | + # First, try to get the flags from pkg-config. |
276 | 247 |
|
277 |
| - # conda Windows header and library paths. |
278 |
| - # https://github.com/conda/conda/issues/2312 re: getting the env dir. |
279 |
| - if sys.platform == 'win32': |
280 |
| - conda_env_path = (os.getenv('CONDA_PREFIX') # conda >= 4.1 |
281 |
| - or os.getenv('CONDA_DEFAULT_ENV')) # conda < 4.1 |
282 |
| - if conda_env_path and os.path.isdir(conda_env_path): |
283 |
| - ext.include_dirs.append(os.fspath( |
284 |
| - pathlib.Path(conda_env_path, "Library/include"))) |
285 |
| - ext.library_dirs.append(os.fspath( |
286 |
| - pathlib.Path(conda_env_path, "Library/lib"))) |
| 248 | + pkg_config = get_pkg_config() |
| 249 | + cmd = [pkg_config, package] if pkg_config else alt_exec |
| 250 | + if cmd is not None: |
| 251 | + try: |
| 252 | + if pkg_config and atleast_version: |
| 253 | + subprocess.check_call( |
| 254 | + [*cmd, f"--atleast-version={atleast_version}"]) |
| 255 | + # Use sys.getfilesystemencoding() to allow round-tripping |
| 256 | + # when passed back to later subprocess calls; do not use |
| 257 | + # locale.getpreferredencoding() which universal_newlines=True |
| 258 | + # would do. |
| 259 | + cflags = shlex.split( |
| 260 | + os.fsdecode(subprocess.check_output([*cmd, "--cflags"]))) |
| 261 | + libs = shlex.split( |
| 262 | + os.fsdecode(subprocess.check_output([*cmd, "--libs"]))) |
| 263 | + except (OSError, subprocess.CalledProcessError): |
| 264 | + pass |
| 265 | + else: |
| 266 | + ext.extra_compile_args.extend(cflags) |
| 267 | + ext.extra_link_args.extend(libs) |
| 268 | + return |
287 | 269 |
|
288 |
| - # Default linked libs. |
289 |
| - ext.libraries.extend(default_libraries) |
| 270 | + # If that fails, fall back on the defaults. |
290 | 271 |
|
| 272 | + # conda Windows header and library paths. |
| 273 | + # https://github.com/conda/conda/issues/2312 re: getting the env dir. |
| 274 | + if sys.platform == 'win32': |
| 275 | + conda_env_path = (os.getenv('CONDA_PREFIX') # conda >= 4.1 |
| 276 | + or os.getenv('CONDA_DEFAULT_ENV')) # conda < 4.1 |
| 277 | + if conda_env_path and os.path.isdir(conda_env_path): |
| 278 | + ext.include_dirs.append(os.fspath( |
| 279 | + pathlib.Path(conda_env_path, "Library/include"))) |
| 280 | + ext.library_dirs.append(os.fspath( |
| 281 | + pathlib.Path(conda_env_path, "Library/lib"))) |
291 | 282 |
|
292 |
| -# The PkgConfig class should be used through this singleton |
293 |
| -pkg_config = PkgConfig() |
| 283 | + # Default linked libs. |
| 284 | + ext.libraries.extend(default_libraries) |
294 | 285 |
|
295 | 286 |
|
296 | 287 | class CheckFailed(Exception):
|
@@ -679,7 +670,7 @@ def add_flags(self, ext):
|
679 | 670 | 0, os.path.join(src_path, 'objs', '.libs', libfreetype))
|
680 | 671 | ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local'))
|
681 | 672 | else:
|
682 |
| - pkg_config.setup_extension( |
| 673 | + pkg_config_setup_extension( |
683 | 674 | # FreeType 2.3 has libtool version 9.11.3 as can be checked
|
684 | 675 | # from the tarball. For FreeType>=2.4, there is a conversion
|
685 | 676 | # table in docs/VERSIONS.txt in the FreeType source tree.
|
@@ -826,7 +817,7 @@ def get_extension(self):
|
826 | 817 | 'src/mplutils.cpp',
|
827 | 818 | ]
|
828 | 819 | ext = Extension('matplotlib._png', sources)
|
829 |
| - pkg_config.setup_extension( |
| 820 | + pkg_config_setup_extension( |
830 | 821 | ext, 'libpng',
|
831 | 822 | atleast_version='1.2',
|
832 | 823 | alt_exec=['libpng-config', '--ldflags'],
|
|
0 commit comments