| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2013-2021 The Meson development team | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from .base import ExternalDependency, DependencyException, sort_libpaths, DependencyTypeName | |
| from ..mesonlib import (EnvironmentVariables, OrderedSet, PerMachine, Popen_safe, Popen_safe_logged, MachineChoice, | |
| join_args, MesonException, path_has_root) | |
| from ..options import OptionKey | |
| from ..programs import find_external_program, ExternalProgram | |
| from .. import mlog | |
| from pathlib import PurePath | |
| from functools import lru_cache | |
| import re | |
| import os | |
| import shlex | |
| import typing as T | |
| if T.TYPE_CHECKING: | |
| from typing_extensions import Literal | |
| from .._typing import ImmutableListProtocol | |
| from ..environment import Environment | |
| from ..utils.core import EnvironOrDict | |
| from ..interpreter.type_checking import PkgConfigDefineType | |
| from .base import DependencyObjectKWs | |
| class PkgConfigInterface: | |
| '''Base class wrapping a pkg-config implementation''' | |
| # keyed on machine and extra_paths | |
| class_impl: PerMachine[T.Dict[T.Optional[T.Tuple[str, ...]], T.Union[Literal[False], T.Optional[PkgConfigInterface]]]] = PerMachine({}, {}) | |
| class_cli_impl: PerMachine[T.Dict[T.Optional[T.Tuple[str, ...]], T.Union[Literal[False], T.Optional[PkgConfigCLI]]]] = PerMachine({}, {}) | |
| pkg_bin_per_machine: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None) | |
| @staticmethod | |
| def set_program_override(pkg_bin: ExternalProgram, for_machine: MachineChoice) -> None: | |
| if PkgConfigInterface.class_impl[for_machine]: | |
| raise MesonException(f'Tried to override pkg-config for machine {for_machine} but it was already initialized.\n' | |
| 'pkg-config must be overridden before it\'s used.') | |
| PkgConfigInterface.pkg_bin_per_machine[for_machine] = pkg_bin | |
| @staticmethod | |
| def instance(env: Environment, for_machine: MachineChoice, silent: bool, | |
| extra_paths: T.Optional[T.List[str]] = None) -> T.Optional[PkgConfigInterface]: | |
| '''Return a pkg-config implementation singleton''' | |
| extra_paths_key = tuple(extra_paths) if extra_paths is not None else None | |
| impl = PkgConfigInterface.class_impl[for_machine].get(extra_paths_key, False) | |
| if impl is False: | |
| impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine], extra_paths) | |
| if not impl.found(): | |
| impl = None | |
| if not impl and not silent: | |
| mlog.log('Found pkg-config:', mlog.red('NO')) | |
| PkgConfigInterface.class_impl[for_machine][extra_paths_key] = impl | |
| return impl | |
| @staticmethod | |
| def _cli(env: Environment, for_machine: MachineChoice, | |
| extra_paths: T.Optional[T.List[str]] = None, | |
| silent: bool = False) -> T.Optional[PkgConfigCLI]: | |
| '''Return the CLI pkg-config implementation singleton | |
| Even when we use another implementation internally, external tools might | |
| still need the CLI implementation. | |
| ''' | |
| impl: T.Union[Literal[False], T.Optional[PkgConfigInterface]] # Help confused mypy | |
| impl = PkgConfigInterface.instance(env, for_machine, silent) | |
| if impl and not isinstance(impl, PkgConfigCLI): | |
| extra_paths_key = tuple(extra_paths) if extra_paths is not None else None | |
| impl = PkgConfigInterface.class_cli_impl[for_machine].get(extra_paths_key, False) | |
| if impl is False: | |
| impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine], extra_paths) | |
| if not impl.found(): | |
| impl = None | |
| PkgConfigInterface.class_cli_impl[for_machine][extra_paths_key] = impl | |
| return T.cast('T.Optional[PkgConfigCLI]', impl) # Trust me, mypy | |
| @staticmethod | |
| def get_env(env: Environment, for_machine: MachineChoice, uninstalled: bool = False, | |
| extra_paths: T.Optional[T.List[str]] = None) -> EnvironmentVariables: | |
| cli = PkgConfigInterface._cli(env, for_machine, extra_paths) | |
| return cli._get_env(uninstalled) if cli else EnvironmentVariables() | |
| @staticmethod | |
| def setup_env(environ: EnvironOrDict, env: Environment, for_machine: MachineChoice, | |
| uninstalled: bool = False) -> EnvironOrDict: | |
| cli = PkgConfigInterface._cli(env, for_machine) | |
| return cli._setup_env(environ, uninstalled) if cli else environ | |
| def __init__(self, env: Environment, for_machine: MachineChoice) -> None: | |
| self.env = env | |
| self.for_machine = for_machine | |
| def found(self) -> bool: | |
| '''Return whether pkg-config is supported''' | |
| raise NotImplementedError | |
| def version(self, name: str) -> T.Optional[str]: | |
| '''Return module version or None if not found''' | |
| raise NotImplementedError | |
| def cflags(self, name: str, static: bool = False, allow_system: bool = False, | |
| define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]: | |
| '''Return module cflags | |
| @allow_system: If False, remove default system include paths | |
| ''' | |
| raise NotImplementedError | |
| def libs(self, name: str, static: bool = False, allow_system: bool = False, | |
| define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]: | |
| '''Return module libs | |
| @static: If True, also include private libraries | |
| @allow_system: If False, remove default system libraries search paths | |
| ''' | |
| raise NotImplementedError | |
| def variable(self, name: str, variable_name: str, | |
| define_variable: PkgConfigDefineType) -> T.Optional[str]: | |
| '''Return module variable or None if variable is not defined''' | |
| raise NotImplementedError | |
| def list_all(self) -> ImmutableListProtocol[str]: | |
| '''Return all available pkg-config modules''' | |
| raise NotImplementedError | |
| class PkgConfigCLI(PkgConfigInterface): | |
| '''pkg-config CLI implementation''' | |
| def __init__(self, env: Environment, for_machine: MachineChoice, silent: bool, | |
| pkgbin: T.Optional[ExternalProgram] = None, | |
| extra_paths: T.Optional[T.List[str]] = None) -> None: | |
| super().__init__(env, for_machine) | |
| self._detect_pkgbin(pkgbin) | |
| if self.pkgbin and not silent: | |
| mlog.log('Found pkg-config:', mlog.green('YES'), mlog.bold(f'({self.pkgbin.get_path()})'), mlog.blue(self.pkgbin_version)) | |
| self.extra_paths = extra_paths or [] | |
| def found(self) -> bool: | |
| return bool(self.pkgbin) | |
| @lru_cache(maxsize=None) | |
| def version(self, name: str) -> T.Optional[str]: | |
| mlog.debug(f'Determining dependency {name!r} with pkg-config executable {self.pkgbin.get_path()!r}') | |
| ret, version, _ = self._call_pkgbin(['--modversion', name]) | |
| return version if ret == 0 else None | |
| @staticmethod | |
| def _define_variable_args(define_variable: PkgConfigDefineType) -> T.List[str]: | |
| ret = [] | |
| if define_variable: | |
| for pair in define_variable: | |
| ret.append('--define-variable=' + '='.join(pair)) | |
| return ret | |
| @lru_cache(maxsize=None) | |
| def cflags(self, name: str, static: bool = False, allow_system: bool = False, | |
| define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]: | |
| env = None | |
| if allow_system: | |
| env = os.environ.copy() | |
| env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1' | |
| args: T.List[str] = [] | |
| args += self._define_variable_args(define_variable) | |
| if static: | |
| args.append('--static') | |
| args += ['--cflags', name] | |
| ret, out, err = self._call_pkgbin(args, env=env) | |
| if ret != 0: | |
| raise DependencyException(f'Could not generate cflags for {name}:\n{err}\n') | |
| return self._split_args(out) | |
| @lru_cache(maxsize=None) | |
| def libs(self, name: str, static: bool = False, allow_system: bool = False, | |
| define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]: | |
| env = None | |
| if allow_system: | |
| env = os.environ.copy() | |
| env['PKG_CONFIG_ALLOW_SYSTEM_LIBS'] = '1' | |
| args: T.List[str] = [] | |
| args += self._define_variable_args(define_variable) | |
| if static: | |
| args.append('--static') | |
| args += ['--libs', name] | |
| ret, out, err = self._call_pkgbin(args, env=env) | |
| if ret != 0: | |
| raise DependencyException(f'Could not generate libs for {name}:\n{err}\n') | |
| return self._split_args(out) | |
| @lru_cache(maxsize=None) | |
| def variable(self, name: str, variable_name: str, | |
| define_variable: PkgConfigDefineType) -> T.Optional[str]: | |
| args: T.List[str] = [] | |
| args += self._define_variable_args(define_variable) | |
| args += ['--variable=' + variable_name, name] | |
| ret, out, err = self._call_pkgbin(args) | |
| if ret != 0: | |
| raise DependencyException(f'Could not get variable for {name}:\n{err}\n') | |
| variable = out.strip() | |
| # pkg-config doesn't distinguish between empty and nonexistent variables | |
| # use the variable list to check for variable existence | |
| if not variable: | |
| ret, out, _ = self._call_pkgbin(['--print-variables', name]) | |
| if not re.search(rf'^{variable_name}$', out, re.MULTILINE): | |
| return None | |
| mlog.debug(f'Got pkg-config variable {variable_name} : {variable}') | |
| return variable | |
| @lru_cache(maxsize=None) | |
| def list_all(self) -> ImmutableListProtocol[str]: | |
| ret, out, err = self._call_pkgbin(['--list-all']) | |
| if ret != 0: | |
| raise DependencyException(f'could not list modules:\n{err}\n') | |
| return [i.split(' ', 1)[0] for i in out.splitlines()] | |
| @staticmethod | |
| def _split_args(cmd: str) -> T.List[str]: | |
| # pkg-config paths follow Unix conventions, even on Windows; split the | |
| # output using shlex.split rather than mesonlib.split_args | |
| return shlex.split(cmd) | |
| def _detect_pkgbin(self, pkgbin: T.Optional[ExternalProgram] = None) -> None: | |
| def validate(potential_pkgbin: ExternalProgram) -> bool: | |
| version_if_ok = self._check_pkgconfig(potential_pkgbin) | |
| if version_if_ok: | |
| self.pkgbin = potential_pkgbin | |
| self.pkgbin_version = version_if_ok | |
| return True | |
| return False | |
| if pkgbin and validate(pkgbin): | |
| return | |
| for potential_pkgbin in find_external_program(self.env, self.for_machine, "pkg-config", "Pkg-config", | |
| self.env.default_pkgconfig, allow_default_for_cross=False): | |
| if validate(potential_pkgbin): | |
| return | |
| self.pkgbin = None | |
| def _check_pkgconfig(self, pkgbin: ExternalProgram) -> T.Optional[str]: | |
| if not pkgbin.found(): | |
| mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}') | |
| return None | |
| command_as_string = ' '.join(pkgbin.get_command()) | |
| try: | |
| helptext = Popen_safe(pkgbin.get_command() + ['--help'])[1] | |
| if 'Pure-Perl' in helptext: | |
| mlog.log(f'Found pkg-config {command_as_string!r} but it is Strawberry Perl and thus broken. Ignoring...') | |
| return None | |
| p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2] | |
| if p.returncode != 0: | |
| mlog.warning(f'Found pkg-config {command_as_string!r} but it failed when ran') | |
| return None | |
| except FileNotFoundError: | |
| mlog.warning(f'We thought we found pkg-config {command_as_string!r} but now it\'s not there. How odd!') | |
| return None | |
| except PermissionError: | |
| msg = f'Found pkg-config {command_as_string!r} but didn\'t have permissions to run it.' | |
| if not self.env.machines.build.is_windows(): | |
| msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.' | |
| mlog.warning(msg) | |
| return None | |
| return out.strip() | |
| def _get_env(self, uninstalled: bool = False) -> EnvironmentVariables: | |
| env = EnvironmentVariables() | |
| key = OptionKey('pkg_config_path', machine=self.for_machine) | |
| pathlist = self.env.coredata.optstore.get_value_for(key) | |
| assert isinstance(pathlist, list) | |
| extra_paths: T.List[str] = pathlist + self.extra_paths | |
| if uninstalled: | |
| bpath = self.env.get_build_dir() | |
| if bpath is not None: | |
| # uninstalled can only be used if a build dir exists. | |
| uninstalled_path = Path(bpath, 'meson-uninstalled').as_posix() | |
| if uninstalled_path not in extra_paths: | |
| extra_paths.insert(0, uninstalled_path) | |
| env.set('PKG_CONFIG_PATH', extra_paths) | |
| sysroot = self.env.properties[self.for_machine].get_sys_root() | |
| if sysroot: | |
| env.set('PKG_CONFIG_SYSROOT_DIR', [sysroot]) | |
| pkg_config_libdir_prop = self.env.properties[self.for_machine].get_pkg_config_libdir() | |
| if pkg_config_libdir_prop: | |
| env.set('PKG_CONFIG_LIBDIR', pkg_config_libdir_prop) | |
| env.set('PKG_CONFIG', [join_args(self.pkgbin.get_command())]) | |
| return env | |
| def _setup_env(self, env: EnvironOrDict, uninstalled: bool = False) -> T.Dict[str, str]: | |
| envvars = self._get_env(uninstalled) | |
| env = envvars.get_env(env) | |
| # Dump all PKG_CONFIG environment variables | |
| for key, value in env.items(): | |
| if key.startswith('PKG_'): | |
| mlog.debug(f'env[{key}]: {value}') | |
| return env | |
| def _call_pkgbin(self, args: T.List[str], env: T.Optional[EnvironOrDict] = None) -> T.Tuple[int, str, str]: | |
| assert isinstance(self.pkgbin, ExternalProgram) | |
| env = env or os.environ | |
| env = self._setup_env(env) | |
| cmd = self.pkgbin.get_command() + args | |
| p, out, err = Popen_safe_logged(cmd, env=env) | |
| return p.returncode, out.strip(), err.strip() | |
| class PkgConfigDependency(ExternalDependency): | |
| type_name = DependencyTypeName('pkgconfig') | |
| def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs, | |
| extra_paths: T.Optional[T.List[str]] = None) -> None: | |
| super().__init__(name, environment, kwargs) | |
| self.is_libtool = False | |
| self.extra_paths = extra_paths or [] | |
| pkgconfig = PkgConfigInterface.instance(self.env, self.for_machine, self.silent, self.extra_paths) | |
| if not pkgconfig: | |
| msg = f'Pkg-config for machine {self.for_machine} not found. Giving up.' | |
| if self.required: | |
| raise DependencyException(msg) | |
| mlog.debug(msg) | |
| return | |
| self.pkgconfig = pkgconfig | |
| version = self.pkgconfig.version(name) | |
| if version is None: | |
| return | |
| self.version = version | |
| self.is_found = True | |
| try: | |
| # Fetch cargs to be used while using this dependency | |
| self._set_cargs() | |
| # Fetch the libraries and library paths needed for using this | |
| self._set_libs() | |
| except DependencyException as e: | |
| mlog.warning(f"Pkg-config error with '{name}': {e}") | |
| if self.required: | |
| raise | |
| else: | |
| self.compile_args = [] | |
| self.link_args = [] | |
| self.is_found = False | |
| self.reason = e | |
| def __repr__(self) -> str: | |
| s = '<{0} {1}: {2} {3}>' | |
| return s.format(self.__class__.__name__, self.name, self.is_found, | |
| self.version_reqs) | |
| def _convert_mingw_paths(self, args: ImmutableListProtocol[str]) -> T.List[str]: | |
| ''' | |
| Both MSVC and native Python on Windows cannot handle MinGW-esque /c/foo | |
| paths so convert them to C:/foo. We cannot resolve other paths starting | |
| with / like /home/foo so leave them as-is so that the user gets an | |
| error/warning from the compiler/linker. | |
| ''' | |
| if not self.env.machines.build.is_windows(): | |
| return args.copy() | |
| converted = [] | |
| for arg in args: | |
| pargs: T.Tuple[str, ...] = tuple() | |
| # Library search path | |
| if arg.startswith('-L/'): | |
| pargs = PurePath(arg[2:]).parts | |
| tmpl = '-L{}:/{}' | |
| elif arg.startswith('-I/'): | |
| pargs = PurePath(arg[2:]).parts | |
| tmpl = '-I{}:/{}' | |
| # Full path to library or .la file | |
| elif arg.startswith('/'): | |
| pargs = PurePath(arg).parts | |
| tmpl = '{}:/{}' | |
| elif arg.startswith(('-L', '-I')) or (len(arg) > 2 and arg[1] == ':'): | |
| # clean out improper '\\ ' as comes from some Windows pkg-config files | |
| arg = arg.replace('\\ ', ' ') | |
| if len(pargs) > 1 and len(pargs[1]) == 1: | |
| arg = tmpl.format(pargs[1], '/'.join(pargs[2:])) | |
| converted.append(arg) | |
| return converted | |
| def _set_cargs(self) -> None: | |
| allow_system = False | |
| if self.language == 'fortran': | |
| # gfortran doesn't appear to look in system paths for INCLUDE files, | |
| # so don't allow pkg-config to suppress -I flags for system paths | |
| allow_system = True | |
| cflags = self.pkgconfig.cflags(self.name, self.static, allow_system) | |
| self.compile_args = self._convert_mingw_paths(cflags) | |
| def _search_libs(self, libs_in: ImmutableListProtocol[str], raw_libs_in: ImmutableListProtocol[str]) -> T.Tuple[T.List[str], T.List[str]]: | |
| ''' | |
| @libs_in: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs | |
| @raw_libs_in: pkg-config --libs | |
| We always look for the file ourselves instead of depending on the | |
| compiler to find it with -lfoo or foo.lib (if possible) because: | |
| 1. We want to be able to select static or shared | |
| 2. We need the full path of the library to calculate RPATH values | |
| 3. De-dup of libraries is easier when we have absolute paths | |
| Libraries that are provided by the toolchain or are not found by | |
| find_library() will be added with -L -l pairs. | |
| ''' | |
| # Library paths should be safe to de-dup | |
| # | |
| # First, figure out what library paths to use. Originally, we were | |
| # doing this as part of the loop, but due to differences in the order | |
| # of -L values between pkg-config and pkgconf, we need to do that as | |
| # a separate step. See: | |
| # https://github.com/mesonbuild/meson/issues/3951 | |
| # https://github.com/mesonbuild/meson/issues/4023 | |
| # | |
| # Separate system and prefix paths, and ensure that prefix paths are | |
| # always searched first. | |
| prefix_libpaths: OrderedSet[str] = OrderedSet() | |
| # We also store this raw_link_args on the object later | |
| raw_link_args = self._convert_mingw_paths(raw_libs_in) | |
| for arg in raw_link_args: | |
| if arg.startswith('-L') and not arg.startswith(('-L-l', '-L-L')): | |
| path = arg[2:] | |
| if not path_has_root(path): | |
| # Resolve the path as a compiler in the build directory would | |
| path = os.path.join(self.env.get_build_dir(), path) | |
| prefix_libpaths.add(path) | |
| # Library paths are not always ordered in a meaningful way | |
| # | |
| # Instead of relying on pkg-config or pkgconf to provide -L flags in a | |
| # specific order, we reorder library paths ourselves, according to th | |
| # order specified in PKG_CONFIG_PATH. See: | |
| # https://github.com/mesonbuild/meson/issues/4271 | |
| # | |
| # Only prefix_libpaths are reordered here because there should not be | |
| # too many system_libpaths to cause library version issues. | |
| pkg_config_path: T.List[str] = self.env.coredata.optstore.get_value_for(OptionKey('pkg_config_path', machine=self.for_machine)) # type: ignore[assignment] | |
| pkg_config_path = self._convert_mingw_paths(pkg_config_path) | |
| prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) | |
| system_libpaths: OrderedSet[str] = OrderedSet() | |
| full_args = self._convert_mingw_paths(libs_in) | |
| for arg in full_args: | |
| if arg.startswith(('-L-l', '-L-L')): | |
| # These are D language arguments, not library paths | |
| continue | |
| if arg.startswith('-L') and arg[2:] not in prefix_libpaths: | |
| system_libpaths.add(arg[2:]) | |
| # Use this re-ordered path list for library resolution | |
| libpaths = list(prefix_libpaths) + list(system_libpaths) | |
| # Track -lfoo libraries to avoid duplicate work | |
| libs_found: OrderedSet[str] = OrderedSet() | |
| # Track not-found libraries to know whether to add library paths | |
| libs_notfound = [] | |
| # Generate link arguments for this library | |
| link_args = [] | |
| for lib in full_args: | |
| if lib.startswith(('-L-l', '-L-L')): | |
| # These are D language arguments, add them as-is | |
| pass | |
| elif lib.startswith('-L'): | |
| # We already handled library paths above | |
| continue | |
| elif lib.startswith('-l:'): | |
| # see: https://stackoverflow.com/questions/48532868/gcc-library-option-with-a-colon-llibevent-a | |
| # also : See the documentation of -lnamespec | --library=namespec in the linker manual | |
| # https://sourceware.org/binutils/docs-2.18/ld/Options.html | |
| # Don't resolve the same -l:libfoo.a argument again | |
| if lib in libs_found: | |
| continue | |
| libfilename = lib[3:] | |
| foundname = None | |
| for libdir in libpaths: | |
| target = os.path.join(libdir, libfilename) | |
| if os.path.exists(target): | |
| foundname = target | |
| break | |
| if foundname is None: | |
| if lib in libs_notfound: | |
| continue | |
| else: | |
| mlog.warning('Library {!r} not found for dependency {!r}, may ' | |
| 'not be successfully linked'.format(libfilename, self.name)) | |
| libs_notfound.append(lib) | |
| else: | |
| lib = foundname | |
| elif lib.startswith('-l'): | |
| # Don't resolve the same -lfoo argument again | |
| if lib in libs_found: | |
| continue | |
| if self.clib_compiler: | |
| # Libraries from pkg-config are trusted to be linkable, so | |
| # we skip the potentially expensive link check for | |
| # performance reasons. | |
| args = self.clib_compiler.find_library( | |
| lib[2:], libpaths, self.libtype, lib_prefix_warning=False, | |
| skip_link_check=True) | |
| # If the project only uses a non-clib language such as D, Rust, | |
| # C#, Python, etc, all we can do is limp along by adding the | |
| # arguments as-is and then adding the libpaths at the end. | |
| else: | |
| args = None | |
| if args is not None: | |
| libs_found.add(lib) | |
| # Replace -l arg with full path to library if available | |
| # else, library is either to be ignored, or is provided by | |
| # the compiler, can't be resolved, and should be used as-is | |
| if args: | |
| if not args[0].startswith('-l'): | |
| lib = args[0] | |
| else: | |
| continue | |
| else: | |
| # Library wasn't found, maybe we're looking in the wrong | |
| # places or the library will be provided with LDFLAGS or | |
| # LIBRARY_PATH from the environment (on macOS), and many | |
| # other edge cases that we can't account for. | |
| # | |
| # Add all -L paths and use it as -lfoo | |
| if lib in libs_notfound: | |
| continue | |
| if self.static: | |
| mlog.warning('Static library {!r} not found for dependency {!r}, may ' | |
| 'not be statically linked'.format(lib[2:], self.name)) | |
| libs_notfound.append(lib) | |
| elif lib.endswith(".la"): | |
| shared_libname = self.extract_libtool_shlib(lib) | |
| shared_lib = os.path.join(os.path.dirname(lib), shared_libname) | |
| if not os.path.exists(shared_lib): | |
| shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname) | |
| if not os.path.exists(shared_lib): | |
| raise DependencyException(f'Got a libtools specific "{lib}" dependencies' | |
| 'but we could not compute the actual shared' | |
| 'library path') | |
| self.is_libtool = True | |
| lib = shared_lib | |
| if lib in link_args: | |
| continue | |
| link_args.append(lib) | |
| # Add all -Lbar args if we have -lfoo args in link_args | |
| if libs_notfound: | |
| # Order of -L flags doesn't matter with ld, but it might with other | |
| # linkers such as MSVC, so prepend them. | |
| link_args = ['-L' + lp for lp in prefix_libpaths] + link_args | |
| return link_args, raw_link_args | |
| def _set_libs(self) -> None: | |
| # Force pkg-config to output -L fields even if they are system | |
| # paths so we can do manual searching with cc.find_library() later. | |
| libs = self.pkgconfig.libs(self.name, self.static, allow_system=True) | |
| # Also get the 'raw' output without -Lfoo system paths for adding -L | |
| # args with -lfoo when a library can't be found, and also in | |
| # gnome.generate_gir + gnome.gtkdoc which need -L -l arguments. | |
| raw_libs = self.pkgconfig.libs(self.name, self.static, allow_system=False) | |
| self.link_args, self.raw_link_args = self._search_libs(libs, raw_libs) | |
| def extract_field(self, la_file: str, fieldname: str) -> T.Optional[str]: | |
| with open(la_file, encoding='utf-8') as f: | |
| for line in f: | |
| arr = line.strip().split('=', 1) | |
| if arr[0] == fieldname: | |
| return arr[1][1:-1] | |
| return None | |
| def extract_dlname_field(self, la_file: str) -> T.Optional[str]: | |
| return self.extract_field(la_file, 'dlname') | |
| def extract_libdir_field(self, la_file: str) -> T.Optional[str]: | |
| return self.extract_field(la_file, 'libdir') | |
| def extract_libtool_shlib(self, la_file: str) -> T.Optional[str]: | |
| ''' | |
| Returns the path to the shared library | |
| corresponding to this .la file | |
| ''' | |
| dlname = self.extract_dlname_field(la_file) | |
| if dlname is None: | |
| return None | |
| # Darwin uses absolute paths where possible; since the libtool files never | |
| # contain absolute paths, use the libdir field | |
| if self.env.machines[self.for_machine].is_darwin(): | |
| dlbasename = os.path.basename(dlname) | |
| libdir = self.extract_libdir_field(la_file) | |
| if libdir is None: | |
| return dlbasename | |
| return os.path.join(libdir, dlbasename) | |
| # From the comments in extract_libtool(), older libtools had | |
| # a path rather than the raw dlname | |
| return os.path.basename(dlname) | |
| def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, | |
| configtool: T.Optional[str] = None, internal: T.Optional[str] = None, | |
| system: T.Optional[str] = None, default_value: T.Optional[str] = None, | |
| pkgconfig_define: PkgConfigDefineType = None) -> str: | |
| if pkgconfig: | |
| try: | |
| variable = self.pkgconfig.variable(self.name, pkgconfig, pkgconfig_define) | |
| if variable is not None: | |
| return variable | |
| except DependencyException: | |
| pass | |
| if default_value is not None: | |
| return default_value | |
| raise DependencyException(f'Could not get pkg-config variable and no default provided for {self!r}') |