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