diff options
| author | Nirbheek Chauhan <nirbheek@centricular.com> | 2024-09-26 14:50:52 +0530 |
|---|---|---|
| committer | Nirbheek Chauhan <nirbheek.chauhan@gmail.com> | 2024-10-04 05:38:40 +0530 |
| commit | 0f914b75fe39d924ff93d9651eba79c32c597365 (patch) | |
| tree | 392e98289e1304c46beab5bf10ea808fd78434eb /mesonbuild/programs.py | |
| parent | 3c2d04d702a765a9510ede80f517f5274dde5933 (diff) | |
| download | meson-0f914b75fe39d924ff93d9651eba79c32c597365.tar.gz | |
programs: Allow excluding certain paths when searching in PATH
Diffstat (limited to 'mesonbuild/programs.py')
| -rw-r--r-- | mesonbuild/programs.py | 62 |
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') |
