1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
# 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
if T.TYPE_CHECKING:
from ..environment import Environment
from ..interpreter.type_checking import PkgConfigDefineType
from .base import DependencyObjectKWs
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 its 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.]+')
type_name = DependencyTypeName('config-tool')
def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, exclude_paths: T.Optional[T.List[str]] = None):
super().__init__(name, environment, kwargs)
# 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 = kwargs.get('version', [])
tool, version = self.find_config(req_version, kwargs.get('returncode_value', 0), exclude_paths=exclude_paths)
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 _check_and_get_version(self, tool: T.List[str], returncode: int) -> T.Tuple[bool, T.Union[str, None]]:
"""Check whether a command is valid and get its version"""
p, out = Popen_safe(tool + [self.version_arg])[:2]
valid = True
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:
valid = False
else:
valid = False
version = self._sanitize_version(out.strip())
return valid, version
def find_config(self, versions: T.List[str], returncode: int = 0, exclude_paths: T.Optional[T.List[str]] = None) \
-> 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.
"""
exclude_paths = [] if exclude_paths is None else exclude_paths
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, exclude_paths=exclude_paths,
allow_default_for_cross=self.allow_default_for_cross):
if not potential_bin.found():
continue
tool = potential_bin.get_command()
try:
valid, version = self._check_and_get_version(tool, returncode)
except (FileNotFoundError, PermissionError):
continue
if not valid:
continue
# 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 version:
return (tool, None)
if versions:
is_found = version_compare_many(version, 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(version, '> {}'.format(best_match[1])):
best_match = (tool, version)
else:
best_match = (tool, version)
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}']
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
system: 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}')
|