typing: Fully annotate dependencies.base

pull/8846/head
Daniel Mensinger 4 years ago committed by Dylan Baker
parent f502a38d1c
commit df4d2bd247
  1. 128
      mesonbuild/dependencies/base.py
  2. 1
      run_mypy.py

@ -15,30 +15,21 @@
# This file contains the detection logic for external dependencies.
# Custom logic for several other packages are in separate files.
import copy
import functools
import os
import re
import itertools
import json
import shlex
import shutil
import textwrap
import typing as T
from enum import Enum
from pathlib import Path, PurePath
from .. import mlog
from .. import mesonlib
from ..compilers import clib_langs
from ..environment import Environment, MachineInfo
from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine
from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list, split_args
from ..mesonlib import Version, LibType, OptionKey
from ..programs import ExternalProgram, find_external_program
from ..mesonlib import MachineChoice, MesonException
from ..mesonlib import version_compare_many
from ..interpreterbase import FeatureDeprecated
if T.TYPE_CHECKING:
from ..compilers.compilers import Compiler
from ..environment import Environment
from ..build import BuildTarget
DependencyType = T.TypeVar('DependencyType', bound='Dependency')
@ -72,7 +63,7 @@ class DependencyMethods(Enum):
class Dependency:
@classmethod
def _process_include_type_kw(cls, kwargs) -> str:
def _process_include_type_kw(cls, kwargs: T.Dict[str, T.Any]) -> str:
if 'include_type' not in kwargs:
return 'preserve'
if not isinstance(kwargs['include_type'], str):
@ -81,25 +72,24 @@ class Dependency:
raise DependencyException("include_type may only be one of ['preserve', 'system', 'non-system']")
return kwargs['include_type']
def __init__(self, type_name, kwargs):
def __init__(self, type_name: str, kwargs: T.Dict[str, T.Any]) -> None:
self.name = "null"
self.version = None # type: T.Optional[str]
self.language = None # None means C-like
self.version: T.Optional[str] = None
self.language: T.Optional[str] = None # None means C-like
self.is_found = False
self.type_name = type_name
self.compile_args = [] # type: T.List[str]
self.link_args = []
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 = None
self.sources = []
self.raw_link_args: T.Optional[T.List[str]] = None
self.sources: T.List[str] = []
self.methods = process_method_kw(self.get_methods(), kwargs)
self.include_type = self._process_include_type_kw(kwargs)
self.ext_deps = [] # type: T.List[Dependency]
self.ext_deps: T.List[Dependency] = []
def __repr__(self):
s = '<{0} {1}: {2}>'
return s.format(self.__class__.__name__, self.name, self.is_found)
def __repr__(self) -> str:
return f'<{self.__class__.__name__} {self.name}: {self.is_found}>'
def is_built(self) -> bool:
return False
@ -109,7 +99,7 @@ class Dependency:
return mlog.red('NO')
if not self.version:
return mlog.green('YES')
return mlog.AnsiText(mlog.green('YES'), ' ', mlog.cyan(self.version))
return mlog.AnsiText([mlog.green('YES'), ' ', mlog.cyan(self.version)])
def get_compile_args(self) -> T.List[str]:
if self.include_type == 'system':
@ -135,7 +125,7 @@ class Dependency:
return list(itertools.chain(self.get_compile_args(),
*[d.get_all_compile_args() for d in self.ext_deps]))
def get_link_args(self, raw: bool = False) -> T.List[str]:
def get_link_args(self, language: T.Optional[str] = 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
@ -148,16 +138,16 @@ class Dependency:
def found(self) -> bool:
return self.is_found
def get_sources(self):
def get_sources(self) -> T.List[str]:
"""Source files that need to be added to the target.
As an example, gtest-all.cc when using GTest."""
return self.sources
@staticmethod
def get_methods():
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO]
def get_name(self):
def get_name(self) -> str:
return self.name
def get_version(self) -> str:
@ -169,18 +159,18 @@ class Dependency:
def get_include_type(self) -> str:
return self.include_type
def get_exe_args(self, compiler):
def get_exe_args(self, compiler: 'Compiler') -> T.List[str]:
return []
def get_pkgconfig_variable(self, variable_name: str, kwargs: T.Dict[str, T.Any]) -> str:
raise DependencyException(f'{self.name!r} is not a pkgconfig dependency')
def get_configtool_variable(self, variable_name):
def get_configtool_variable(self, variable_name: str) -> str:
raise DependencyException(f'{self.name!r} is not a config-tool dependency')
def get_partial_dependency(self, *, compile_args: bool = False,
link_args: bool = False, links: bool = False,
includes: bool = False, sources: 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:
@ -220,14 +210,16 @@ class Dependency:
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: str) -> T.Type['Dependency']:
def generate_system_dependency(self, include_type: str) -> 'Dependency':
new_dep = copy.deepcopy(self)
new_dep.include_type = self._process_include_type_kw({'include_type': include_type})
return new_dep
class InternalDependency(Dependency):
def __init__(self, version, incdirs, compile_args, link_args, libraries,
whole_libraries, sources, ext_deps, variables: T.Dict[str, T.Any]):
def __init__(self, version: str, incdirs: T.List[str], compile_args: T.List[str],
link_args: T.List[str], libraries: T.List['BuildTarget'],
whole_libraries: T.List['BuildTarget'], sources: T.List[str],
ext_deps: T.List[Dependency], variables: T.Dict[str, T.Any]):
super().__init__('internal', {})
self.version = version
self.is_found = True
@ -240,8 +232,9 @@ class InternalDependency(Dependency):
self.ext_deps = ext_deps
self.variables = variables
def __deepcopy__(self, memo: dict) -> 'InternalDependency':
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']:
@ -264,13 +257,13 @@ class InternalDependency(Dependency):
raise DependencyException('Method "get_pkgconfig_variable()" is '
'invalid for an internal dependency')
def get_configtool_variable(self, variable_name):
def get_configtool_variable(self, variable_name: str) -> str:
raise DependencyException('Method "get_configtool_variable()" is '
'invalid for an internal dependency')
def get_partial_dependency(self, *, compile_args: bool = False,
link_args: bool = False, links: bool = False,
includes: bool = False, sources: bool = False):
includes: bool = False, sources: bool = False) -> 'InternalDependency':
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 []
@ -291,10 +284,16 @@ class InternalDependency(Dependency):
pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]:
val = self.variables.get(internal, default_value)
if val is not None:
return val
# TODO: Try removing this assert by better typing self.variables
if isinstance(val, str):
return val
if isinstance(val, list):
for i in val:
assert isinstance(i, str)
return val
raise DependencyException(f'Could not get an internal variable and no default provided for {self!r}')
def generate_link_whole_dependency(self) -> T.Type['Dependency']:
def generate_link_whole_dependency(self) -> Dependency:
new_dep = copy.deepcopy(self)
new_dep.whole_libraries += new_dep.libraries
new_dep.libraries = []
@ -308,7 +307,7 @@ class HasNativeKwarg:
return MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST
class ExternalDependency(Dependency, HasNativeKwarg):
def __init__(self, type_name, environment: Environment, kwargs, language: T.Optional[str] = None):
def __init__(self, type_name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None):
Dependency.__init__(self, type_name, kwargs)
self.env = environment
self.name = type_name # default
@ -326,12 +325,12 @@ class ExternalDependency(Dependency, HasNativeKwarg):
HasNativeKwarg.__init__(self, kwargs)
self.clib_compiler = detect_compiler(self.name, environment, self.for_machine, self.language)
def get_compiler(self):
def get_compiler(self) -> '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):
includes: bool = False, sources: bool = False) -> Dependency:
new = copy.copy(self)
if not compile_args:
new.compile_args = []
@ -340,32 +339,32 @@ class ExternalDependency(Dependency, HasNativeKwarg):
if not sources:
new.sources = []
if not includes:
new.include_directories = []
pass # TODO maybe filter compile_args?
if not sources:
new.sources = []
return new
def log_details(self):
def log_details(self) -> str:
return ''
def log_info(self):
def log_info(self) -> str:
return ''
def log_tried(self):
def log_tried(self) -> str:
return ''
# Check if dependency version meets the requirements
def _check_version(self):
def _check_version(self) -> None:
if not self.is_found:
return
if self.version_reqs:
# an unknown version can never satisfy any requirement
if not self.version:
found_msg = ['Dependency', mlog.bold(self.name), 'found:']
found_msg += [mlog.red('NO'), 'unknown version, but need:',
self.version_reqs]
found_msg: mlog.TV_LoggableList = []
found_msg += ['Dependency', mlog.bold(self.name), 'found:']
found_msg += [mlog.red('NO'), 'unknown version, but need:', self.version_reqs]
mlog.log(*found_msg)
if self.required:
@ -392,7 +391,7 @@ class ExternalDependency(Dependency, HasNativeKwarg):
class NotFoundDependency(Dependency):
def __init__(self, environment):
def __init__(self, environment: 'Environment') -> None:
super().__init__('not-found', {})
self.env = environment
self.name = 'not-found'
@ -400,12 +399,13 @@ class NotFoundDependency(Dependency):
def get_partial_dependency(self, *, compile_args: bool = False,
link_args: bool = False, links: bool = False,
includes: bool = False, sources: bool = False):
includes: bool = False, sources: bool = False) -> 'NotFoundDependency':
return copy.copy(self)
class ExternalLibrary(ExternalDependency):
def __init__(self, name, link_args, environment, language, silent=False):
def __init__(self, name: str, link_args: T.List[str], environment: 'Environment',
language: str, silent: bool = False) -> None:
super().__init__('library', environment, {}, language=language)
self.name = name
self.language = language
@ -419,7 +419,7 @@ class ExternalLibrary(ExternalDependency):
else:
mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
def get_link_args(self, language=None, **kwargs):
def get_link_args(self, language: T.Optional[str] = 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
@ -432,11 +432,11 @@ class ExternalLibrary(ExternalDependency):
if (self.language == 'vala' and language != 'vala') or \
(language == 'vala' and self.language != 'vala'):
return []
return super().get_link_args(**kwargs)
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):
includes: bool = False, sources: bool = False) -> 'ExternalLibrary':
# External library only has link_args, so ignore the rest of the
# interface.
new = copy.copy(self)
@ -454,11 +454,11 @@ def sort_libpaths(libpaths: T.List[str], refpaths: T.List[str]) -> T.List[str]:
if len(refpaths) == 0:
return list(libpaths)
def key_func(libpath):
common_lengths = []
def key_func(libpath: str) -> T.Tuple[int, int]:
common_lengths: T.List[int] = []
for refpath in refpaths:
try:
common_path = os.path.commonpath([libpath, refpath])
common_path: str = os.path.commonpath([libpath, refpath])
except ValueError:
common_path = ''
common_lengths.append(len(common_path))
@ -468,7 +468,7 @@ def sort_libpaths(libpaths: T.List[str], refpaths: T.List[str]) -> T.List[str]:
return (max_index, reversed_max_length)
return sorted(libpaths, key=key_func)
def strip_system_libdirs(environment, for_machine: MachineChoice, link_args):
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
@ -478,7 +478,7 @@ def strip_system_libdirs(environment, for_machine: MachineChoice, link_args):
exclude = {f'-L{p}' for p in environment.get_compiler_system_dirs(for_machine)}
return [l for l in link_args if l not in exclude]
def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs) -> T.List[DependencyMethods]:
def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs: T.Dict[str, T.Any]) -> T.List[DependencyMethods]:
method = kwargs.get('method', 'auto') # type: T.Union[DependencyMethods, str]
if isinstance(method, DependencyMethods):
return [method]
@ -511,7 +511,7 @@ def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs) -> T.List
return methods
def detect_compiler(name: str, env: Environment, for_machine: MachineChoice,
def detect_compiler(name: str, env: 'Environment', for_machine: MachineChoice,
language: T.Optional[str]) -> T.Optional['Compiler']:
"""Given a language and environment find the compiler used."""
compilers = env.coredata.compilers[for_machine]

@ -18,6 +18,7 @@ modules = [
# specific files
'mesonbuild/arglist.py',
# 'mesonbuild/coredata.py',
'mesonbuild/dependencies/base.py',
'mesonbuild/dependencies/boost.py',
'mesonbuild/dependencies/hdf5.py',
'mesonbuild/dependencies/mpi.py',

Loading…
Cancel
Save