diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index de55d3e66..64150e88b 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -14,7 +14,7 @@ from .base import ExternalDependency, DependencyException, DependencyMethods, sort_libpaths from ..mesonlib import LibType, MachineChoice, OptionKey, OrderedSet, PerMachine, Popen_safe -from ..programs import find_external_program +from ..programs import find_external_program, ExternalProgram from .. import mlog from pathlib import PurePath import re @@ -28,17 +28,20 @@ if T.TYPE_CHECKING: class PkgConfigDependency(ExternalDependency): # The class's copy of the pkg-config path. Avoids having to search for it # multiple times in the same Meson invocation. - class_pkgbin = PerMachine(None, None) + class_pkgbin: PerMachine[T.Union[None, bool, ExternalProgram]] = PerMachine(None, None) # We cache all pkg-config subprocess invocations to avoid redundant calls - pkgbin_cache = {} + pkgbin_cache: T.Dict[ + T.Tuple[ExternalProgram, T.Tuple[str, ...], T.FrozenSet[T.Tuple[str, str]]], + T.Tuple[int, str, str] + ] = {} - def __init__(self, name, environment: 'Environment', kwargs, language: T.Optional[str] = None): + def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: super().__init__('pkgconfig', environment, kwargs, language=language) self.name = name self.is_libtool = False # Store a copy of the pkg-config path on the object itself so it is # stored in the pickled coredata and recovered. - self.pkgbin = None + self.pkgbin: T.Union[None, bool, ExternalProgram] = None # Only search for pkg-config for each machine the first time and store # the result in the class definition @@ -77,6 +80,7 @@ class PkgConfigDependency(ExternalDependency): mlog.debug(msg) return + assert isinstance(self.pkgbin, ExternalProgram) mlog.debug('Determining dependency {!r} with pkg-config executable ' '{!r}'.format(name, self.pkgbin.get_path())) ret, self.version, _ = self._call_pkgbin(['--modversion', name]) @@ -100,12 +104,13 @@ class PkgConfigDependency(ExternalDependency): self.is_found = False self.reason = e - def __repr__(self): + def __repr__(self) -> str: s = '<{0} {1}: {2} {3}>' return s.format(self.__class__.__name__, self.name, self.is_found, self.version_reqs) - def _call_pkgbin_real(self, args, env): + def _call_pkgbin_real(self, args: T.List[str], env: T.Dict[str, str]) -> T.Tuple[int, str, str]: + assert isinstance(self.pkgbin, ExternalProgram) cmd = self.pkgbin.get_command() + args p, out, err = Popen_safe(cmd, env=env) rc, out, err = p.returncode, out.strip(), err.strip() @@ -134,7 +139,7 @@ class PkgConfigDependency(ExternalDependency): if key.startswith('PKG_'): mlog.debug(f'env[{key}]: {value}') - def _call_pkgbin(self, args, env=None): + def _call_pkgbin(self, args: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, str, str]: # Always copy the environment since we're going to modify it # with pkg-config variables if env is None: @@ -142,6 +147,7 @@ class PkgConfigDependency(ExternalDependency): else: env = env.copy() + assert isinstance(self.pkgbin, ExternalProgram) PkgConfigDependency.setup_env(env, self.env, self.for_machine) fenv = frozenset(env.items()) @@ -162,7 +168,7 @@ class PkgConfigDependency(ExternalDependency): return args converted = [] for arg in args: - pargs = [] + pargs: T.Tuple[str, ...] = tuple() # Library search path if arg.startswith('-L/'): pargs = PurePath(arg[2:]).parts @@ -182,12 +188,12 @@ class PkgConfigDependency(ExternalDependency): converted.append(arg) return converted - def _split_args(self, cmd): + def _split_args(self, 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 _set_cargs(self): + def _set_cargs(self) -> None: env = None if self.language == 'fortran': # gfortran doesn't appear to look in system paths for INCLUDE files, @@ -200,7 +206,7 @@ class PkgConfigDependency(ExternalDependency): (self.name, err)) self.compile_args = self._convert_mingw_paths(self._split_args(out)) - def _search_libs(self, out, out_raw): + def _search_libs(self, out: str, out_raw: str) -> T.Tuple[T.List[str], T.List[str]]: ''' @out: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs @out_raw: pkg-config --libs @@ -225,7 +231,7 @@ class PkgConfigDependency(ExternalDependency): # # Separate system and prefix paths, and ensure that prefix paths are # always searched first. - prefix_libpaths = OrderedSet() + prefix_libpaths: OrderedSet[str] = OrderedSet() # We also store this raw_link_args on the object later raw_link_args = self._convert_mingw_paths(self._split_args(out_raw)) for arg in raw_link_args: @@ -246,8 +252,8 @@ class PkgConfigDependency(ExternalDependency): # too many system_libpaths to cause library version issues. pkg_config_path: T.List[str] = self.env.coredata.options[OptionKey('pkg_config_path', machine=self.for_machine)].value pkg_config_path = self._convert_mingw_paths(pkg_config_path) - prefix_libpaths = sort_libpaths(prefix_libpaths, pkg_config_path) - system_libpaths = OrderedSet() + prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) + system_libpaths: OrderedSet[str] = OrderedSet() full_args = self._convert_mingw_paths(self._split_args(out)) for arg in full_args: if arg.startswith(('-L-l', '-L-L')): @@ -258,7 +264,7 @@ class PkgConfigDependency(ExternalDependency): # 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() + libs_found: OrderedSet[str] = OrderedSet() # Track not-found libraries to know whether to add library paths libs_notfound = [] libtype = LibType.STATIC if self.static else LibType.PREFER_SHARED @@ -328,7 +334,7 @@ class PkgConfigDependency(ExternalDependency): link_args = ['-L' + lp for lp in prefix_libpaths] + link_args return link_args, raw_link_args - def _set_libs(self): + def _set_libs(self) -> None: env = None libcmd = ['--libs'] @@ -354,7 +360,7 @@ class PkgConfigDependency(ExternalDependency): (self.name, out_raw)) self.link_args, self.raw_link_args = self._search_libs(out, out_raw) - def get_pkgconfig_variable(self, variable_name: str, kwargs: T.Dict[str, T.Any]) -> str: + def get_pkgconfig_variable(self, variable_name: str, kwargs: T.Dict[str, T.Union[str, T.List[str]]]) -> str: options = ['--variable=' + variable_name, self.name] if 'define_variable' in kwargs: @@ -382,6 +388,7 @@ class PkgConfigDependency(ExternalDependency): ret, out, _ = self._call_pkgbin(['--print-variables', self.name]) if not re.search(r'^' + variable_name + r'$', out, re.MULTILINE): if 'default' in kwargs: + assert isinstance(kwargs['default'], str) variable = kwargs['default'] else: mlog.warning(f"pkgconfig variable '{variable_name}' not defined for dependency {self.name}.") @@ -390,10 +397,10 @@ class PkgConfigDependency(ExternalDependency): return variable @staticmethod - def get_methods(): + def get_methods() -> T.List[DependencyMethods]: return [DependencyMethods.PKGCONFIG] - def check_pkgconfig(self, pkgbin): + 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 @@ -415,7 +422,7 @@ class PkgConfigDependency(ExternalDependency): return None return out.strip() - def extract_field(self, la_file, fieldname): + def extract_field(self, la_file: str, fieldname: str) -> T.Optional[str]: with open(la_file) as f: for line in f: arr = line.strip().split('=') @@ -423,13 +430,13 @@ class PkgConfigDependency(ExternalDependency): return arr[1][1:-1] return None - def extract_dlname_field(self, la_file): + 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): + 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): + def extract_libtool_shlib(self, la_file: str) -> T.Optional[str]: ''' Returns the path to the shared library corresponding to this .la file @@ -450,7 +457,7 @@ class PkgConfigDependency(ExternalDependency): # a path rather than the raw dlname return os.path.basename(dlname) - def log_tried(self): + def log_tried(self) -> str: return self.type_name def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, @@ -458,7 +465,7 @@ class PkgConfigDependency(ExternalDependency): default_value: T.Optional[str] = None, pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: if pkgconfig: - kwargs = {} + kwargs: T.Dict[str, T.Union[str, T.List[str]]] = {} if default_value is not None: kwargs['default'] = default_value if pkgconfig_define is not None: diff --git a/run_mypy.py b/run_mypy.py index f1525bece..bbf4d76a0 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -25,6 +25,7 @@ modules = [ 'mesonbuild/dependencies/boost.py', 'mesonbuild/dependencies/hdf5.py', 'mesonbuild/dependencies/mpi.py', + 'mesonbuild/dependencies/pkgconfig.py', 'mesonbuild/dependencies/qt.py', 'mesonbuild/envconfig.py', 'mesonbuild/interpreterbase.py',