diff options
Diffstat (limited to 'meson/mesonbuild/dependencies/configtool.py')
-rw-r--r-- | meson/mesonbuild/dependencies/configtool.py | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/meson/mesonbuild/dependencies/configtool.py b/meson/mesonbuild/dependencies/configtool.py new file mode 100644 index 000000000..623affb2c --- /dev/null +++ b/meson/mesonbuild/dependencies/configtool.py @@ -0,0 +1,178 @@ +# Copyright 2013-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName +from ..mesonlib import listify, Popen_safe, 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 + +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 + :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' + __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=False): + 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: + 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(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) + + @staticmethod + def get_methods() -> T.List[DependencyMethods]: + return [DependencyMethods.AUTO, DependencyMethods.CONFIG_TOOL] + + def get_configtool_variable(self, variable_name: str) -> str: + p, out, _ = Popen_safe(self.config + [f'--{variable_name}']) + if p.returncode != 0: + if self.required: + raise DependencyException( + 'Could not get variable "{}" for dependency {}'.format( + variable_name, self.name)) + variable = out.strip() + mlog.debug(f'Got config-tool variable {variable_name} : {variable}') + return variable + + def log_tried(self) -> str: + return self.type_name + + 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: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]: + if configtool: + # In the not required case '' (empty string) will be returned if the + # variable is not found. Since '' is a valid value to return we + # set required to True here to force and error, and use the + # finally clause to ensure it's restored. + restore = self.required + self.required = True + try: + return self.get_configtool_variable(configtool) + except DependencyException: + pass + finally: + self.required = restore + 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}') |