summaryrefslogtreecommitdiff
path: root/mesonbuild/programs.py
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 /mesonbuild/programs.py
parent3c2d04d702a765a9510ede80f517f5274dde5933 (diff)
downloadmeson-0f914b75fe39d924ff93d9651eba79c32c597365.tar.gz
programs: Allow excluding certain paths when searching in PATH
Diffstat (limited to 'mesonbuild/programs.py')
-rw-r--r--mesonbuild/programs.py62
1 files changed, 36 insertions, 26 deletions
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')