|
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): |
@@ -511,7 +502,7 @@ def add_flags(self, ext): |
511 | 502 | 0, os.path.join(src_path, 'objs', '.libs', libfreetype)) |
512 | 503 | ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local')) |
513 | 504 | else: |
514 | | - pkg_config.setup_extension( |
| 505 | + pkg_config_setup_extension( |
515 | 506 | # FreeType 2.3 has libtool version 9.11.3 as can be checked |
516 | 507 | # from the tarball. For FreeType>=2.4, there is a conversion |
517 | 508 | # table in docs/VERSIONS.txt in the FreeType source tree. |
@@ -650,7 +641,7 @@ def get_extension(self): |
650 | 641 | 'src/mplutils.cpp', |
651 | 642 | ] |
652 | 643 | ext = Extension('matplotlib._png', sources) |
653 | | - pkg_config.setup_extension( |
| 644 | + pkg_config_setup_extension( |
654 | 645 | ext, 'libpng', |
655 | 646 | atleast_version='1.2', |
656 | 647 | alt_exec=['libpng-config', '--ldflags'], |
|
0 commit comments