|
|
|
@ -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') |
|
|
|
|