aboutsummaryrefslogtreecommitdiffstats
path: root/meson/mesonbuild/dependencies/configtool.py
diff options
context:
space:
mode:
Diffstat (limited to 'meson/mesonbuild/dependencies/configtool.py')
-rw-r--r--meson/mesonbuild/dependencies/configtool.py178
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}')