| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2013-2021 The Meson development team |
| |
| from __future__ import annotations |
| |
| from .base import ExternalDependency, DependencyException, DependencyTypeName |
| from ..mesonlib import listify, Popen_safe, Popen_safe_logged, split_args, version_compare, version_compare_many |
| from ..programs import find_external_program |
| from .. import mlog |
| import re |
| import typing as T |
| |
| from mesonbuild import mesonlib |
| |
| if T.TYPE_CHECKING: |
| from ..environment import Environment |
| from ..interpreter.type_checking import PkgConfigDefineType |
| |
| class ConfigToolDependency(ExternalDependency): |
| |
| """Class representing dependencies found using a config tool. |
| |
| Takes the following extra keys in kwargs that it uses internally: |
| :tools List[str]: A list of tool names to use |
| :version_arg str: The argument to pass to the tool to get it's version |
| :skip_version str: The argument to pass to the tool to ignore its version |
| (if ``version_arg`` fails, but it may start accepting it in the future) |
| Because some tools are stupid and don't accept --version |
| :returncode_value int: The value of the correct returncode |
| Because some tools are stupid and don't return 0 |
| """ |
| |
| tools: T.Optional[T.List[str]] = None |
| tool_name: T.Optional[str] = None |
| version_arg = '--version' |
| skip_version: T.Optional[str] = None |
| allow_default_for_cross = False |
| __strip_version = re.compile(r'^[0-9][0-9.]+') |
| |
| def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): |
| super().__init__(DependencyTypeName('config-tool'), environment, kwargs, language=language) |
| self.name = name |
| # You may want to overwrite the class version in some cases |
| self.tools = listify(kwargs.get('tools', self.tools)) |
| if not self.tool_name: |
| self.tool_name = self.tools[0] |
| if 'version_arg' in kwargs: |
| self.version_arg = kwargs['version_arg'] |
| |
| req_version_raw = kwargs.get('version', None) |
| if req_version_raw is not None: |
| req_version = mesonlib.stringlistify(req_version_raw) |
| else: |
| req_version = [] |
| tool, version = self.find_config(req_version, kwargs.get('returncode_value', 0)) |
| self.config = tool |
| self.is_found = self.report_config(version, req_version) |
| if not self.is_found: |
| self.config = None |
| return |
| self.version = version |
| |
| def _sanitize_version(self, version: str) -> str: |
| """Remove any non-numeric, non-point version suffixes.""" |
| m = self.__strip_version.match(version) |
| if m: |
| # Ensure that there isn't a trailing '.', such as an input like |
| # `1.2.3.git-1234` |
| return m.group(0).rstrip('.') |
| return version |
| |
| def find_config(self, versions: T.List[str], returncode: int = 0) \ |
| -> T.Tuple[T.Optional[T.List[str]], T.Optional[str]]: |
| """Helper method that searches for config tool binaries in PATH and |
| returns the one that best matches the given version requirements. |
| """ |
| best_match: T.Tuple[T.Optional[T.List[str]], T.Optional[str]] = (None, None) |
| for potential_bin in find_external_program( |
| self.env, self.for_machine, self.tool_name, |
| self.tool_name, self.tools, allow_default_for_cross=self.allow_default_for_cross): |
| if not potential_bin.found(): |
| continue |
| tool = potential_bin.get_command() |
| try: |
| p, out = Popen_safe(tool + [self.version_arg])[:2] |
| except (FileNotFoundError, PermissionError): |
| continue |
| if p.returncode != returncode: |
| if self.skip_version: |
| # maybe the executable is valid even if it doesn't support --version |
| p = Popen_safe(tool + [self.skip_version])[0] |
| if p.returncode != returncode: |
| continue |
| else: |
| continue |
| |
| out = self._sanitize_version(out.strip()) |
| # Some tools, like pcap-config don't supply a version, but also |
| # don't fail with --version, in that case just assume that there is |
| # only one version and return it. |
| if not out: |
| return (tool, None) |
| if versions: |
| is_found = version_compare_many(out, versions)[0] |
| # This allows returning a found version without a config tool, |
| # which is useful to inform the user that you found version x, |
| # but y was required. |
| if not is_found: |
| tool = None |
| if best_match[1]: |
| if version_compare(out, '> {}'.format(best_match[1])): |
| best_match = (tool, out) |
| else: |
| best_match = (tool, out) |
| |
| return best_match |
| |
| def report_config(self, version: T.Optional[str], req_version: T.List[str]) -> bool: |
| """Helper method to print messages about the tool.""" |
| |
| found_msg: T.List[T.Union[str, mlog.AnsiDecorator]] = [mlog.bold(self.tool_name), 'found:'] |
| |
| if self.config is None: |
| found_msg.append(mlog.red('NO')) |
| if version is not None and req_version: |
| found_msg.append(f'found {version!r} but need {req_version!r}') |
| elif req_version: |
| found_msg.append(f'need {req_version!r}') |
| else: |
| found_msg += [mlog.green('YES'), '({})'.format(' '.join(self.config)), version] |
| |
| mlog.log(*found_msg) |
| |
| return self.config is not None |
| |
| def get_config_value(self, args: T.List[str], stage: str) -> T.List[str]: |
| p, out, err = Popen_safe_logged(self.config + args) |
| if p.returncode != 0: |
| if self.required: |
| raise DependencyException(f'Could not generate {stage} for {self.name}.\n{err}') |
| return [] |
| return split_args(out) |
| |
| def get_variable_args(self, variable_name: str) -> T.List[str]: |
| return [f'--{variable_name}'] |
| |
| @staticmethod |
| def log_tried() -> str: |
| return 'config-tool' |
| |
| def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, |
| configtool: T.Optional[str] = None, internal: T.Optional[str] = None, |
| default_value: T.Optional[str] = None, |
| pkgconfig_define: PkgConfigDefineType = None) -> str: |
| if configtool: |
| p, out, _ = Popen_safe(self.config + self.get_variable_args(configtool)) |
| if p.returncode == 0: |
| variable = out.strip() |
| mlog.debug(f'Got config-tool variable {configtool} : {variable}') |
| return variable |
| if default_value is not None: |
| return default_value |
| raise DependencyException(f'Could not get config-tool variable and no default provided for {self!r}') |