| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2013-2018 The Meson development team | |
| # Copyright © 2024-2025 Intel Corporation | |
| # This file contains the detection logic for external dependencies. | |
| # Custom logic for several other packages are in separate files. | |
| from __future__ import annotations | |
| import copy | |
| import dataclasses | |
| import os | |
| import collections | |
| import itertools | |
| import typing as T | |
| import uuid | |
| from enum import Enum | |
| from .. import mlog, mesonlib | |
| from ..compilers import clib_langs | |
| from ..mesonlib import LibType, MachineChoice, MesonException, HoldableObject, version_compare_many | |
| from ..options import OptionKey | |
| #from ..interpreterbase import FeatureDeprecated, FeatureNew | |
| if T.TYPE_CHECKING: | |
| from typing_extensions import Literal, Required, Self, TypedDict, TypeAlias | |
| from ..compilers.compilers import Language, Compiler | |
| from ..environment import Environment | |
| from ..interpreterbase import FeatureCheckBase | |
| from ..build import ( | |
| CustomTarget, IncludeDirs, CustomTargetIndex, LinkableTargetTypes, | |
| StaticLibrary, ExtractedObjects, TargetSources | |
| ) | |
| from ..interpreter.type_checking import PkgConfigDefineType | |
| IncludeType: TypeAlias = Literal['system', 'non-system', 'preserve'] | |
| class DependencyObjectKWs(TypedDict, total=False): | |
| """Keyword arguments that the Dependency IR object accepts. | |
| This is different than the arguments as taken by the Interpreter, since | |
| it is expected to be clean. | |
| """ | |
| cmake_args: T.List[str] | |
| cmake_module_path: T.List[str] | |
| cmake_package_version: str | |
| components: T.List[str] | |
| include_type: IncludeType | |
| language: T.Optional[Language] | |
| main: bool | |
| method: DependencyMethods | |
| modules: T.List[str] | |
| native: Required[MachineChoice] | |
| optional_modules: T.List[str] | |
| private_headers: bool | |
| required: bool | |
| static: T.Optional[bool] | |
| version: T.List[str] | |
| # Only in the python dependency | |
| embed: bool | |
| # Only passed internally, not part of the DSL API | |
| paths: T.List[str] | |
| returncode_value: int | |
| silent: bool | |
| tools: T.List[str] | |
| version_arg: str | |
| build_config: str | |
| _MissingCompilerBase = Compiler | |
| else: | |
| _MissingCompilerBase = object | |
| DepType = T.TypeVar('DepType', bound='ExternalDependency', covariant=True) | |
| class DependencyException(MesonException): | |
| '''Exceptions raised while trying to find dependencies''' | |
| class MissingCompiler(_MissingCompilerBase): | |
| """Represent a None Compiler - when no tool chain is found. | |
| replacing AttributeError with DependencyException""" | |
| # These are needed in type checking mode to avoid errors, but we don't want | |
| # the extra overhead at runtime | |
| if T.TYPE_CHECKING: | |
| def __init__(self) -> None: | |
| pass | |
| def get_optimization_args(self, optimization_level: str) -> T.List[str]: | |
| return [] | |
| def get_output_args(self, outputname: str) -> T.List[str]: | |
| return [] | |
| def _sanity_check_source_code(self) -> str: | |
| return '' | |
| def __getattr__(self, item: str) -> T.Any: | |
| if item.startswith('__'): | |
| raise AttributeError() | |
| raise DependencyException('no toolchain found') | |
| def __bool__(self) -> bool: | |
| return False | |
| class DependencyMethods(Enum): | |
| # Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. | |
| AUTO = 'auto' | |
| PKGCONFIG = 'pkg-config' | |
| CMAKE = 'cmake' | |
| # The dependency is provided by the standard library and does not need to be linked | |
| BUILTIN = 'builtin' | |
| # Just specify the standard link arguments, assuming the operating system provides the library. | |
| SYSTEM = 'system' | |
| # This is only supported on OSX - search the frameworks directory by name. | |
| EXTRAFRAMEWORK = 'extraframework' | |
| # Detect using the sysconfig module. | |
| SYSCONFIG = 'sysconfig' | |
| # Specify using a "program"-config style tool | |
| CONFIG_TOOL = 'config-tool' | |
| # Misc | |
| DUB = 'dub' | |
| DependencyTypeName = T.NewType('DependencyTypeName', str) | |
| class Dependency(HoldableObject): | |
| type_name: DependencyTypeName | |
| def __init__(self, kwargs: DependencyObjectKWs) -> None: | |
| # This allows two Dependencies to be compared even after being copied. | |
| # The purpose is to allow the name to be changed, but still have a proper comparison | |
| self._id = uuid.uuid4().int | |
| self.name = f'dep{self._id}' | |
| self.version: T.Optional[str] = None | |
| self.language: T.Optional[Language] = kwargs.get('language') # None means C-like | |
| self.is_found = False | |
| self.compile_args: T.List[str] = [] | |
| self.link_args: T.List[str] = [] | |
| # Raw -L and -l arguments without manual library searching | |
| # If None, self.link_args will be used | |
| self.raw_link_args: T.Optional[T.List[str]] = None | |
| self.sources: T.List[TargetSources] = [] | |
| self.extra_files: T.List[mesonlib.File] = [] | |
| self.include_type = kwargs.get('include_type', 'preserve') | |
| self.ext_deps: T.List[Dependency] = [] | |
| self.d_features: T.DefaultDict[str, T.List[T.Any]] = collections.defaultdict(list) | |
| self.featurechecks: T.List['FeatureCheckBase'] = [] | |
| self.feature_since: T.Optional[T.Tuple[str, str]] = None | |
| self.meson_variables: T.List[str] = [] | |
| def __eq__(self, other: object) -> bool: | |
| if not isinstance(other, Dependency): | |
| return NotImplemented | |
| return self._id == other._id | |
| def __hash__(self) -> int: | |
| return self._id | |
| def __repr__(self) -> str: | |
| return f'<{self.__class__.__name__} {self.name}: {self.is_found}>' | |
| def is_built(self) -> bool: | |
| return False | |
| def is_named(self) -> bool: | |
| if self.name is None: | |
| return False | |
| return self.name != f'dep{self._id}' | |
| def summary_value(self) -> T.Union[str, mlog.AnsiDecorator, mlog.AnsiText]: | |
| if not self.found(): | |
| return mlog.red('NO') | |
| if not self.version: | |
| return mlog.green('YES') | |
| return mlog.AnsiText(mlog.green('YES'), ' ', mlog.cyan(self.version)) | |
| def get_compile_args(self) -> T.List[str]: | |
| if self.include_type == 'system': | |
| converted = [] | |
| for i in self.compile_args: | |
| if i.startswith('-I') or i.startswith('/I'): | |
| converted += ['-isystem' + i[2:]] | |
| else: | |
| converted += [i] | |
| return converted | |
| if self.include_type == 'non-system': | |
| converted = [] | |
| for i in self.compile_args: | |
| if i.startswith('-isystem'): | |
| converted += ['-I' + i[8:]] | |
| else: | |
| converted += [i] | |
| return converted | |
| return self.compile_args | |
| def get_all_compile_args(self) -> T.List[str]: | |
| """Get the compile arguments from this dependency and its sub dependencies.""" | |
| return list(itertools.chain(self.get_compile_args(), | |
| *(d.get_all_compile_args() for d in self.ext_deps))) | |
| def get_link_args(self, language: T.Optional[Language] = None, raw: bool = False) -> T.List[str]: | |
| if raw and self.raw_link_args is not None: | |
| return self.raw_link_args | |
| return self.link_args | |
| def get_all_link_args(self) -> T.List[str]: | |
| """Get the link arguments from this dependency and its sub dependencies.""" | |
| return list(itertools.chain(self.get_link_args(), | |
| *(d.get_all_link_args() for d in self.ext_deps))) | |
| def found(self) -> bool: | |
| return self.is_found | |
| def get_sources(self) -> T.List[TargetSources]: | |
| """Source files that need to be added to the target. | |
| As an example, gtest-all.cc when using GTest.""" | |
| return self.sources | |
| def get_extra_files(self) -> T.List[mesonlib.File]: | |
| """Mostly for introspection and IDEs""" | |
| return self.extra_files | |
| def get_name(self) -> str: | |
| return self.name | |
| def get_version(self) -> str: | |
| if self.version: | |
| return self.version | |
| else: | |
| return 'unknown' | |
| def get_include_dirs(self) -> T.List['IncludeDirs']: | |
| return [] | |
| def get_include_type(self) -> str: | |
| return self.include_type | |
| def get_exe_args(self, compiler: 'Compiler') -> T.List[str]: | |
| return [] | |
| def get_partial_dependency(self, *, compile_args: bool = False, | |
| link_args: bool = False, links: bool = False, | |
| includes: bool = False, sources: bool = False) -> 'Dependency': | |
| """Create a new dependency that contains part of the parent dependency. | |
| The following options can be inherited: | |
| links -- all link_with arguments | |
| includes -- all include_directory and -I/-isystem calls | |
| sources -- any source, header, or generated sources | |
| compile_args -- any compile args | |
| link_args -- any link args | |
| Additionally the new dependency will have the version parameter of its | |
| parent (if any) and the requested values of any dependencies will be | |
| added as well. | |
| """ | |
| raise RuntimeError('Unreachable code in partial_dependency called') | |
| def _add_sub_dependency(self, deplist: T.Iterable[T.Callable[[], 'Dependency']]) -> bool: | |
| """Add an internal dependency from a list of possible dependencies. | |
| This method is intended to make it easier to add additional | |
| dependencies to another dependency internally. | |
| Returns true if the dependency was successfully added, false | |
| otherwise. | |
| """ | |
| for d in deplist: | |
| dep = d() | |
| if dep.is_found: | |
| self.ext_deps.append(dep) | |
| return True | |
| return False | |
| 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 default_value is not None: | |
| return default_value | |
| raise DependencyException(f'No default provided for dependency {self!r}, which is not pkg-config, cmake, or config-tool based.') | |
| def generate_system_dependency(self, include_type: IncludeType) -> 'Dependency': | |
| new_dep = copy.deepcopy(self) | |
| new_dep.include_type = include_type | |
| return new_dep | |
| def get_as_static(self, recursive: bool) -> Dependency: | |
| """Used as base case for internal_dependency""" | |
| return self | |
| def get_as_shared(self, recursive: bool) -> Dependency: | |
| """Used as base case for internal_dependency""" | |
| return self | |
| class InternalDependency(Dependency): | |
| type_name = DependencyTypeName('internal') | |
| def __init__(self, version: str, incdirs: T.Optional[T.List['IncludeDirs']] = None, | |
| compile_args: T.Optional[T.List[str]] = None, | |
| link_args: T.Optional[T.List[str]] = None, | |
| libraries: T.Optional[T.List[LinkableTargetTypes]] = None, | |
| whole_libraries: T.Optional[T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]]] = None, | |
| sources: T.Optional[T.Sequence[TargetSources]] = None, | |
| extra_files: T.Optional[T.Sequence[mesonlib.File]] = None, | |
| ext_deps: T.Optional[T.List[Dependency]] = None, variables: T.Optional[T.Dict[str, str]] = None, | |
| d_module_versions: T.Optional[T.List[T.Union[str, int]]] = None, | |
| d_import_dirs: T.Optional[T.List['IncludeDirs']] = None, | |
| objects: T.Optional[T.List['ExtractedObjects']] = None, | |
| name: T.Optional[str] = None): | |
| super().__init__({'native': MachineChoice.HOST}) # TODO: does the native key actually matter | |
| self.version = version | |
| self.is_found = True | |
| self.include_directories = incdirs or [] | |
| self.compile_args = compile_args or [] | |
| self.link_args = link_args or [] | |
| self.libraries = libraries or [] | |
| self.whole_libraries = whole_libraries or [] | |
| self.sources = list(sources or []) | |
| self.extra_files = list(extra_files or []) | |
| self.ext_deps = ext_deps or [] | |
| self.variables = variables or {} | |
| self.objects = objects or [] | |
| if d_module_versions: | |
| self.d_features['versions'] = d_module_versions | |
| if d_import_dirs: | |
| self.d_features['import_dirs'] = d_import_dirs | |
| if name: | |
| self.name = name | |
| def __deepcopy__(self, memo: T.Dict[int, 'InternalDependency']) -> 'InternalDependency': | |
| result = self.__class__.__new__(self.__class__) | |
| assert isinstance(result, InternalDependency) | |
| memo[id(self)] = result | |
| for k, v in self.__dict__.items(): | |
| if k in {'libraries', 'whole_libraries'}: | |
| setattr(result, k, copy.copy(v)) | |
| else: | |
| setattr(result, k, copy.deepcopy(v, memo)) | |
| return result | |
| def summary_value(self) -> mlog.AnsiDecorator: | |
| # Omit the version. Most of the time it will be just the project | |
| # version, which is uninteresting in the summary. | |
| return mlog.green('YES') | |
| def is_built(self) -> bool: | |
| if self.sources or self.libraries or self.whole_libraries: | |
| return True | |
| return any(d.is_built() for d in self.ext_deps) | |
| def get_partial_dependency(self, *, compile_args: bool = False, | |
| link_args: bool = False, links: bool = False, | |
| includes: bool = False, sources: bool = False, | |
| extra_files: bool = False) -> Self: | |
| final_compile_args = self.compile_args.copy() if compile_args else [] | |
| final_link_args = self.link_args.copy() if link_args else [] | |
| final_libraries = self.libraries.copy() if links else [] | |
| final_whole_libraries = self.whole_libraries.copy() if links else [] | |
| final_sources = self.sources.copy() if sources else [] | |
| final_extra_files = self.extra_files.copy() if extra_files else [] | |
| final_includes = self.include_directories.copy() if includes else [] | |
| final_deps = [d.get_partial_dependency( | |
| compile_args=compile_args, link_args=link_args, links=links, | |
| includes=includes, sources=sources) for d in self.ext_deps] | |
| return type(self)( | |
| self.version, final_includes, final_compile_args, | |
| final_link_args, final_libraries, final_whole_libraries, | |
| final_sources, final_extra_files, final_deps, self.variables, [], [], [], self.name) | |
| def get_include_dirs(self) -> T.List['IncludeDirs']: | |
| return self.include_directories | |
| 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: | |
| val = self.variables.get(internal, default_value) | |
| if val is not None: | |
| return val | |
| raise DependencyException(f'Could not get an internal variable and no default provided for {self!r}') | |
| def generate_link_whole_dependency(self) -> Dependency: | |
| from ..build import SharedLibrary, CustomTarget, CustomTargetIndex | |
| new_dep = copy.deepcopy(self) | |
| for x in new_dep.libraries: | |
| if isinstance(x, SharedLibrary): | |
| raise MesonException('Cannot convert a dependency to link_whole when it contains a ' | |
| 'SharedLibrary') | |
| elif isinstance(x, (CustomTarget, CustomTargetIndex)) and x.links_dynamically(): | |
| raise MesonException('Cannot convert a dependency to link_whole when it contains a ' | |
| 'CustomTarget or CustomTargetIndex which is a shared library') | |
| # Mypy doesn't understand that the above is a TypeGuard | |
| new_dep.whole_libraries += T.cast('T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]]', | |
| new_dep.libraries) | |
| new_dep.libraries = [] | |
| return new_dep | |
| def get_as_static(self, recursive: bool) -> InternalDependency: | |
| new_dep = copy.copy(self) | |
| new_dep.libraries = [lib.get('static', recursive) for lib in self.libraries] | |
| if recursive: | |
| new_dep.ext_deps = [dep.get_as_static(True) for dep in self.ext_deps] | |
| return new_dep | |
| def get_as_shared(self, recursive: bool) -> InternalDependency: | |
| new_dep = copy.copy(self) | |
| new_dep.libraries = [lib.get('shared', recursive) for lib in self.libraries] | |
| if recursive: | |
| new_dep.ext_deps = [dep.get_as_shared(True) for dep in self.ext_deps] | |
| return new_dep | |
| class ExternalDependency(Dependency): | |
| def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): | |
| Dependency.__init__(self, kwargs) | |
| self.env = environment | |
| self.name = name | |
| self.is_found = False | |
| self.version_reqs = kwargs.get('version', []) | |
| self.required = kwargs.get('required', True) | |
| self.silent = kwargs.get('silent', False) | |
| static = kwargs.get('static') | |
| if static is None: | |
| static = T.cast('bool', self.env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) | |
| self.static = static | |
| self.libtype = LibType.STATIC if self.static else LibType.PREFER_SHARED | |
| # Is this dependency to be run on the build platform? | |
| self.for_machine = kwargs['native'] | |
| self.clib_compiler = detect_compiler(self.name, environment, self.for_machine, self.language) | |
| def get_compiler(self) -> T.Union['MissingCompiler', 'Compiler']: | |
| return self.clib_compiler | |
| def get_partial_dependency(self, *, compile_args: bool = False, | |
| link_args: bool = False, links: bool = False, | |
| includes: bool = False, sources: bool = False) -> Dependency: | |
| new = copy.copy(self) | |
| new._id = uuid.uuid4().int | |
| if not compile_args: | |
| new.compile_args = [] | |
| if not link_args: | |
| new.link_args = [] | |
| if not sources: | |
| new.sources = [] | |
| if not includes: | |
| pass # TODO maybe filter compile_args? | |
| if not sources: | |
| new.sources = [] | |
| return new | |
| def log_details(self) -> str: | |
| return '' | |
| def log_info(self) -> str: | |
| return '' | |
| # Check if dependency version meets the requirements | |
| def _check_version(self) -> None: | |
| if not self.is_found: | |
| return | |
| if self.version_reqs: | |
| for_msg = ['for', mlog.bold(self.for_machine.get_lower_case_name()), 'machine'] | |
| # an unknown version can never satisfy any requirement | |
| if not self.version: | |
| self.is_found = False | |
| found_msg: mlog.TV_LoggableList = [] | |
| found_msg.extend(['Dependency', mlog.bold(self.name)]) | |
| found_msg.extend(for_msg) | |
| found_msg.append('found:') | |
| found_msg.extend([str(mlog.red('NO')) + '.', 'Unknown version, but need:', self.version_reqs]) | |
| mlog.log(*found_msg) | |
| if self.required: | |
| m = f'Unknown version, but need {self.version_reqs!r}.' | |
| raise DependencyException(m) | |
| else: | |
| (self.is_found, not_found, found) = \ | |
| version_compare_many(self.version, self.version_reqs) | |
| if not self.is_found: | |
| found_msg = ['Dependency', mlog.bold(self.name)] | |
| found_msg.extend(for_msg) | |
| found_msg.append('found:') | |
| found_msg += [str(mlog.red('NO')) + '.', | |
| 'Found', mlog.normal_cyan(self.version), 'but need:', | |
| mlog.bold(', '.join([f"'{e}'" for e in not_found]))] | |
| if found: | |
| found_msg += ['; matched:', | |
| ', '.join([f"'{e}'" for e in found])] | |
| mlog.log(*found_msg) | |
| if self.required: | |
| m = 'Invalid version, need {!r} {!r} found {!r}.' | |
| raise DependencyException(m.format(self.name, not_found, self.version)) | |
| return | |
| class NotFoundDependency(Dependency): | |
| type_name = DependencyTypeName('not-found') | |
| def __init__(self, name: str, environment: 'Environment') -> None: | |
| super().__init__({'native': MachineChoice.HOST}) # TODO: does this actually matter? | |
| self.env = environment | |
| self.name = name | |
| self.is_found = False | |
| def get_partial_dependency(self, *, compile_args: bool = False, | |
| link_args: bool = False, links: bool = False, | |
| includes: bool = False, sources: bool = False) -> 'NotFoundDependency': | |
| new = copy.copy(self) | |
| new._id = uuid.uuid4().int | |
| return new | |
| class ExternalLibrary(ExternalDependency): | |
| type_name = DependencyTypeName('library') | |
| def __init__(self, name: str, link_args: T.List[str], environment: 'Environment', | |
| language: Language, for_machine: MachineChoice, silent: bool = False) -> None: | |
| super().__init__(name, environment, {'language': language, 'native': for_machine}) | |
| self.is_found = False | |
| if link_args: | |
| self.is_found = True | |
| self.link_args = link_args | |
| if not silent: | |
| if self.is_found: | |
| mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES')) | |
| else: | |
| mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO')) | |
| def get_link_args(self, language: T.Optional[Language] = None, raw: bool = False) -> T.List[str]: | |
| ''' | |
| External libraries detected using a compiler must only be used with | |
| compatible code. For instance, Vala libraries (.vapi files) cannot be | |
| used with C code, and not all Rust library types can be linked with | |
| C-like code. Note that C++ libraries *can* be linked with C code with | |
| a C++ linker (and vice-versa). | |
| ''' | |
| # Using a vala library in a non-vala target, or a non-vala library in a vala target | |
| # XXX: This should be extended to other non-C linkers such as Rust | |
| if (self.language == 'vala' and language != 'vala') or \ | |
| (language == 'vala' and self.language != 'vala'): | |
| return [] | |
| return super().get_link_args(language=language, raw=raw) | |
| def get_partial_dependency(self, *, compile_args: bool = False, | |
| link_args: bool = False, links: bool = False, | |
| includes: bool = False, sources: bool = False) -> 'ExternalLibrary': | |
| # External library only has link_args, so ignore the rest of the | |
| # interface. | |
| new = copy.copy(self) | |
| new._id = uuid.uuid4().int | |
| if not link_args: | |
| new.link_args = [] | |
| return new | |
| def get_leaf_external_dependencies(deps: T.List[Dependency]) -> T.List[Dependency]: | |
| if not deps: | |
| # Ensure that we always return a new instance | |
| return deps.copy() | |
| final_deps = [] | |
| while deps: | |
| next_deps = [] | |
| for d in mesonlib.listify(deps): | |
| if not isinstance(d, Dependency) or d.is_built(): | |
| raise DependencyException('Dependencies must be external dependencies') | |
| final_deps.append(d) | |
| next_deps.extend(d.ext_deps) | |
| deps = next_deps | |
| return final_deps | |
| def sort_libpaths(libpaths: T.List[str], refpaths: T.List[str]) -> T.List[str]: | |
| """Sort <libpaths> according to <refpaths> | |
| It is intended to be used to sort -L flags returned by pkg-config. | |
| Pkg-config returns flags in random order which cannot be relied on. | |
| """ | |
| if len(refpaths) == 0: | |
| return list(libpaths) | |
| def key_func(libpath: str) -> T.Tuple[int, int]: | |
| common_lengths: T.List[int] = [] | |
| for refpath in refpaths: | |
| try: | |
| common_path: str = os.path.commonpath([libpath, refpath]) | |
| except ValueError: | |
| common_path = '' | |
| common_lengths.append(len(common_path)) | |
| max_length = max(common_lengths) | |
| max_index = common_lengths.index(max_length) | |
| reversed_max_length = len(refpaths[max_index]) - max_length | |
| return (max_index, reversed_max_length) | |
| return sorted(libpaths, key=key_func) | |
| def strip_system_libdirs(environment: 'Environment', for_machine: MachineChoice, link_args: T.List[str]) -> T.List[str]: | |
| """Remove -L<system path> arguments. | |
| leaving these in will break builds where a user has a version of a library | |
| in the system path, and a different version not in the system path if they | |
| want to link against the non-system path version. | |
| """ | |
| exclude = {f'-L{p}' for p in environment.get_compiler_system_lib_dirs(for_machine)} | |
| return [l for l in link_args if l not in exclude] | |
| def strip_system_includedirs(environment: 'Environment', for_machine: MachineChoice, include_args: T.List[str]) -> T.List[str]: | |
| """Remove -I<system path> arguments. | |
| leaving these in will break builds where user want dependencies with system | |
| include-type used in rust.bindgen targets as if will cause system headers | |
| to not be found. | |
| """ | |
| exclude = {f'-I{p}' for p in environment.get_compiler_system_include_dirs(for_machine)} | |
| return [i for i in include_args if i not in exclude] | |
| def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs: DependencyObjectKWs) -> T.List[DependencyMethods]: | |
| method = kwargs.get('method', DependencyMethods.AUTO) | |
| # Set the detection method. If the method is set to auto, use any available method. | |
| # If method is set to a specific string, allow only that detection method. | |
| if method == DependencyMethods.AUTO: | |
| # annotated for https://github.com/python/mypy/issues/19894 | |
| methods: T.List[DependencyMethods] = list(possible) | |
| elif method in possible: | |
| methods = [method] | |
| else: | |
| raise DependencyException( | |
| 'Unsupported detection method: {}, allowed methods are {}'.format( | |
| method.value, | |
| mlog.format_list([x.value for x in [DependencyMethods.AUTO] + list(possible)]))) | |
| return methods | |
| def detect_compiler(name: str, env: 'Environment', for_machine: MachineChoice, | |
| language: T.Optional[Language]) -> T.Union['MissingCompiler', 'Compiler']: | |
| """Given a language and environment find the compiler used.""" | |
| compilers = env.coredata.compilers[for_machine] | |
| # Set the compiler for this dependency if a language is specified, | |
| # else try to pick something that looks usable. | |
| if language: | |
| if language not in compilers: | |
| m = name.capitalize() + ' requires a {0} compiler, but ' \ | |
| '{0} is not in the list of project languages' | |
| raise DependencyException(m.format(language.capitalize())) | |
| return compilers[language] | |
| else: | |
| # https://github.com/python/mypy/issues/18826 | |
| # However, we need to support versions of mypy that cannot deduce the | |
| # tuple. | |
| for lang in T.cast('T.Tuple[Language, ...]', clib_langs): | |
| try: | |
| return compilers[lang] | |
| except KeyError: | |
| continue | |
| return MissingCompiler() | |
| class SystemDependency(ExternalDependency): | |
| """Dependency base for System type dependencies.""" | |
| type_name = DependencyTypeName('system') | |
| class BuiltinDependency(ExternalDependency): | |
| """Dependency base for Builtin type dependencies.""" | |
| type_name = DependencyTypeName('builtin') | |
| @dataclasses.dataclass | |
| class DependencyCandidate(T.Generic[DepType]): | |
| callable: T.Union[T.Type[DepType], T.Callable[[str, Environment, DependencyObjectKWs], DepType]] | |
| name: str | |
| method: str | |
| modules: T.Optional[T.List[str]] = None | |
| arguments: T.Optional[T.Tuple[Environment, DependencyObjectKWs]] = dataclasses.field(default=None) | |
| def __call__(self) -> DepType: | |
| if self.arguments is None: | |
| raise mesonlib.MesonBugException('Attempted to instantiate a candidate before setting its arguments') | |
| env, kwargs = self.arguments | |
| if self.modules is not None: | |
| kwargs['modules'] = self.modules.copy() | |
| return self.callable(self.name, env, kwargs) | |
| @classmethod | |
| def from_dependency(cls, name: str, dep: T.Type[DepType], | |
| args: T.Optional[T.Tuple[Environment, DependencyObjectKWs]] = None, | |
| modules: T.Optional[T.List[str]] = None, | |
| ) -> DependencyCandidate[DepType]: | |
| tried = str(dep.type_name) | |
| # fixup the cases where type_name and log tried don't match | |
| if tried in {'extraframeworks', 'appleframeworks'}: | |
| tried = 'framework' | |
| elif tried == 'pkgconfig': | |
| tried = 'pkg-config' | |
| return cls(dep, name, tried, modules, arguments=args) |