summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNirbheek Chauhan <nirbheek@centricular.com>2024-09-26 14:50:52 +0530
committerNirbheek Chauhan <nirbheek.chauhan@gmail.com>2024-10-04 05:38:40 +0530
commit0f914b75fe39d924ff93d9651eba79c32c597365 (patch)
tree392e98289e1304c46beab5bf10ea808fd78434eb
parent3c2d04d702a765a9510ede80f517f5274dde5933 (diff)
downloadmeson-0f914b75fe39d924ff93d9651eba79c32c597365.tar.gz
programs: Allow excluding certain paths when searching in PATH
-rw-r--r--mesonbuild/dependencies/configtool.py10
-rw-r--r--mesonbuild/dependencies/ui.py2
-rw-r--r--mesonbuild/interpreter/interpreter.py18
-rw-r--r--mesonbuild/programs.py62
-rw-r--r--unittests/failuretests.py4
5 files changed, 54 insertions, 42 deletions
diff --git a/mesonbuild/dependencies/configtool.py b/mesonbuild/dependencies/configtool.py
index ef106a8b0..476f7ad42 100644
--- a/mesonbuild/dependencies/configtool.py
+++ b/mesonbuild/dependencies/configtool.py
@@ -37,7 +37,7 @@ class ConfigToolDependency(ExternalDependency):
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):
+ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None, exclude_paths: T.Optional[T.List[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
@@ -52,7 +52,7 @@ class ConfigToolDependency(ExternalDependency):
req_version = mesonlib.stringlistify(req_version_raw)
else:
req_version = []
- tool, version = self.find_config(req_version, kwargs.get('returncode_value', 0))
+ 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:
@@ -84,15 +84,17 @@ class ConfigToolDependency(ExternalDependency):
version = self._sanitize_version(out.strip())
return valid, version
- def find_config(self, versions: T.List[str], returncode: int = 0) \
+ 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, allow_default_for_cross=self.allow_default_for_cross):
+ 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()
diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py
index cc17377a6..d88af7945 100644
--- a/mesonbuild/dependencies/ui.py
+++ b/mesonbuild/dependencies/ui.py
@@ -68,7 +68,7 @@ class GnuStepDependency(ConfigToolDependency):
['--gui-libs' if 'gui' in self.modules else '--base-libs'],
'link_args'))
- def find_config(self, versions: T.Optional[T.List[str]] = None, returncode: int = 0) -> T.Tuple[T.Optional[T.List[str]], T.Optional[str]]:
+ def find_config(self, versions: T.Optional[T.List[str]] = None, returncode: int = 0, exclude_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.Optional[T.List[str]], T.Optional[str]]:
tool = [self.tools[0]]
try:
p, out = Popen_safe(tool + ['--help'])[:2]
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index eceb40a6b..aa839da36 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -816,7 +816,7 @@ class Interpreter(InterpreterBase, HoldableObject):
cmd = cmd.absolute_path(srcdir, builddir)
# Prefer scripts in the current source directory
search_dir = os.path.join(srcdir, self.subdir)
- prog = ExternalProgram(cmd, silent=True, search_dir=search_dir)
+ prog = ExternalProgram(cmd, silent=True, search_dirs=[search_dir])
if not prog.found():
raise InterpreterException(f'Program or command {cmd!r} not found or not executable')
cmd = prog
@@ -1586,7 +1586,7 @@ class Interpreter(InterpreterBase, HoldableObject):
return prog
return None
- def program_from_system(self, args: T.List[mesonlib.FileOrString], search_dirs: T.List[str],
+ def program_from_system(self, args: T.List[mesonlib.FileOrString], search_dirs: T.Optional[T.List[str]],
extra_info: T.List[mlog.TV_Loggable]) -> T.Optional[ExternalProgram]:
# Search for scripts relative to current subdir.
# Do not cache found programs because find_program('foobar')
@@ -1601,15 +1601,15 @@ class Interpreter(InterpreterBase, HoldableObject):
search_dir = os.path.join(self.environment.get_source_dir(),
exename.subdir)
exename = exename.fname
- extra_search_dirs = []
+ search_dirs = [search_dir]
elif isinstance(exename, str):
- search_dir = source_dir
- extra_search_dirs = search_dirs
+ if search_dirs:
+ search_dirs = [source_dir] + search_dirs
+ else:
+ search_dirs = [source_dir]
else:
raise InvalidArguments(f'find_program only accepts strings and files, not {exename!r}')
- extprog = ExternalProgram(exename, search_dir=search_dir,
- extra_search_dirs=extra_search_dirs,
- silent=True)
+ extprog = ExternalProgram(exename, search_dirs=search_dirs, silent=True)
if extprog.found():
extra_info.append(f"({' '.join(extprog.get_command())})")
return extprog
@@ -1681,7 +1681,7 @@ class Interpreter(InterpreterBase, HoldableObject):
def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice,
default_options: T.Optional[T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]],
required: bool,
- search_dirs: T.List[str],
+ search_dirs: T.Optional[T.List[str]],
wanted: T.Union[str, T.List[str]],
version_arg: T.Optional[str],
version_func: T.Optional[ProgramVersionFunc],
diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py
index bbe8ea421..9ad38e126 100644
--- a/mesonbuild/programs.py
+++ b/mesonbuild/programs.py
@@ -25,14 +25,20 @@ if T.TYPE_CHECKING:
class ExternalProgram(mesonlib.HoldableObject):
- """A program that is found on the system."""
+ """A program that is found on the system.
+ :param name: The name of the program
+ :param command: Optionally, an argument list constituting the command. Used when
+ you already know the command and do not want to search.
+ :param silent: Whether to print messages when initializing
+ :param search_dirs: A list of directories to search in first, followed by PATH
+ :param exclude_paths: A list of directories to exclude when searching in PATH"""
windows_exts = ('exe', 'msc', 'com', 'bat', 'cmd')
for_machine = MachineChoice.BUILD
def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
- silent: bool = False, search_dir: T.Optional[str] = None,
- extra_search_dirs: T.Optional[T.List[str]] = None):
+ silent: bool = False, search_dirs: T.Optional[T.List[T.Optional[str]]] = None,
+ exclude_paths: T.Optional[T.List[str]] = None):
self.name = name
self.path: T.Optional[str] = None
self.cached_version: T.Optional[str] = None
@@ -51,13 +57,10 @@ class ExternalProgram(mesonlib.HoldableObject):
else:
self.command = [cmd] + args
else:
- all_search_dirs = [search_dir]
- if extra_search_dirs:
- all_search_dirs += extra_search_dirs
- for d in all_search_dirs:
- self.command = self._search(name, d)
- if self.found():
- break
+ if search_dirs is None:
+ # For compat with old behaviour
+ search_dirs = [None]
+ self.command = self._search(name, search_dirs, exclude_paths)
if self.found():
# Set path to be the last item that is actually a file (in order to
@@ -242,7 +245,7 @@ class ExternalProgram(mesonlib.HoldableObject):
return [trial_ext]
return None
- def _search_windows_special_cases(self, name: str, command: str) -> T.List[T.Optional[str]]:
+ def _search_windows_special_cases(self, name: str, command: T.Optional[str], exclude_paths: T.Optional[T.List[str]]) -> T.List[T.Optional[str]]:
'''
Lots of weird Windows quirks:
1. PATH search for @name returns files with extensions from PATHEXT,
@@ -278,31 +281,37 @@ class ExternalProgram(mesonlib.HoldableObject):
# On Windows, interpreted scripts must have an extension otherwise they
# cannot be found by a standard PATH search. So we do a custom search
# where we manually search for a script with a shebang in PATH.
- search_dirs = self._windows_sanitize_path(os.environ.get('PATH', '')).split(';')
+ search_dirs = OrderedSet(self._windows_sanitize_path(os.environ.get('PATH', '')).split(';'))
+ if exclude_paths:
+ search_dirs.difference_update(exclude_paths)
for search_dir in search_dirs:
commands = self._search_dir(name, search_dir)
if commands:
return commands
return [None]
- def _search(self, name: str, search_dir: T.Optional[str]) -> T.List[T.Optional[str]]:
+ def _search(self, name: str, search_dirs: T.List[T.Optional[str]], exclude_paths: T.Optional[T.List[str]]) -> T.List[T.Optional[str]]:
'''
- Search in the specified dir for the specified executable by name
+ Search in the specified dirs for the specified executable by name
and if not found search in PATH
'''
- commands = self._search_dir(name, search_dir)
- if commands:
- return commands
+ for search_dir in search_dirs:
+ commands = self._search_dir(name, search_dir)
+ if commands:
+ return commands
# If there is a directory component, do not look in PATH
if os.path.dirname(name) and not os.path.isabs(name):
return [None]
# Do a standard search in PATH
- path = os.environ.get('PATH', None)
+ path = os.environ.get('PATH', os.defpath)
if mesonlib.is_windows() and path:
path = self._windows_sanitize_path(path)
+ if exclude_paths:
+ paths = OrderedSet(path.split(os.pathsep)).difference(exclude_paths)
+ path = os.pathsep.join(paths)
command = shutil.which(name, path=path)
if mesonlib.is_windows():
- return self._search_windows_special_cases(name, command)
+ return self._search_windows_special_cases(name, command, exclude_paths)
# On UNIX-like platforms, shutil.which() is enough to find
# all executables whether in PATH or with an absolute path
return [command]
@@ -341,15 +350,16 @@ class OverrideProgram(ExternalProgram):
"""A script overriding a program."""
def __init__(self, name: str, version: str, command: T.Optional[T.List[str]] = None,
- silent: bool = False, search_dir: T.Optional[str] = None,
- extra_search_dirs: T.Optional[T.List[str]] = None):
+ silent: bool = False, search_dirs: T.Optional[T.List[T.Optional[str]]] = None,
+ exclude_paths: T.Optional[T.List[str]] = None):
self.cached_version = version
super().__init__(name, command=command, silent=silent,
- search_dir=search_dir, extra_search_dirs=extra_search_dirs)
+ search_dirs=search_dirs, exclude_paths=exclude_paths)
def find_external_program(env: 'Environment', for_machine: MachineChoice, name: str,
display_name: str, default_names: T.List[str],
- allow_default_for_cross: bool = True) -> T.Generator['ExternalProgram', None, None]:
+ allow_default_for_cross: bool = True,
+ exclude_paths: T.Optional[T.List[str]] = None) -> T.Generator['ExternalProgram', None, None]:
"""Find an external program, checking the cross file plus any default options."""
potential_names = OrderedSet(default_names)
potential_names.add(name)
@@ -367,8 +377,8 @@ def find_external_program(env: 'Environment', for_machine: MachineChoice, name:
# Fallback on hard-coded defaults, if a default binary is allowed for use
# with cross targets, or if this is not a cross target
if allow_default_for_cross or not (for_machine is MachineChoice.HOST and env.is_cross_build(for_machine)):
- for potential_path in default_names:
- mlog.debug(f'Trying a default {display_name} fallback at', potential_path)
- yield ExternalProgram(potential_path, silent=True)
+ for potential_name in default_names:
+ mlog.debug(f'Trying a default {display_name} fallback at', potential_name)
+ yield ExternalProgram(potential_name, silent=True, exclude_paths=exclude_paths)
else:
mlog.debug('Default target is not allowed for cross use')
diff --git a/unittests/failuretests.py b/unittests/failuretests.py
index baa592047..8a802120b 100644
--- a/unittests/failuretests.py
+++ b/unittests/failuretests.py
@@ -34,10 +34,10 @@ def no_pkgconfig():
old_which = shutil.which
old_search = ExternalProgram._search
- def new_search(self, name, search_dir):
+ def new_search(self, name, search_dirs, exclude_paths):
if name == 'pkg-config':
return [None]
- return old_search(self, name, search_dir)
+ return old_search(self, name, search_dirs, exclude_paths)
def new_which(cmd, *kwargs):
if cmd == 'pkg-config':