split program related classes and functions out of dependencies

Dependencies is already a large and complicated package without adding
programs to the list. This also allows us to untangle a bit of spaghetti
that we have.
pull/8563/head
Dylan Baker 4 years ago committed by Xavier Claessens
parent f7b0238ed6
commit 40e3577a65
  1. 15
      mesonbuild/backend/backends.py
  2. 7
      mesonbuild/build.py
  3. 9
      mesonbuild/cmake/executor.py
  4. 2
      mesonbuild/cmake/interpreter.py
  5. 3
      mesonbuild/compilers/c.py
  6. 3
      mesonbuild/compilers/cpp.py
  7. 3
      mesonbuild/compilers/cuda.py
  8. 2
      mesonbuild/compilers/d.py
  9. 3
      mesonbuild/compilers/fortran.py
  10. 3
      mesonbuild/compilers/mixins/clike.py
  11. 2
      mesonbuild/compilers/objc.py
  12. 2
      mesonbuild/compilers/objcpp.py
  13. 2
      mesonbuild/compilers/rust.py
  14. 9
      mesonbuild/dependencies/__init__.py
  15. 324
      mesonbuild/dependencies/base.py
  16. 6
      mesonbuild/dependencies/ui.py
  17. 6
      mesonbuild/environment.py
  18. 34
      mesonbuild/interpreter.py
  19. 5
      mesonbuild/modules/cmake.py
  20. 6
      mesonbuild/modules/dlang.py
  21. 3
      mesonbuild/modules/gnome.py
  22. 3
      mesonbuild/modules/hotdoc.py
  23. 4
      mesonbuild/modules/python.py
  24. 7
      mesonbuild/modules/python3.py
  25. 3
      mesonbuild/modules/qt.py
  26. 2
      mesonbuild/modules/unstable_rust.py
  27. 2
      mesonbuild/modules/windows.py
  28. 2
      mesonbuild/mtest.py
  29. 351
      mesonbuild/programs.py
  30. 3
      run_unittests.py

@ -26,6 +26,7 @@ import hashlib
from .. import build
from .. import dependencies
from .. import programs
from .. import mesonlib
from .. import mlog
from ..compilers import LANGUAGES_USING_LDFLAGS
@ -140,7 +141,7 @@ class ExecutableSerialisation:
self.cmd_args = cmd_args
self.env = env
if exe_wrapper is not None:
assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
assert(isinstance(exe_wrapper, programs.ExternalProgram))
self.exe_runner = exe_wrapper
self.workdir = workdir
self.extra_paths = extra_paths
@ -152,7 +153,7 @@ class ExecutableSerialisation:
class TestSerialisation:
def __init__(self, name: str, project: str, suite: str, fname: T.List[str],
is_cross_built: bool, exe_wrapper: T.Optional[dependencies.ExternalProgram],
is_cross_built: bool, exe_wrapper: T.Optional[programs.ExternalProgram],
needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str],
env: build.EnvironmentVariables, should_fail: bool,
timeout: T.Optional[int], workdir: T.Optional[str],
@ -164,7 +165,7 @@ class TestSerialisation:
self.fname = fname
self.is_cross_built = is_cross_built
if exe_wrapper is not None:
assert(isinstance(exe_wrapper, dependencies.ExternalProgram))
assert(isinstance(exe_wrapper, programs.ExternalProgram))
self.exe_runner = exe_wrapper
self.is_parallel = is_parallel
self.cmd_args = cmd_args
@ -406,7 +407,7 @@ class Backend:
env: T.Optional[build.EnvironmentVariables] = None):
exe = cmd[0]
cmd_args = cmd[1:]
if isinstance(exe, dependencies.ExternalProgram):
if isinstance(exe, programs.ExternalProgram):
exe_cmd = exe.get_command()
exe_for_machine = exe.for_machine
elif isinstance(exe, build.BuildTarget):
@ -490,7 +491,7 @@ class Backend:
['--internal', 'exe', '--capture', capture, '--'] + es.cmd_args),
', '.join(reasons))
if isinstance(exe, (dependencies.ExternalProgram,
if isinstance(exe, (programs.ExternalProgram,
build.BuildTarget, build.CustomTarget)):
basename = exe.name
elif isinstance(exe, mesonlib.File):
@ -897,11 +898,11 @@ class Backend:
arr = []
for t in sorted(tests, key=lambda tst: -1 * tst.priority):
exe = t.get_exe()
if isinstance(exe, dependencies.ExternalProgram):
if isinstance(exe, programs.ExternalProgram):
cmd = exe.get_command()
else:
cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
if isinstance(exe, (build.BuildTarget, dependencies.ExternalProgram)):
if isinstance(exe, (build.BuildTarget, programs.ExternalProgram)):
test_for_machine = exe.for_machine
else:
# E.g. an external verifier or simulator program run on a generated executable.

@ -25,6 +25,7 @@ import typing as T
from . import environment
from . import dependencies
from . import mlog
from . import programs
from .mesonlib import (
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources,
@ -1486,7 +1487,7 @@ class Generator:
if len(args) != 1:
raise InvalidArguments('Generator requires exactly one positional argument: the executable')
exe = unholder(args[0])
if not isinstance(exe, (Executable, dependencies.ExternalProgram)):
if not isinstance(exe, (Executable, programs.ExternalProgram)):
raise InvalidArguments('First generator argument must be an executable.')
self.exe = exe
self.depfile = None
@ -1610,7 +1611,7 @@ class GeneratedList:
self.depend_files = []
self.preserve_path_from = preserve_path_from
self.extra_args = extra_args if extra_args is not None else []
if isinstance(self.generator.exe, dependencies.ExternalProgram):
if isinstance(self.generator.exe, programs.ExternalProgram):
if not self.generator.exe.found():
raise InvalidArguments('Tried to use not-found external program as generator')
path = self.generator.exe.get_path()
@ -2176,7 +2177,7 @@ class CommandBase:
elif isinstance(c, File):
self.depend_files.append(c)
final_cmd.append(c)
elif isinstance(c, dependencies.ExternalProgram):
elif isinstance(c, programs.ExternalProgram):
if not c.found():
raise InvalidArguments('Tried to use not-found external program in "command"')
path = c.get_path()

@ -23,12 +23,13 @@ import re
import os
from .. import mlog
from ..environment import Environment
from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice, is_windows, OptionKey
from ..programs import find_external_program, NonExistingExternalProgram
if T.TYPE_CHECKING:
from ..environment import Environment
from ..dependencies.base import ExternalProgram
from ..compilers import Compiler
from ..programs import ExternalProgram
TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]]
TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]]
@ -65,9 +66,7 @@ class CMakeExecutor:
if self.prefix_paths:
self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))]
def find_cmake_binary(self, environment: 'Environment', silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]:
from ..dependencies.base import find_external_program, NonExistingExternalProgram
def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]:
# Only search for CMake the first time and store the result in the class
# definition
if isinstance(CMakeExecutor.class_cmakebin[self.for_machine], NonExistingExternalProgram):

@ -25,6 +25,7 @@ from .. import mlog, mesonlib
from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey
from ..mesondata import mesondata
from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header
from ..programs import ExternalProgram
from enum import Enum
from functools import lru_cache
from pathlib import Path
@ -750,7 +751,6 @@ class ConverterCustomTarget:
if target:
# When cross compiling, binaries have to be executed with an exe_wrapper (for instance wine for mingw-w64)
if self.env.exe_wrapper is not None and self.env.properties[self.for_machine].get_cmake_use_exe_wrapper():
from ..dependencies import ExternalProgram
assert isinstance(self.env.exe_wrapper, ExternalProgram)
cmd += self.env.exe_wrapper.get_command()
cmd += [target]

@ -40,10 +40,11 @@ from .compilers import (
if T.TYPE_CHECKING:
from ..coredata import KeyedOptionDictType
from ..dependencies import Dependency, ExternalProgram
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker
from ..programs import ExternalProgram
CompilerMixinBase = Compiler
else:

@ -42,10 +42,11 @@ from .mixins.emscripten import EmscriptenMixin
if T.TYPE_CHECKING:
from ..coredata import KeyedOptionDictType
from ..dependencies import Dependency, ExternalProgram
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker
from ..programs import ExternalProgram
from .mixins.clike import CLikeCompiler as CompilerMixinBase
else:
CompilerMixinBase = object

@ -29,10 +29,11 @@ from .compilers import (Compiler, cuda_buildtype_args, cuda_optimization_args,
if T.TYPE_CHECKING:
from ..build import BuildTarget
from ..coredata import KeyedOptionDictType
from ..dependencies import Dependency, ExternalProgram
from ..dependencies import Dependency
from ..environment import Environment # noqa: F401
from ..envconfig import MachineInfo
from ..linkers import DynamicLinker
from ..programs import ExternalProgram
class _Phase(enum.Enum):

@ -34,7 +34,7 @@ from .mixins.gnu import GnuCompiler
if T.TYPE_CHECKING:
from .compilers import Compiler as CompilerMixinBase
from ..dependencies import Dependency, ExternalProgram
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker

@ -37,10 +37,11 @@ from mesonbuild.mesonlib import (
if T.TYPE_CHECKING:
from ..coredata import KeyedOptionDictType
from ..dependencies import Dependency, ExternalProgram
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker
from ..programs import ExternalProgram
class FortranCompiler(CLikeCompiler, Compiler):

@ -41,9 +41,10 @@ from ..compilers import CompileCheckMode
from .visualstudio import VisualStudioLikeCompiler
if T.TYPE_CHECKING:
from ...dependencies import Dependency, ExternalProgram
from ...dependencies import Dependency
from ...environment import Environment
from ...compilers.compilers import Compiler
from ...programs import ExternalProgram
else:
# This is a bit clever, for mypy we pretend that these mixins descend from
# Compiler, so we get all of the methods and attributes defined for us, but

@ -23,7 +23,7 @@ from .mixins.gnu import GnuCompiler
from .mixins.clang import ClangCompiler
if T.TYPE_CHECKING:
from ..dependencies import ExternalProgram
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker

@ -23,7 +23,7 @@ from .mixins.gnu import GnuCompiler
from .mixins.clang import ClangCompiler
if T.TYPE_CHECKING:
from ..dependencies import ExternalProgram
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
from ..linkers import DynamicLinker

@ -25,10 +25,10 @@ from .compilers import Compiler, rust_buildtype_args, clike_debug_args
if T.TYPE_CHECKING:
from ..coredata import KeyedOptionDictType
from ..dependencies import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment # noqa: F401
from ..linkers import DynamicLinker
from ..programs import ExternalProgram
rust_optimization_args = {

@ -16,10 +16,11 @@ from .boost import BoostDependency
from .cuda import CudaDependency
from .hdf5 import hdf5_factory
from .base import ( # noqa: F401
Dependency, DependencyException, DependencyMethods, ExternalProgram, EmptyExternalProgram, NonExistingExternalProgram,
ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency,
PkgConfigDependency, CMakeDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language,
DependencyFactory)
Dependency, DependencyException, DependencyMethods, ExternalDependency,
NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency,
InternalDependency, PkgConfigDependency, CMakeDependency,
find_external_dependency, get_dep_identifier, packages,
_packages_accept_language, DependencyFactory)
from .dev import ValgrindDependency, gmock_factory, gtest_factory, llvm_factory, zlib_factory
from .coarrays import coarray_factory
from .mpi import mpi_factory

@ -21,8 +21,6 @@ import re
import json
import shlex
import shutil
import stat
import sys
import textwrap
import platform
import typing as T
@ -38,6 +36,7 @@ from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine
from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list, split_args
from ..mesonlib import Version, LibType, OptionKey
from ..mesondata import mesondata
from ..programs import ExternalProgram, find_external_program
if T.TYPE_CHECKING:
from ..compilers.compilers import CompilerType # noqa: F401
@ -75,30 +74,6 @@ class DependencyMethods(Enum):
DUB = 'dub'
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]:
"""Find an external program, chcking the cross file plus any default options."""
# Lookup in cross or machine file.
potential_path = env.lookup_binary_entry(for_machine, name)
if potential_path is not None:
mlog.debug('{} binary for {} specified from cross file, native file, '
'or env var as {}'.format(display_name, for_machine, potential_path))
yield ExternalProgram.from_entry(name, potential_path)
# We never fallback if the user-specified option is no good, so
# stop returning options.
return
mlog.debug(f'{display_name} binary missing from cross or native file, or env var undefined.')
# 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)
else:
mlog.debug('Default target is not allowed for cross use')
class Dependency:
@classmethod
@ -1852,303 +1827,6 @@ class DubDependency(ExternalDependency):
def get_methods():
return [DependencyMethods.DUB]
class ExternalProgram:
windows_exts = ('exe', 'msc', 'com', 'bat', 'cmd')
# An 'ExternalProgram' always runs on the build machine
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):
self.name = name
if command is not None:
self.command = listify(command)
if mesonlib.is_windows():
cmd = self.command[0]
args = self.command[1:]
# Check whether the specified cmd is a path to a script, in
# which case we need to insert the interpreter. If not, try to
# use it as-is.
ret = self._shebang_to_cmd(cmd)
if ret:
self.command = ret + args
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
# Set path to be the last item that is actually a file (in order to
# skip options in something like ['python', '-u', 'file.py']. If we
# can't find any components, default to the last component of the path.
self.path = self.command[-1]
for i in range(len(self.command) - 1, -1, -1):
arg = self.command[i]
if arg is not None and os.path.isfile(arg):
self.path = arg
break
if not silent:
# ignore the warning because derived classes never call this __init__
# method, and thus only the found() method of this class is ever executed
if self.found(): # lgtm [py/init-calls-subclass]
mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
'(%s)' % ' '.join(self.command))
else:
mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
def summary_value(self) -> T.Union[str, mlog.AnsiDecorator]:
if not self.found():
return mlog.red('NO')
return self.path
def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def description(self) -> str:
'''Human friendly description of the command'''
return ' '.join(self.command)
@classmethod
def from_bin_list(cls, env: Environment, for_machine: MachineChoice, name):
# There is a static `for_machine` for this class because the binary
# aways runs on the build platform. (It's host platform is our build
# platform.) But some external programs have a target platform, so this
# is what we are specifying here.
command = env.lookup_binary_entry(for_machine, name)
if command is None:
return NonExistingExternalProgram()
return cls.from_entry(name, command)
@staticmethod
@functools.lru_cache(maxsize=None)
def _windows_sanitize_path(path: str) -> str:
# Ensure that we use USERPROFILE even when inside MSYS, MSYS2, Cygwin, etc.
if 'USERPROFILE' not in os.environ:
return path
# The WindowsApps directory is a bit of a problem. It contains
# some zero-sized .exe files which have "reparse points", that
# might either launch an installed application, or might open
# a page in the Windows Store to download the application.
#
# To handle the case where the python interpreter we're
# running on came from the Windows Store, if we see the
# WindowsApps path in the search path, replace it with
# dirname(sys.executable).
appstore_dir = Path(os.environ['USERPROFILE']) / 'AppData' / 'Local' / 'Microsoft' / 'WindowsApps'
paths = []
for each in path.split(os.pathsep):
if Path(each) != appstore_dir:
paths.append(each)
elif 'WindowsApps' in sys.executable:
paths.append(os.path.dirname(sys.executable))
return os.pathsep.join(paths)
@staticmethod
def from_entry(name, command):
if isinstance(command, list):
if len(command) == 1:
command = command[0]
# We cannot do any searching if the command is a list, and we don't
# need to search if the path is an absolute path.
if isinstance(command, list) or os.path.isabs(command):
return ExternalProgram(name, command=command, silent=True)
assert isinstance(command, str)
# Search for the command using the specified string!
return ExternalProgram(command, silent=True)
@staticmethod
def _shebang_to_cmd(script: str) -> T.Optional[list]:
"""
Check if the file has a shebang and manually parse it to figure out
the interpreter to use. This is useful if the script is not executable
or if we're on Windows (which does not understand shebangs).
"""
try:
with open(script) as f:
first_line = f.readline().strip()
if first_line.startswith('#!'):
# In a shebang, everything before the first space is assumed to
# be the command to run and everything after the first space is
# the single argument to pass to that command. So we must split
# exactly once.
commands = first_line[2:].split('#')[0].strip().split(maxsplit=1)
if mesonlib.is_windows():
# Windows does not have UNIX paths so remove them,
# but don't remove Windows paths
if commands[0].startswith('/'):
commands[0] = commands[0].split('/')[-1]
if len(commands) > 0 and commands[0] == 'env':
commands = commands[1:]
# Windows does not ship python3.exe, but we know the path to it
if len(commands) > 0 and commands[0] == 'python3':
commands = mesonlib.python_command + commands[1:]
elif mesonlib.is_haiku():
# Haiku does not have /usr, but a lot of scripts assume that
# /usr/bin/env always exists. Detect that case and run the
# script with the interpreter after it.
if commands[0] == '/usr/bin/env':
commands = commands[1:]
# We know what python3 is, we're running on it
if len(commands) > 0 and commands[0] == 'python3':
commands = mesonlib.python_command + commands[1:]
else:
# Replace python3 with the actual python3 that we are using
if commands[0] == '/usr/bin/env' and commands[1] == 'python3':
commands = mesonlib.python_command + commands[2:]
elif commands[0].split('/')[-1] == 'python3':
commands = mesonlib.python_command + commands[1:]
return commands + [script]
except Exception as e:
mlog.debug(e)
mlog.debug(f'Unusable script {script!r}')
return None
def _is_executable(self, path):
suffix = os.path.splitext(path)[-1].lower()[1:]
execmask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
if mesonlib.is_windows():
if suffix in self.windows_exts:
return True
elif os.stat(path).st_mode & execmask:
return not os.path.isdir(path)
return False
def _search_dir(self, name: str, search_dir: T.Optional[str]) -> T.Optional[list]:
if search_dir is None:
return None
trial = os.path.join(search_dir, name)
if os.path.exists(trial):
if self._is_executable(trial):
return [trial]
# Now getting desperate. Maybe it is a script file that is
# a) not chmodded executable, or
# b) we are on windows so they can't be directly executed.
return self._shebang_to_cmd(trial)
else:
if mesonlib.is_windows():
for ext in self.windows_exts:
trial_ext = f'{trial}.{ext}'
if os.path.exists(trial_ext):
return [trial_ext]
return None
def _search_windows_special_cases(self, name: str, command: str) -> list:
'''
Lots of weird Windows quirks:
1. PATH search for @name returns files with extensions from PATHEXT,
but only self.windows_exts are executable without an interpreter.
2. @name might be an absolute path to an executable, but without the
extension. This works inside MinGW so people use it a lot.
3. The script is specified without an extension, in which case we have
to manually search in PATH.
4. More special-casing for the shebang inside the script.
'''
if command:
# On Windows, even if the PATH search returned a full path, we can't be
# sure that it can be run directly if it's not a native executable.
# For instance, interpreted scripts sometimes need to be run explicitly
# with an interpreter if the file association is not done properly.
name_ext = os.path.splitext(command)[1]
if name_ext[1:].lower() in self.windows_exts:
# Good, it can be directly executed
return [command]
# Try to extract the interpreter from the shebang
commands = self._shebang_to_cmd(command)
if commands:
return commands
return [None]
# Maybe the name is an absolute path to a native Windows
# executable, but without the extension. This is technically wrong,
# but many people do it because it works in the MinGW shell.
if os.path.isabs(name):
for ext in self.windows_exts:
command = f'{name}.{ext}'
if os.path.exists(command):
return [command]
# 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(';')
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]) -> list:
'''
Search in the specified dir for the specified executable by name
and if not found search in PATH
'''
commands = self._search_dir(name, search_dir)
if commands:
return commands
# Do a standard search in PATH
path = os.environ.get('PATH', None)
if mesonlib.is_windows() and path:
path = self._windows_sanitize_path(path)
command = shutil.which(name, path=path)
if mesonlib.is_windows():
return self._search_windows_special_cases(name, command)
# On UNIX-like platforms, shutil.which() is enough to find
# all executables whether in PATH or with an absolute path
return [command]
def found(self) -> bool:
return self.command[0] is not None
def get_command(self) -> T.List[str]:
return self.command[:]
def get_path(self) -> str:
return self.path
def get_name(self) -> str:
return self.name
class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
"A program that will never exist"
def __init__(self, name: str = 'nonexistingprogram') -> None:
self.name = name
self.command = [None]
self.path = None
def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def found(self) -> bool:
return False
class EmptyExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
'''
A program object that returns an empty list of commands. Used for cases
such as a cross file exe_wrapper to represent that it's not required.
'''
def __init__(self):
self.name = None
self.command = []
self.path = None
def __repr__(self):
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def found(self):
return True
class ExternalLibrary(ExternalDependency):
def __init__(self, name, link_args, environment, language, silent=False):
super().__init__('library', environment, {}, language=language)

@ -28,14 +28,14 @@ from ..mesonlib import (
from ..environment import detect_cpu_family
from .base import DependencyException, DependencyMethods
from .base import ExternalDependency, NonExistingExternalProgram
from .base import ExternalDependency
from .base import ExtraFrameworkDependency, PkgConfigDependency
from .base import ConfigToolDependency, DependencyFactory
from .base import find_external_program
from ..programs import find_external_program, NonExistingExternalProgram
if T.TYPE_CHECKING:
from ..environment import Environment
from .base import ExternalProgram
from ..programs import ExternalProgram
class GLDependencySystem(ExternalDependency):

@ -27,6 +27,9 @@ from .mesonlib import (
PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey
)
from . import mlog
from .programs import (
ExternalProgram, EmptyExternalProgram
)
from .envconfig import (
BinaryTable, MachineInfo, Properties, known_cpu_families, CMakeVariables,
@ -208,7 +211,6 @@ def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.List[str]:
return r[0] if r else None
def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> (T.List[str], str):
from .dependencies.base import ExternalProgram
env_ninja = os.environ.get('NINJA', None)
for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']:
prog = ExternalProgram(n, silent=True)
@ -694,7 +696,6 @@ class Environment:
exe_wrapper = self.lookup_binary_entry(MachineChoice.HOST, 'exe_wrapper')
if exe_wrapper is not None:
from .dependencies import ExternalProgram
self.exe_wrapper = ExternalProgram.from_bin_list(self, MachineChoice.HOST, 'exe_wrapper')
else:
self.exe_wrapper = None
@ -2157,6 +2158,5 @@ class Environment:
def get_exe_wrapper(self):
if not self.need_exe_wrapper():
from .dependencies import EmptyExternalProgram
return EmptyExternalProgram()
return self.exe_wrapper

@ -22,7 +22,7 @@ from . import compilers
from .wrap import wrap, WrapMode
from . import mesonlib
from .mesonlib import FileMode, MachineChoice, OptionKey, Popen_safe, listify, extract_as_list, has_path_sep, unholder
from .dependencies import ExternalProgram
from .programs import ExternalProgram, NonExistingExternalProgram
from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException
from .depfile import DepFile
from .interpreterbase import InterpreterBase, typed_pos_args
@ -72,7 +72,7 @@ def stringifyUserArguments(args, quote=False):
raise InvalidArguments('Function accepts only strings, integers, lists, dictionaries and lists thereof.')
class OverrideProgram(dependencies.ExternalProgram):
class OverrideProgram(ExternalProgram):
pass
@ -1931,9 +1931,9 @@ class MesonMain(InterpreterObject):
found = self._found_source_scripts[key]
elif isinstance(prog, mesonlib.File):
prog = prog.rel_to_builddir(self.interpreter.environment.source_dir)
found = dependencies.ExternalProgram(prog, search_dir=self.interpreter.environment.build_dir)
found = ExternalProgram(prog, search_dir=self.interpreter.environment.build_dir)
else:
found = dependencies.ExternalProgram(prog, search_dir=search_dir)
found = ExternalProgram(prog, search_dir=search_dir)
if found.found():
self._found_source_scripts[key] = found
@ -1976,7 +1976,7 @@ class MesonMain(InterpreterObject):
elif isinstance(a, build.ConfigureFile):
new = True
script_args.append(os.path.join(a.subdir, a.targetname))
elif isinstance(a, dependencies.ExternalProgram):
elif isinstance(a, ExternalProgram):
script_args.extend(a.command)
new = True
else:
@ -2163,7 +2163,7 @@ class MesonMain(InterpreterObject):
if not os.path.exists(abspath):
raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
exe = OverrideProgram(name, abspath)
if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)):
if not isinstance(exe, (ExternalProgram, build.Executable)):
raise InterpreterException('Second argument must be an external program or executable.')
self.interpreter.add_find_program_override(name, exe)
@ -2543,7 +2543,7 @@ class Interpreter(InterpreterBase):
return DataHolder(item)
elif isinstance(item, dependencies.Dependency):
return DependencyHolder(item, self.subproject)
elif isinstance(item, dependencies.ExternalProgram):
elif isinstance(item, ExternalProgram):
return ExternalProgramHolder(item, self.subproject)
elif isinstance(item, ModuleObject):
return ModuleObjectHolder(item, self)
@ -2576,7 +2576,7 @@ class Interpreter(InterpreterBase):
elif isinstance(v, Test):
self.build.tests.append(v)
elif isinstance(v, (int, str, bool, Disabler, ObjectHolder, build.GeneratedList,
dependencies.ExternalProgram)):
ExternalProgram)):
pass
else:
raise InterpreterException('Module returned a value of unknown type.')
@ -3424,12 +3424,12 @@ external dependencies (including libraries) must go to "dependencies".''')
else:
raise InvalidArguments('find_program only accepts strings and '
'files, not {!r}'.format(exename))
extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
extra_search_dirs=extra_search_dirs,
silent=True)
extprog = ExternalProgram(exename, search_dir=search_dir,
extra_search_dirs=extra_search_dirs,
silent=True)
progobj = ExternalProgramHolder(extprog, self.subproject)
if progobj.found():
extra_info.append('({})'.format(' '.join(progobj.get_command())))
extra_info.append(f"({' '.join(progobj.get_command())})")
return progobj
def program_from_overrides(self, command_names, extra_info):
@ -3457,7 +3457,7 @@ external dependencies (including libraries) must go to "dependencies".''')
self.build.find_overrides[name] = exe
def notfound_program(self, args):
return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject)
return ExternalProgramHolder(NonExistingExternalProgram(' '.join(args)), self.subproject)
# TODO update modules to always pass `for_machine`. It is bad-form to assume
# the host machine.
@ -3515,7 +3515,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if progobj is None:
progobj = self.program_from_system(args, search_dirs, extra_info)
if progobj is None and args[0].endswith('python3'):
prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True)
prog = ExternalProgram('python3', mesonlib.python_command, silent=True)
progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None
if progobj is None and fallback and required:
progobj = self.find_program_fallback(fallback, args, required, extra_info)
@ -4032,10 +4032,10 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
cleaned_args = []
for i in unholder(listify(all_args)):
if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)):
if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, ExternalProgram, mesonlib.File)):
mlog.debug('Wrong type:', str(i))
raise InterpreterException('Invalid argument to run_target.')
if isinstance(i, dependencies.ExternalProgram) and not i.found():
if isinstance(i, ExternalProgram) and not i.found():
raise InterpreterException(f'Tried to use non-existing executable {i.name!r}')
cleaned_args.append(i)
if isinstance(cleaned_args[0], str):
@ -4647,7 +4647,7 @@ This warning will become a hard error in a future Meson release.
for i in inp:
if isinstance(i, str):
exe_wrapper.append(i)
elif isinstance(i, dependencies.ExternalProgram):
elif isinstance(i, ExternalProgram):
if not i.found():
raise InterpreterException('Tried to use non-found executable.')
exe_wrapper += i.get_command()

@ -18,7 +18,7 @@ import typing as T
from . import ExtensionModule, ModuleReturnValue
from .. import build, dependencies, mesonlib, mlog
from .. import build, mesonlib, mlog
from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args
from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder, DependencyHolder
from ..interpreterbase import (
@ -36,6 +36,7 @@ from ..interpreterbase import (
InvalidArguments,
)
from ..programs import ExternalProgram
COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion']
@ -232,7 +233,7 @@ class CmakeModule(ExtensionModule):
if self.cmake_detected:
return True
cmakebin = dependencies.ExternalProgram('cmake', silent=False)
cmakebin = ExternalProgram('cmake', silent=False)
p, stdout, stderr = mesonlib.Popen_safe(cmakebin.get_command() + ['--system-information', '-G', 'Ninja'])[0:3]
if p.returncode != 0:
mlog.log(f'error retrieving cmake information: returnCode={p.returncode} stdout={stdout} stderr={stderr}')

@ -26,10 +26,8 @@ from ..mesonlib import (
Popen_safe, MesonException
)
from ..dependencies.base import (
ExternalProgram, DubDependency
)
from ..dependencies.base import DubDependency
from ..programs import ExternalProgram
from ..interpreter import DependencyHolder
class DlangModule(ExtensionModule):

@ -33,7 +33,8 @@ from ..mesonlib import (
MachineChoice, MesonException, OrderedSet, Popen_safe, extract_as_list,
join_args, unholder,
)
from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram
from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
from ..programs import ExternalProgram
from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs
if T.TYPE_CHECKING:

@ -23,9 +23,10 @@ from mesonbuild.coredata import MesonException
from . import ModuleReturnValue
from . import ExtensionModule
from . import get_include_args
from ..dependencies import Dependency, InternalDependency, ExternalProgram
from ..dependencies import Dependency, InternalDependency
from ..interpreterbase import FeatureNew, InvalidArguments, noPosargs, noKwargs
from ..interpreter import CustomTargetHolder
from ..programs import ExternalProgram
def ensure_list(value):

@ -32,9 +32,9 @@ from .. import mlog
from ..environment import detect_cpu_family
from ..dependencies.base import (
DependencyMethods, ExternalDependency,
ExternalProgram, PkgConfigDependency,
NonExistingExternalProgram, NotFoundDependency
PkgConfigDependency, NotFoundDependency
)
from ..programs import ExternalProgram, NonExistingExternalProgram
mod_kwargs = {'subdir'}
mod_kwargs.update(known_shmod_kwargs)

@ -13,12 +13,13 @@
# limitations under the License.
import sysconfig
from .. import mesonlib, dependencies
from .. import mesonlib
from . import ExtensionModule
from mesonbuild.modules import ModuleReturnValue
from ..interpreterbase import noKwargs, permittedKwargs, FeatureDeprecated
from ..build import known_shmod_kwargs
from ..programs import ExternalProgram
class Python3Module(ExtensionModule):
@ -50,9 +51,9 @@ class Python3Module(ExtensionModule):
def find_python(self, state, args, kwargs):
command = state.environment.lookup_binary_entry(mesonlib.MachineChoice.HOST, 'python3')
if command is not None:
py3 = dependencies.ExternalProgram.from_entry('python3', command)
py3 = ExternalProgram.from_entry('python3', command)
else:
py3 = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True)
py3 = ExternalProgram('python3', mesonlib.python_command, silent=True)
return ModuleReturnValue(py3, [py3])
@noKwargs

@ -17,11 +17,12 @@ import shutil
from .. import mlog
from .. import build
from ..mesonlib import MesonException, extract_as_list, File, unholder, version_compare
from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency, Qt6Dependency, NonExistingExternalProgram
from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency, Qt6Dependency
import xml.etree.ElementTree as ET
from . import ModuleReturnValue, get_include_args, ExtensionModule
from ..interpreterbase import noPosargs, permittedKwargs, FeatureNew, FeatureNewKwargs
from ..interpreter import extract_required_kwarg
from ..programs import NonExistingExternalProgram
_QT_DEPS_LUT = {
4: Qt4Dependency,

@ -26,7 +26,7 @@ from ..mesonlib import stringlistify, unholder, listify, typeslistify, File
if T.TYPE_CHECKING:
from . import ModuleState
from ..interpreter import Interpreter
from ..dependencies import ExternalProgram
from ..programs import ExternalProgram
class RustModule(ExtensionModule):

@ -24,7 +24,7 @@ from . import ModuleReturnValue
from . import ExtensionModule
from ..interpreter import CustomTargetHolder
from ..interpreterbase import permittedKwargs, FeatureNewKwargs, flatten
from ..dependencies import ExternalProgram
from ..programs import ExternalProgram
class ResourceCompilerType(enum.Enum):
windres = 1

@ -43,9 +43,9 @@ from . import environment
from . import mlog
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .dependencies import ExternalProgram
from .mesonlib import MesonException, OrderedSet, get_wine_shortpath, split_args, join_args
from .mintro import get_infodir, load_info_file
from .programs import ExternalProgram
from .backend.backends import TestProtocol, TestSerialisation
# GNU autotools interprets a return code of 77 from tests it executes to

@ -0,0 +1,351 @@
# Copyright 2013-2020 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Representations and logic for External and Internal Programs."""
import functools
import os
import shutil
import stat
import sys
import typing as T
from pathlib import Path
from . import mesonlib
from . import mlog
from .mesonlib import MachineChoice
if T.TYPE_CHECKING:
from .environment import Environment
class ExternalProgram:
windows_exts = ('exe', 'msc', 'com', 'bat', 'cmd')
# An 'ExternalProgram' always runs on the build machine
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):
self.name = name
if command is not None:
self.command = mesonlib.listify(command)
if mesonlib.is_windows():
cmd = self.command[0]
args = self.command[1:]
# Check whether the specified cmd is a path to a script, in
# which case we need to insert the interpreter. If not, try to
# use it as-is.
ret = self._shebang_to_cmd(cmd)
if ret:
self.command = ret + args
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
# Set path to be the last item that is actually a file (in order to
# skip options in something like ['python', '-u', 'file.py']. If we
# can't find any components, default to the last component of the path.
self.path = self.command[-1]
for i in range(len(self.command) - 1, -1, -1):
arg = self.command[i]
if arg is not None and os.path.isfile(arg):
self.path = arg
break
if not silent:
# ignore the warning because derived classes never call this __init__
# method, and thus only the found() method of this class is ever executed
if self.found(): # lgtm [py/init-calls-subclass]
mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
'(%s)' % ' '.join(self.command))
else:
mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
def summary_value(self) -> T.Union[str, mlog.AnsiDecorator]:
if not self.found():
return mlog.red('NO')
return self.path
def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def description(self) -> str:
'''Human friendly description of the command'''
return ' '.join(self.command)
@classmethod
def from_bin_list(cls, env: 'Environment', for_machine: MachineChoice, name):
# There is a static `for_machine` for this class because the binary
# aways runs on the build platform. (It's host platform is our build
# platform.) But some external programs have a target platform, so this
# is what we are specifying here.
command = env.lookup_binary_entry(for_machine, name)
if command is None:
return NonExistingExternalProgram()
return cls.from_entry(name, command)
@staticmethod
@functools.lru_cache(maxsize=None)
def _windows_sanitize_path(path: str) -> str:
# Ensure that we use USERPROFILE even when inside MSYS, MSYS2, Cygwin, etc.
if 'USERPROFILE' not in os.environ:
return path
# The WindowsApps directory is a bit of a problem. It contains
# some zero-sized .exe files which have "reparse points", that
# might either launch an installed application, or might open
# a page in the Windows Store to download the application.
#
# To handle the case where the python interpreter we're
# running on came from the Windows Store, if we see the
# WindowsApps path in the search path, replace it with
# dirname(sys.executable).
appstore_dir = Path(os.environ['USERPROFILE']) / 'AppData' / 'Local' / 'Microsoft' / 'WindowsApps'
paths = []
for each in path.split(os.pathsep):
if Path(each) != appstore_dir:
paths.append(each)
elif 'WindowsApps' in sys.executable:
paths.append(os.path.dirname(sys.executable))
return os.pathsep.join(paths)
@staticmethod
def from_entry(name, command):
if isinstance(command, list):
if len(command) == 1:
command = command[0]
# We cannot do any searching if the command is a list, and we don't
# need to search if the path is an absolute path.
if isinstance(command, list) or os.path.isabs(command):
return ExternalProgram(name, command=command, silent=True)
assert isinstance(command, str)
# Search for the command using the specified string!
return ExternalProgram(command, silent=True)
@staticmethod
def _shebang_to_cmd(script: str) -> T.Optional[list]:
"""
Check if the file has a shebang and manually parse it to figure out
the interpreter to use. This is useful if the script is not executable
or if we're on Windows (which does not understand shebangs).
"""
try:
with open(script) as f:
first_line = f.readline().strip()
if first_line.startswith('#!'):
# In a shebang, everything before the first space is assumed to
# be the command to run and everything after the first space is
# the single argument to pass to that command. So we must split
# exactly once.
commands = first_line[2:].split('#')[0].strip().split(maxsplit=1)
if mesonlib.is_windows():
# Windows does not have UNIX paths so remove them,
# but don't remove Windows paths
if commands[0].startswith('/'):
commands[0] = commands[0].split('/')[-1]
if len(commands) > 0 and commands[0] == 'env':
commands = commands[1:]
# Windows does not ship python3.exe, but we know the path to it
if len(commands) > 0 and commands[0] == 'python3':
commands = mesonlib.python_command + commands[1:]
elif mesonlib.is_haiku():
# Haiku does not have /usr, but a lot of scripts assume that
# /usr/bin/env always exists. Detect that case and run the
# script with the interpreter after it.
if commands[0] == '/usr/bin/env':
commands = commands[1:]
# We know what python3 is, we're running on it
if len(commands) > 0 and commands[0] == 'python3':
commands = mesonlib.python_command + commands[1:]
else:
# Replace python3 with the actual python3 that we are using
if commands[0] == '/usr/bin/env' and commands[1] == 'python3':
commands = mesonlib.python_command + commands[2:]
elif commands[0].split('/')[-1] == 'python3':
commands = mesonlib.python_command + commands[1:]
return commands + [script]
except Exception as e:
mlog.debug(e)
mlog.debug(f'Unusable script {script!r}')
return None
def _is_executable(self, path):
suffix = os.path.splitext(path)[-1].lower()[1:]
execmask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
if mesonlib.is_windows():
if suffix in self.windows_exts:
return True
elif os.stat(path).st_mode & execmask:
return not os.path.isdir(path)
return False
def _search_dir(self, name: str, search_dir: T.Optional[str]) -> T.Optional[list]:
if search_dir is None:
return None
trial = os.path.join(search_dir, name)
if os.path.exists(trial):
if self._is_executable(trial):
return [trial]
# Now getting desperate. Maybe it is a script file that is
# a) not chmodded executable, or
# b) we are on windows so they can't be directly executed.
return self._shebang_to_cmd(trial)
else:
if mesonlib.is_windows():
for ext in self.windows_exts:
trial_ext = f'{trial}.{ext}'
if os.path.exists(trial_ext):
return [trial_ext]
return None
def _search_windows_special_cases(self, name: str, command: str) -> list:
'''
Lots of weird Windows quirks:
1. PATH search for @name returns files with extensions from PATHEXT,
but only self.windows_exts are executable without an interpreter.
2. @name might be an absolute path to an executable, but without the
extension. This works inside MinGW so people use it a lot.
3. The script is specified without an extension, in which case we have
to manually search in PATH.
4. More special-casing for the shebang inside the script.
'''
if command:
# On Windows, even if the PATH search returned a full path, we can't be
# sure that it can be run directly if it's not a native executable.
# For instance, interpreted scripts sometimes need to be run explicitly
# with an interpreter if the file association is not done properly.
name_ext = os.path.splitext(command)[1]
if name_ext[1:].lower() in self.windows_exts:
# Good, it can be directly executed
return [command]
# Try to extract the interpreter from the shebang
commands = self._shebang_to_cmd(command)
if commands:
return commands
return [None]
# Maybe the name is an absolute path to a native Windows
# executable, but without the extension. This is technically wrong,
# but many people do it because it works in the MinGW shell.
if os.path.isabs(name):
for ext in self.windows_exts:
command = f'{name}.{ext}'
if os.path.exists(command):
return [command]
# 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(';')
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]) -> list:
'''
Search in the specified dir for the specified executable by name
and if not found search in PATH
'''
commands = self._search_dir(name, search_dir)
if commands:
return commands
# Do a standard search in PATH
path = os.environ.get('PATH', None)
if mesonlib.is_windows() and path:
path = self._windows_sanitize_path(path)
command = shutil.which(name, path=path)
if mesonlib.is_windows():
return self._search_windows_special_cases(name, command)
# On UNIX-like platforms, shutil.which() is enough to find
# all executables whether in PATH or with an absolute path
return [command]
def found(self) -> bool:
return self.command[0] is not None
def get_command(self) -> T.List[str]:
return self.command[:]
def get_path(self) -> str:
return self.path
def get_name(self) -> str:
return self.name
class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
"A program that will never exist"
def __init__(self, name: str = 'nonexistingprogram') -> None:
self.name = name
self.command = [None]
self.path = None
def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def found(self) -> bool:
return False
class EmptyExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
'''
A program object that returns an empty list of commands. Used for cases
such as a cross file exe_wrapper to represent that it's not required.
'''
def __init__(self):
self.name = None
self.command = []
self.path = None
def __repr__(self):
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def found(self):
return True
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]:
"""Find an external program, chcking the cross file plus any default options."""
# Lookup in cross or machine file.
potential_cmd = env.lookup_binary_entry(for_machine, name)
if potential_cmd is not None:
mlog.debug(f'{display_name} binary for {for_machine} specified from cross file, native file, '
f'or env var as {potential_cmd}')
yield ExternalProgram.from_entry(name, potential_cmd)
# We never fallback if the user-specified option is no good, so
# stop returning options.
return
mlog.debug(f'{display_name} binary missing from cross or native file, or env var undefined.')
# 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)
else:
mlog.debug('Default target is not allowed for cross use')

@ -61,7 +61,8 @@ from mesonbuild.mesonlib import (
)
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import MesonException, EnvironmentException, OptionKey
from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
from mesonbuild.dependencies import PkgConfigDependency
from mesonbuild.programs import ExternalProgram
import mesonbuild.dependencies.base
from mesonbuild.build import Target, ConfigurationData
import mesonbuild.modules.pkgconfig

Loading…
Cancel
Save