diff options
| author | Eli Schwartz <eschwartz93@gmail.com> | 2025-10-15 21:49:10 -0400 |
|---|---|---|
| committer | Eli Schwartz <eschwartz93@gmail.com> | 2025-10-15 23:01:36 -0400 |
| commit | 5c0aad57f92d2a6bebc1cb17655dd8a56f4bcd3f (patch) | |
| tree | 69a228a10f425b33392df576f79bd8b7c5402104 | |
| parent | 1177e77c2893891cb35144b8033786cb8f75c7cd (diff) | |
| download | meson-5c0aad57f92d2a6bebc1cb17655dd8a56f4bcd3f.tar.gz | |
revert local_program()
This reverts https://github.com/mesonbuild/meson/pull/15107
Explicit objections regarding the design were raised and not answered,
so it shouldn't have been merged. It needs to be discussed and
revisited.
25 files changed, 187 insertions, 402 deletions
diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim index c4181c3f1..905e9fe64 100644 --- a/data/syntax-highlighting/vim/syntax/meson.vim +++ b/data/syntax-highlighting/vim/syntax/meson.vim @@ -93,7 +93,6 @@ syn keyword mesonBuiltin \ executable \ files \ find_program - \ local_program \ generator \ get_option \ get_variable diff --git a/docs/markdown/snippets/local_program.md b/docs/markdown/snippets/local_program.md deleted file mode 100644 index e671efb2c..000000000 --- a/docs/markdown/snippets/local_program.md +++ /dev/null @@ -1,13 +0,0 @@ -## New [[local_program]] function - -Similar to [[find_program]], but only work with a program that exists in -source tree, or a built target. Meson will not look for the program in the -system or in a subproject. - -In addition, `depends` keyword argument can be specified in case the program -depends on built targets, for example a Python script could require a compiled -C module. If any such dependency is present, the program can only be used in -build-time commands (e.g. [[custom_target]]). - -The program can be passed to [[meson.override_find_program]] and used in -subprojects. diff --git a/docs/yaml/functions/local_program.yaml b/docs/yaml/functions/local_program.yaml deleted file mode 100644 index 07fafcf3a..000000000 --- a/docs/yaml/functions/local_program.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: local_program -returns: external_program -since: 1.10.0 -description: | - Similar to [[find_program]], but only work with a program that exists in - source tree, or a built target. Meson will not look for the program in the - system or in a subproject. - - In addition, `depends` keyword argument can be specified in case the program - depends on built targets, for example a Python script could require a compiled - C module. If any such dependency is present, the program can only be used in - build-time commands (e.g. [[custom_target]]). - - The program can be passed to [[meson.override_find_program]] and used in - subprojects. - -posargs: - program: - type: str | file | exe | custom_tgt | custom_idx - description: | - A [[@file]] object or the name of a program in the current source directory. - -kwargs: - depend_files: - type: array[str | file] - description: | - files ([[@str]], - [[@file]], or the return value of [[configure_file]] that - this target depends on. Useful for adding regen dependencies. - - depends: - type: array[build_tgt | custom_tgt | custom_idx] - description: | - Specifies that this target depends on the specified - target(s). If specified, this program can only be used at build time, - after those targets have been built. - - interpreter: - type: external_program - description: | - When the program is a [[@custom_tgt]], Meson cannot derive the interpreter - from the file's "shebang" (`#!`) line before it's built. If needed, this - argument allows specifying an interpreter for the script. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 17ca15dd7..2f82a4a84 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -200,7 +200,6 @@ class AstInterpreter(InterpreterBase): 'configuration_data': self.func_do_nothing, 'configure_file': self.func_do_nothing, 'find_program': self.func_do_nothing, - 'local_program': self.func_do_nothing, 'include_directories': self.func_do_nothing, 'add_global_arguments': self.func_do_nothing, 'add_global_link_arguments': self.func_do_nothing, diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index a322c9882..a5995e6b1 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -529,7 +529,7 @@ class Backend: return result def get_executable_serialisation( - self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, File, str, build.LocalProgram]], + self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]], workdir: T.Optional[str] = None, extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, capture: T.Optional[str] = None, @@ -542,15 +542,13 @@ class Backend: # XXX: cmd_args either need to be lowered to strings, or need to be checked for non-string arguments, right? exe, *raw_cmd_args = cmd - if isinstance(exe, build.LocalProgram): - exe = exe.program if isinstance(exe, programs.ExternalProgram): exe_cmd = exe.get_command() exe_for_machine = exe.for_machine elif isinstance(exe, build.BuildTarget): exe_cmd = [self.get_target_filename_abs(exe)] exe_for_machine = exe.for_machine - elif isinstance(exe, (build.CustomTarget, build.CustomTargetIndex)): + elif isinstance(exe, build.CustomTarget): # The output of a custom target can either be directly runnable # or not, that is, a script, a native binary or a cross compiled # binary when exe wrapper is available and when it is not. @@ -567,11 +565,9 @@ class Backend: cmd_args: T.List[str] = [] for c in raw_cmd_args: - if isinstance(c, build.LocalProgram): - c = c.program if isinstance(c, programs.ExternalProgram): cmd_args += c.get_command() - elif isinstance(c, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): + elif isinstance(c, (build.BuildTarget, build.CustomTarget)): cmd_args.append(self.get_target_filename_abs(c)) elif isinstance(c, mesonlib.File): cmd_args.append(c.rel_to_builddir(self.environment.source_dir)) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 8f294c2ba..41ba32b63 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -363,7 +363,7 @@ class Build: self.stdlibs = PerMachine({}, {}) self.test_setups: T.Dict[str, TestSetup] = {} self.test_setup_default_name = None - self.find_overrides: T.Dict[str, T.Union[programs.ExternalProgram, LocalProgram]] = {} + self.find_overrides: T.Dict[str, T.Union['OverrideExecutable', programs.ExternalProgram, programs.OverrideProgram]] = {} self.searched_programs: T.Set[str] = set() # The list of all programs that have been searched for. # If we are doing a cross build we need two caches, if we're doing a @@ -1995,7 +1995,7 @@ class FileMaybeInTargetPrivateDir: return self.fname class Generator(HoldableObject): - def __init__(self, exe: T.Union[Executable, programs.ExternalProgram, LocalProgram, CustomTarget, CustomTargetIndex], + def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], arguments: T.List[str], output: T.List[str], # how2dataclass @@ -2004,14 +2004,10 @@ class Generator(HoldableObject): capture: bool = False, depends: T.Optional[T.List[BuildTargetTypes]] = None, name: str = 'Generator'): - self.depends = list(depends or []) - if isinstance(exe, LocalProgram): - # FIXME: Generator does not have depend_files? - self.depends.extend(exe.depends) - exe = exe.program self.exe = exe self.depfile = depfile self.capture = capture + self.depends: T.List[BuildTargetTypes] = depends or [] self.arglist = arguments self.outputs = output self.name = name @@ -2020,7 +2016,7 @@ class Generator(HoldableObject): repr_str = "<{0}: {1}>" return repr_str.format(self.__class__.__name__, self.exe) - def get_exe(self) -> T.Union[Executable, programs.ExternalProgram, CustomTarget, CustomTargetIndex]: + def get_exe(self) -> T.Union['Executable', programs.ExternalProgram]: return self.exe def get_base_outnames(self, inname: str) -> T.List[str]: @@ -2260,6 +2256,10 @@ class Executable(BuildTarget): def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: return self.environment.get_bindir(), '{bindir}' + def description(self): + '''Human friendly description of the executable''' + return self.name + def type_suffix(self): return "@exe" @@ -2282,6 +2282,21 @@ class Executable(BuildTarget): def is_linkable_target(self): return self.is_linkwithable + def get_command(self) -> 'ImmutableListProtocol[str]': + """Provides compatibility with ExternalProgram. + + Since you can override ExternalProgram instances with Executables. + """ + return self.outputs + + def get_path(self) -> str: + """Provides compatibility with ExternalProgram.""" + return os.path.join(self.subdir, self.filename) + + def found(self) -> bool: + """Provides compatibility with ExternalProgram.""" + return True + class StaticLibrary(BuildTarget): known_kwargs = known_stlib_kwargs @@ -2785,15 +2800,11 @@ class CommandBase: dependencies: T.List[T.Union[BuildTarget, 'CustomTarget']] subproject: str - def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes, LocalProgram]]) -> \ + def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes]]) -> \ T.List[T.Union[str, File, BuildTarget, CustomTarget, programs.ExternalProgram]]: cmd = listify(cmd) final_cmd: T.List[T.Union[str, File, BuildTarget, 'CustomTarget']] = [] for c in cmd: - if isinstance(c, LocalProgram): - self.dependencies.extend(c.depends) - self.depend_files.extend(c.depend_files) - c = c.program if isinstance(c, str): final_cmd.append(c) elif isinstance(c, File): @@ -2859,7 +2870,7 @@ class CustomTarget(Target, CustomTargetBase, CommandBase): environment: environment.Environment, command: T.Sequence[T.Union[ str, BuildTargetTypes, GeneratedList, - programs.ExternalProgram, File, LocalProgram]], + programs.ExternalProgram, File]], sources: T.Sequence[T.Union[ str, File, BuildTargetTypes, ExtractedObjects, GeneratedList, programs.ExternalProgram]], @@ -3119,7 +3130,7 @@ class RunTarget(Target, CommandBase): typename = 'run' def __init__(self, name: str, - command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram, LocalProgram]], + command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram]], dependencies: T.Sequence[AnyTargetType], subdir: str, subproject: str, @@ -3342,46 +3353,17 @@ class ConfigurationData(HoldableObject): def keys(self) -> T.Iterator[str]: return self.values.keys() -class LocalProgram(programs.BaseProgram): - ''' A wrapper for a program that may have build dependencies.''' - def __init__(self, program: T.Union[programs.ExternalProgram, Executable, CustomTarget, CustomTargetIndex], version: str, - depends: T.Optional[T.List[T.Union[BuildTarget, CustomTarget]]] = None, - depend_files: T.Optional[T.List[File]] = None) -> None: - super().__init__() - self.name = program.name - self.program = program - self.depends = list(depends or []) - self.depend_files = list(depend_files or []) - self.version = version +class OverrideExecutable(Executable): + def __init__(self, executable: Executable, version: str): + self._executable = executable + self._version = version - def found(self) -> bool: - return True + def __getattr__(self, name: str) -> T.Any: + _executable = object.__getattribute__(self, '_executable') + return getattr(_executable, name) def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str: - return self.version - - def get_command(self) -> T.List[str]: - if isinstance(self.program, (Executable, CustomTarget, CustomTargetIndex)): - return [os.path.join(self.program.subdir, self.program.get_filename())] - return self.program.get_command() - - def get_path(self) -> str: - if isinstance(self.program, (Executable, CustomTarget, CustomTargetIndex)): - return os.path.join(self.program.subdir, self.program.get_filename()) - return self.program.get_path() - - def description(self) -> str: - if isinstance(self.program, Executable): - return self.program.name - if isinstance(self.program, (CustomTarget, CustomTargetIndex)): - return self.program.get_filename() - return self.program.description() - - def run_program(self) -> T.Optional[programs.ExternalProgram]: - ''' Returns an ExternalProgram if it can be run at configure time.''' - if isinstance(self.program, programs.ExternalProgram) and not self.depends: - return self.program - return None + return self._version # A bit poorly named, but this represents plain data files to copy # during install. diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index 5186b842b..e2ccce479 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -20,6 +20,7 @@ __all__ = [ 'SubprojectHolder', 'DependencyHolder', 'GeneratedListHolder', + 'ExternalProgramHolder', 'extract_required_kwarg', 'ArrayHolder', @@ -34,7 +35,8 @@ from .compiler import CompilerHolder from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, CustomTargetIndexHolder, MachineHolder, Test, ConfigurationDataHolder, SubprojectHolder, DependencyHolder, - GeneratedListHolder, extract_required_kwarg) + GeneratedListHolder, ExternalProgramHolder, + extract_required_kwarg) from .primitives import ( ArrayHolder, diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 51671bfc5..8481f36bd 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -21,7 +21,7 @@ from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugE FileMode, MachineChoice, is_parent_path, listify, extract_as_list, has_path_sep, path_is_in_root, PerMachine) from ..options import OptionKey -from ..programs import ExternalProgram, NonExistingExternalProgram, BaseProgram +from ..programs import ExternalProgram, NonExistingExternalProgram from ..dependencies import Dependency from ..depfile import DepFile from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args @@ -118,6 +118,7 @@ if T.TYPE_CHECKING: from ..backend.backends import Backend from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs from ..options import OptionDict + from ..programs import OverrideProgram from .type_checking import SourcesVarargsType # Input source types passed to Targets @@ -129,7 +130,7 @@ if T.TYPE_CHECKING: BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources] - ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.LocalProgram]], str] + ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str] TestClass = T.TypeVar('TestClass', bound=Test) @@ -368,7 +369,6 @@ class Interpreter(InterpreterBase, HoldableObject): 'executable': self.func_executable, 'files': self.func_files, 'find_program': self.func_find_program, - 'local_program': self.func_local_program, 'generator': self.func_generator, 'get_option': self.func_get_option, 'get_variable': self.func_get_variable, @@ -440,6 +440,7 @@ class Interpreter(InterpreterBase, HoldableObject): build.Generator: OBJ.GeneratorHolder, build.GeneratedList: OBJ.GeneratedListHolder, build.ExtractedObjects: OBJ.GeneratedObjectsHolder, + build.OverrideExecutable: OBJ.OverrideExecutableHolder, build.RunTarget: OBJ.RunTargetHolder, build.AliasTarget: OBJ.AliasTargetHolder, build.Headers: OBJ.HeadersHolder, @@ -469,7 +470,7 @@ class Interpreter(InterpreterBase, HoldableObject): ''' self.bound_holder_map.update({ dependencies.Dependency: OBJ.DependencyHolder, - BaseProgram: OBJ.BaseProgramHolder, + ExternalProgram: OBJ.ExternalProgramHolder, compilers.Compiler: compilerOBJ.CompilerHolder, ModuleObject: OBJ.ModuleObjectHolder, MutableModuleObject: OBJ.MutableModuleObjectHolder, @@ -768,8 +769,8 @@ class Interpreter(InterpreterBase, HoldableObject): # better error messages when overridden @typed_pos_args( 'run_command', - (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram), - varargs=(build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram)) + (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str), + varargs=(build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str)) @typed_kwargs( 'run_command', KwargInfo('check', (bool, NoneType), since='0.47.0'), @@ -777,21 +778,14 @@ class Interpreter(InterpreterBase, HoldableObject): ENV_KW.evolve(since='0.50.0'), ) def func_run_command(self, node: mparser.BaseNode, - args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram], - T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram]]], + args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str], + T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]], kwargs: 'kwtypes.RunCommand') -> RunProcess: return self.run_command_impl(args, kwargs) - def _compiled_exe_error(self, cmd: T.Union[build.LocalProgram, build.Executable]) -> T.NoReturn: - descr = cmd.name if isinstance(cmd, build.Executable) else cmd.description() - for name, exe in self.build.find_overrides.items(): - if cmd == exe: - raise InterpreterException(f'Program {name!r} was overridden with the compiled executable {descr!r} and therefore cannot be used during configuration') - raise InterpreterException(f'Program {descr!r} is a compiled executable and therefore cannot be used during configuration') - def run_command_impl(self, - args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram], - T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram]]], + args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str], + T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]], kwargs: 'kwtypes.RunCommand', in_builddir: bool = False) -> RunProcess: cmd, cargs = args @@ -805,17 +799,19 @@ class Interpreter(InterpreterBase, HoldableObject): mlog.warning(implicit_check_false_warning, once=True) check = False + overridden_msg = ('Program {!r} was overridden with the compiled ' + 'executable {!r} and therefore cannot be used during ' + 'configuration') expanded_args: T.List[str] = [] - if isinstance(cmd, build.LocalProgram): - prog = cmd.run_program() - if prog is None: - self._compiled_exe_error(cmd) - for f in cmd.depend_files: - self.add_build_def_file(f) - cmd = prog - elif isinstance(cmd, build.Executable): - self._compiled_exe_error(cmd) - elif isinstance(cmd, ExternalProgram): + if isinstance(cmd, build.Executable): + for name, exe in self.build.find_overrides.items(): + if cmd == exe: + progname = name + break + else: + raise InterpreterException(f'Program {cmd.description()!r} is a compiled executable and therefore cannot be used during configuration') + raise InterpreterException(overridden_msg.format(progname, cmd.description())) + if isinstance(cmd, ExternalProgram): if not cmd.found(): raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable') elif isinstance(cmd, compilers.Compiler): @@ -847,15 +843,8 @@ class Interpreter(InterpreterBase, HoldableObject): if not prog.found(): raise InterpreterException(f'Program {cmd!r} not found or not executable') expanded_args.append(prog.get_path()) - elif isinstance(a, build.LocalProgram): - prog = a.run_program() - if prog is None: - self._compiled_exe_error(a) - for f in a.depend_files: - self.add_build_def_file(f) - expanded_args.append(prog.get_path()) else: - self._compiled_exe_error(a) + raise InterpreterException(overridden_msg.format(a.name, cmd.description())) # If any file that was used as an argument to the command # changes, we must re-run the configuration step. @@ -1620,7 +1609,7 @@ class Interpreter(InterpreterBase, HoldableObject): def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString], extra_info: T.List['mlog.TV_Loggable'] - ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: + ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.OverrideExecutable]]: for name in command_names: if not isinstance(name, str): continue @@ -1635,7 +1624,7 @@ class Interpreter(InterpreterBase, HoldableObject): if isinstance(name, str): self.build.searched_programs.add(name) - def add_find_program_override(self, name: str, exe: T.Union[ExternalProgram, build.LocalProgram]) -> None: + def add_find_program_override(self, name: str, exe: T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']) -> None: if name in self.build.searched_programs: raise InterpreterException(f'Tried to override finding of executable "{name}" which has already been found.') if name in self.build.find_overrides: @@ -1660,7 +1649,7 @@ class Interpreter(InterpreterBase, HoldableObject): search_dirs: T.Optional[T.List[str]] = None, version_arg: T.Optional[str] = '', version_func: T.Optional[ProgramVersionFunc] = None - ) -> T.Union[ExternalProgram, build.LocalProgram]: + ) -> T.Union['ExternalProgram', 'build.OverrideExecutable', 'OverrideProgram']: args = mesonlib.listify(args) extra_info: T.List[mlog.TV_Loggable] = [] @@ -1692,7 +1681,7 @@ class Interpreter(InterpreterBase, HoldableObject): version_arg: T.Optional[str], version_func: T.Optional[ProgramVersionFunc], extra_info: T.List[mlog.TV_Loggable] - ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: + ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: progobj = self.program_from_overrides(args, extra_info) if progobj: return progobj @@ -1728,7 +1717,7 @@ class Interpreter(InterpreterBase, HoldableObject): return progobj - def check_program_version(self, progobj: T.Union[ExternalProgram, build.LocalProgram], + def check_program_version(self, progobj: T.Union[ExternalProgram, build.Executable, OverrideProgram], wanted: T.Union[str, T.List[str]], version_func: T.Optional[ProgramVersionFunc], extra_info: T.List[mlog.TV_Loggable]) -> bool: @@ -1755,7 +1744,7 @@ class Interpreter(InterpreterBase, HoldableObject): def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString], default_options: OptionDict, required: bool, extra_info: T.List[mlog.TV_Loggable] - ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: + ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', mlog.bold(' '.join(args))) sp_kwargs: kwtypes.DoSubproject = { @@ -1782,7 +1771,7 @@ class Interpreter(InterpreterBase, HoldableObject): @disablerIfNotFound def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: 'kwtypes.FindProgram', - ) -> T.Union[ExternalProgram, build.LocalProgram]: + ) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: assert feature, 'for mypy' @@ -1795,44 +1784,6 @@ class Interpreter(InterpreterBase, HoldableObject): silent=False, wanted=kwargs['version'], version_arg=kwargs['version_argument'], search_dirs=search_dirs) - @FeatureNew('local_program', '1.10.0') - @typed_pos_args('local_program', (str, mesonlib.File, build.Executable, build.CustomTarget, build.CustomTargetIndex)) - @typed_kwargs( - 'local_program', - DEPENDS_KW, - DEPEND_FILES_KW, - KwargInfo('interpreter', (ExternalProgram, NoneType), default=None), - ) - def func_local_program(self, node: mparser.BaseNode, args: T.Tuple[T.Union[mesonlib.FileOrString, build.Executable, build.CustomTarget, build.CustomTargetIndex]], - kwargs: kwtypes.LocalProgram) -> build.LocalProgram: - return self._local_program_impl(args[0], kwargs['depends'], kwargs['depend_files'], kwargs['interpreter']) - - def _local_program_impl(self, exe: T.Union[mesonlib.FileOrString, build.Executable, build.CustomTarget, build.CustomTargetIndex], - depends_: T.Optional[T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]]] = None, - depend_files_: T.Optional[T.List[mesonlib.FileOrString]] = None, - interpreter: T.Optional[ExternalProgram] = None) -> build.LocalProgram: - if isinstance(exe, build.CustomTarget): - if len(exe.outputs) != 1: - raise InvalidArguments('CustomTarget used as LocalProgram must have exactly one output.') - depends = [d.target if isinstance(d, build.CustomTargetIndex) else d for d in (depends_ or [])] - depend_files = self.source_strings_to_files(depend_files_ or []) - if isinstance(exe, (str, mesonlib.File)): - file = self.source_strings_to_files([exe])[0] - abspath = file.absolute_path(self.environment.source_dir, self.environment.build_dir) - prog = ExternalProgram(file.fname, command=[abspath], silent=True) - return build.LocalProgram(prog, self.project_version, depends, depend_files) - if interpreter: - if not isinstance(exe, (build.CustomTarget, build.CustomTargetIndex)): - raise InvalidArguments('The "interpreter" argument can only be used when the first argument is a custom target.') - if not interpreter.found(): - raise InvalidArguments(f'Specified interpreter program {interpreter.description()!r} not found.') - target = exe.target if isinstance(exe, build.CustomTargetIndex) else exe - depends.append(target) - cmd = interpreter.get_command() + [self.backend.get_target_filename(exe)] - prog = ExternalProgram(exe.name, command=cmd, silent=True) - return build.LocalProgram(prog, self.project_version, depends, depend_files) - return build.LocalProgram(exe, self.project_version, depends, depend_files) - # When adding kwargs, please check if they make sense in dependencies.get_dep_identifier() @FeatureNewKwargs('dependency', '0.57.0', ['cmake_package_version']) @FeatureNewKwargs('dependency', '0.56.0', ['allow_fallback']) @@ -2255,7 +2206,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.add_target(name, tg) return tg - @typed_pos_args('generator', (build.Executable, ExternalProgram, build.LocalProgram)) + @typed_pos_args('generator', (build.Executable, ExternalProgram)) @typed_kwargs( 'generator', KwargInfo('arguments', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), @@ -2265,7 +2216,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('capture', bool, default=False, since='0.43.0'), ) def func_generator(self, node: mparser.FunctionNode, - args: T.Tuple[T.Union[build.Executable, ExternalProgram, build.LocalProgram]], + args: T.Tuple[T.Union[build.Executable, ExternalProgram]], kwargs: 'kwtypes.FuncGenerator') -> build.Generator: for rule in kwargs['output']: if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: @@ -2279,17 +2230,17 @@ class Interpreter(InterpreterBase, HoldableObject): return build.Generator(args[0], **kwargs) - @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram)) + @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) @typed_kwargs('benchmark', *TEST_KWS) def func_benchmark(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.LocalProgram]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], kwargs: 'kwtypes.FuncBenchmark') -> None: self.add_test(node, args, kwargs, False) - @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram)) + @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) @typed_kwargs('test', *TEST_KWS, KwargInfo('is_parallel', bool, default=True)) def func_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], kwargs: 'kwtypes.FuncTest') -> None: self.add_test(node, args, kwargs, True) @@ -2303,7 +2254,7 @@ class Interpreter(InterpreterBase, HoldableObject): return ENV_KW.convertor(envlist) def make_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], kwargs: 'kwtypes.BaseTest', klass: T.Type[TestClass] = Test) -> TestClass: name = args[0] @@ -2312,20 +2263,15 @@ class Interpreter(InterpreterBase, HoldableObject): location=node) name = name.replace(':', '_') exe = args[1] - depends = list(kwargs['depends'] or []) - if isinstance(exe, build.LocalProgram): - # FIXME: tests does not have depend_files? - depends.extend(exe.depends) - exe = exe.program - elif isinstance(exe, ExternalProgram): + if isinstance(exe, ExternalProgram): if not exe.found(): raise InvalidArguments('Tried to use not-found external program as test exe') elif isinstance(exe, mesonlib.File): exe = self.find_program_impl([exe]) elif isinstance(exe, build.CustomTarget): - depends.append(exe) + kwargs.setdefault('depends', []).append(exe) elif isinstance(exe, build.CustomTargetIndex): - depends.append(exe.target) + kwargs.setdefault('depends', []).append(exe.target) env = self.unpack_env_kwarg(kwargs) @@ -2344,7 +2290,7 @@ class Interpreter(InterpreterBase, HoldableObject): prj, suite, exe, - depends, + kwargs['depends'], kwargs.get('is_parallel', False), kwargs['args'], env, @@ -2669,7 +2615,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('capture', bool, default=False, since='0.41.0'), KwargInfo( 'command', - (ContainerTypeInfo(list, (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram), allow_empty=False), NoneType), + (ContainerTypeInfo(list, (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str), allow_empty=False), NoneType), listify=True, ), KwargInfo( diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index addc7d3db..86e8957bc 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -23,7 +23,7 @@ from ..interpreterbase import ( flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode) from ..interpreter.type_checking import NoneType, ENV_KW, ENV_SEPARATOR_KW, PKGCONFIG_DEFINE_KW from ..dependencies import Dependency, ExternalLibrary, InternalDependency -from ..programs import ExternalProgram, BaseProgram +from ..programs import ExternalProgram from ..mesonlib import HoldableObject, listify, Popen_safe import typing as T @@ -605,10 +605,10 @@ class DependencyHolder(ObjectHolder[Dependency]): raise InterpreterException('as_shared method is only supported on declare_dependency() objects') return self.held_object.get_as_shared(kwargs['recursive']) -_BASEPROG = T.TypeVar('_BASEPROG', bound=BaseProgram) +_EXTPROG = T.TypeVar('_EXTPROG', bound=ExternalProgram) -class BaseProgramHolder(ObjectHolder[_BASEPROG]): - def __init__(self, ep: _BASEPROG, interpreter: 'Interpreter') -> None: +class _ExternalProgramHolder(ObjectHolder[_EXTPROG]): + def __init__(self, ep: _EXTPROG, interpreter: 'Interpreter') -> None: super().__init__(ep, interpreter) @noPosargs @@ -619,15 +619,15 @@ class BaseProgramHolder(ObjectHolder[_BASEPROG]): @noPosargs @noKwargs - @FeatureDeprecated('Program.path', '0.55.0', - 'use Program.full_path() instead') + @FeatureDeprecated('ExternalProgram.path', '0.55.0', + 'use ExternalProgram.full_path() instead') @InterpreterObject.method('path') def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() @noPosargs @noKwargs - @FeatureNew('Program.full_path', '0.55.0') + @FeatureNew('ExternalProgram.full_path', '0.55.0') @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() @@ -641,11 +641,9 @@ class BaseProgramHolder(ObjectHolder[_BASEPROG]): @noPosargs @noKwargs - @FeatureNew('Program.version', '0.62.0') + @FeatureNew('ExternalProgram.version', '0.62.0') @InterpreterObject.method('version') def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: - if isinstance(self.held_object, build.LocalProgram) and isinstance(self.held_object.program, build.Executable): - FeatureNew.single_use('Program.version with an executable', '1.9.0', subproject=self.subproject, location=self.current_node) if not self.found(): raise InterpreterException('Unable to get the version of a not-found external program') try: @@ -656,6 +654,8 @@ class BaseProgramHolder(ObjectHolder[_BASEPROG]): def found(self) -> bool: return self.held_object.found() +class ExternalProgramHolder(_ExternalProgramHolder[ExternalProgram]): + pass class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'): @@ -1163,3 +1163,11 @@ class StructuredSourcesHolder(ObjectHolder[build.StructuredSources]): def __init__(self, sources: build.StructuredSources, interp: 'Interpreter'): super().__init__(sources, interp) + +class OverrideExecutableHolder(BuildTargetHolder[build.OverrideExecutable]): + @noPosargs + @noKwargs + @FeatureNew('OverrideExecutable.version', '1.9.0') + @InterpreterObject.method('version') + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_version(self.interpreter) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 22dde255d..e95d47341 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -171,8 +171,7 @@ class FuncAddLanguages(ExtractRequired): class RunTarget(TypedDict): - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, - File, LocalProgram]] + command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, File]] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] env: EnvironmentVariables @@ -183,7 +182,7 @@ class CustomTarget(TypedDict): build_always_stale: T.Optional[bool] build_by_default: T.Optional[bool] capture: bool - command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, File, LocalProgram]] + command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, File]] console: bool depend_files: T.List[FileOrString] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] @@ -247,13 +246,6 @@ class FindProgram(ExtractRequired, ExtractSearchDirs): version: T.List[str] -class LocalProgram(TypedDict): - - depend_files: T.List[FileOrString] - depends: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]] - interpreter: T.Optional[ExternalProgram] - - class RunCommand(TypedDict): check: bool diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index 3afeb8647..d22d36bf0 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -14,7 +14,7 @@ from .. import mlog, coredata from ..mesonlib import MachineChoice from ..options import OptionKey -from ..programs import ExternalProgram +from ..programs import OverrideProgram, ExternalProgram from ..interpreter.type_checking import ENV_KW, ENV_METHOD_KW, ENV_SEPARATOR_KW, env_convertor_with_method from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, typed_pos_args, noArgsFlattening, noPosargs, noKwargs, @@ -57,23 +57,21 @@ class MesonMain(MesonInterpreterObject): self.interpreter = interpreter def _find_source_script( - self, name: str, prog: T.Union[str, mesonlib.File, build.Executable, ExternalProgram, build.LocalProgram], - args: T.List[str], allow_built_program: bool = False) -> 'ExecutableSerialisation': - largs: T.List[T.Union[str, build.Executable, ExternalProgram, build.LocalProgram]] = [] + self, name: str, prog: T.Union[str, mesonlib.File, build.Executable, ExternalProgram], + args: T.List[str]) -> 'ExecutableSerialisation': + largs: T.List[T.Union[str, build.Executable, ExternalProgram]] = [] if isinstance(prog, (build.Executable, ExternalProgram)): FeatureNew.single_use(f'Passing executable/found program object to script parameter of {name}', '0.55.0', self.subproject, location=self.current_node) - elif isinstance(prog, (str, mesonlib.File)): + largs.append(prog) + else: if isinstance(prog, mesonlib.File): FeatureNew.single_use(f'Passing file object to script parameter of {name}', '0.57.0', self.subproject, location=self.current_node) - prog = self.interpreter.find_program_impl([prog]) - - if isinstance(prog, build.LocalProgram) and not allow_built_program and not prog.run_program(): - self.interpreter._compiled_exe_error(prog) + found = self.interpreter.find_program_impl([prog]) + largs.append(found) - largs.append(prog) largs.extend(args) es = self.interpreter.backend.get_executable_serialisation(largs, verbose=True) es.subproject = self.interpreter.subproject @@ -118,7 +116,7 @@ class MesonMain(MesonInterpreterObject): @typed_pos_args( 'meson.add_install_script', - (str, mesonlib.File, build.Executable, ExternalProgram, build.LocalProgram), + (str, mesonlib.File, build.Executable, ExternalProgram), varargs=(str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, ExternalProgram) ) @typed_kwargs( @@ -134,7 +132,7 @@ class MesonMain(MesonInterpreterObject): T.List[T.Union[str, mesonlib.File, build.BuildTargetTypes, ExternalProgram]]], kwargs: 'AddInstallScriptKW') -> None: script_args = self._process_script_args('add_install_script', args[1]) - script = self._find_source_script('add_install_script', args[0], script_args, allow_built_program=True) + script = self._find_source_script('add_install_script', args[0], script_args) script.skip_if_destdir = kwargs['skip_if_destdir'] script.tag = kwargs['install_tag'] script.dry_run = kwargs['dry_run'] @@ -149,7 +147,7 @@ class MesonMain(MesonInterpreterObject): @InterpreterObject.method('add_postconf_script') def add_postconf_script_method( self, - args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram, build.LocalProgram], + args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], T.List[T.Union[str, mesonlib.File, ExternalProgram]]], kwargs: 'TYPE_kwargs') -> None: script_args = self._process_script_args('add_postconf_script', args[1]) @@ -166,7 +164,7 @@ class MesonMain(MesonInterpreterObject): @InterpreterObject.method('add_dist_script') def add_dist_script_method( self, - args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram, build.LocalProgram], + args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], T.List[T.Union[str, mesonlib.File, ExternalProgram]]], kwargs: 'TYPE_kwargs') -> None: if args[1]: @@ -314,13 +312,19 @@ class MesonMain(MesonInterpreterObject): self.build.dep_manifest_name = args[0] @FeatureNew('meson.override_find_program', '0.46.0') - @typed_pos_args('meson.override_find_program', str, (mesonlib.File, ExternalProgram, build.Executable, build.LocalProgram)) + @typed_pos_args('meson.override_find_program', str, (mesonlib.File, ExternalProgram, build.Executable)) @noKwargs @InterpreterObject.method('override_find_program') - def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable, build.LocalProgram]], kwargs: 'TYPE_kwargs') -> None: + def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable]], kwargs: 'TYPE_kwargs') -> None: name, exe = args - if not isinstance(exe, (ExternalProgram, build.LocalProgram)): - exe = self.interpreter._local_program_impl(exe) + if isinstance(exe, mesonlib.File): + abspath = exe.absolute_path(self.interpreter.environment.source_dir, + self.interpreter.environment.build_dir) + if not os.path.exists(abspath): + raise InterpreterException(f'Tried to override {name} with a file that does not exist.') + exe = OverrideProgram(name, self.interpreter.project_version, command=[abspath]) + elif isinstance(exe, build.Executable): + exe = build.OverrideExecutable(exe, self.interpreter.project_version) self.interpreter.add_find_program_override(name, exe) @typed_kwargs( diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index a54a69ff0..4f961bb60 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -10,8 +10,7 @@ import typing as T from .. import compilers from ..build import (CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, - BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources, - LocalProgram) + BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) from ..options import OptionKey, UserFeatureOption from ..dependencies import Dependency, InternalDependency from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo, FeatureBroken @@ -286,9 +285,9 @@ DEPEND_FILES_KW: KwargInfo[T.List[T.Union[str, File]]] = KwargInfo( default=[], ) -COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTargetTypes, ExternalProgram, File, LocalProgram]]] = KwargInfo( +COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTargetTypes, ExternalProgram, File]]] = KwargInfo( 'command', - ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File, LocalProgram), allow_empty=False), + ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File), allow_empty=False), required=True, listify=True, default=[], diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 5f088e9ed..87892e6d7 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,6 +18,7 @@ if T.TYPE_CHECKING: from ..interpreter import Interpreter from ..interpreter.interpreter import ProgramVersionFunc from ..interpreterbase import TYPE_var, TYPE_kwargs + from ..programs import OverrideProgram from ..dependencies import Dependency from ..options import ElementaryOptionValues @@ -74,14 +75,14 @@ class ModuleState: required: bool = True, version_func: T.Optional[ProgramVersionFunc] = None, wanted: T.Union[str, T.List[str]] = '', silent: bool = False, - for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.LocalProgram]: + for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.OverrideExecutable, OverrideProgram]: if not isinstance(prog, list): prog = [prog] return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted, silent=silent, for_machine=for_machine) def find_tool(self, name: str, depname: str, varname: str, required: bool = True, - wanted: T.Optional[str] = None) -> T.Union[ExternalProgram, build.LocalProgram]: + wanted: T.Optional[str] = None) -> T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: # Look in overrides in case it's built as subproject progobj = self._interpreter.program_from_overrides([name], []) if progobj is not None: @@ -117,7 +118,7 @@ class ModuleState: # implementations of meson functions anyway. return self._interpreter.func_dependency(self.current_node, [depname], kwargs) # type: ignore - def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, build.LocalProgram, mesonlib.File]], + def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, 'ExternalProgram', mesonlib.File]], workdir: T.Optional[str] = None, env: T.Union[T.List[str], T.Dict[str, str], str] = None, depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]] = None) -> None: diff --git a/mesonbuild/modules/_qt.py b/mesonbuild/modules/_qt.py index 0f6d2ecb2..675c174a7 100644 --- a/mesonbuild/modules/_qt.py +++ b/mesonbuild/modules/_qt.py @@ -208,7 +208,7 @@ class QtBaseModule(ExtensionModule): self.qt_version = qt_version # It is important that this list does not change order as the order of # the returned ExternalPrograms will change as well - self.tools: T.Dict[str, T.Union[ExternalProgram, build.LocalProgram]] = { + self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = { tool: NonExistingExternalProgram(tool) for tool in self._set_of_qt_tools } self.methods.update({ @@ -250,7 +250,7 @@ class QtBaseModule(ExtensionModule): arg = ['-v'] # Ensure that the version of qt and each tool are the same - def get_version(p: T.Union[ExternalProgram, build.LocalProgram]) -> str: + def get_version(p: T.Union[ExternalProgram, build.Executable]) -> str: _, out, err = Popen_safe(p.get_command() + arg) if name == 'lrelease' or not qt_dep.version.startswith('4'): care = out @@ -445,17 +445,12 @@ class QtBaseModule(ExtensionModule): for s in sources: qrc_deps.extend(self._parse_qrc_deps(state, s)) - cmd: T.List[T.Union[ExternalProgram, build.LocalProgram, str]] - cmd = [self.tools['rcc'], '-name', name, '-o', '@OUTPUT@'] - cmd.extend(extra_args) - cmd.append('@INPUT@') - cmd.extend(DEPFILE_ARGS) res_target = build.CustomTarget( name, state.subdir, state.subproject, state.environment, - cmd, + self.tools['rcc'].get_command() + ['-name', name, '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, sources, [f'{name}.cpp'], depend_files=qrc_deps, @@ -471,16 +466,12 @@ class QtBaseModule(ExtensionModule): else: basename = os.path.basename(rcc_file.fname) name = f'qt{self.qt_version}-{basename.replace(".", "_")}' - cmd = [self.tools['rcc'], '-name', '@BASENAME@', '-o', '@OUTPUT@'] - cmd.extend(extra_args) - cmd.append('@INPUT@') - cmd.extend(DEPFILE_ARGS) res_target = build.CustomTarget( name, state.subdir, state.subproject, state.environment, - cmd, + self.tools['rcc'].get_command() + ['-name', '@BASENAME@', '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, [rcc_file], [f'{name}.cpp'], depend_files=qrc_deps, @@ -733,7 +724,7 @@ class QtBaseModule(ExtensionModule): ts = os.path.basename(ts) else: outdir = state.subdir - cmd: T.List[T.Union[ExternalProgram, build.LocalProgram, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] + cmd: T.List[T.Union[ExternalProgram, build.Executable, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] lrelease_target = build.CustomTarget( f'qt{self.qt_version}-compile-{ts}', outdir, @@ -873,15 +864,12 @@ class QtBaseModule(ExtensionModule): input_args.append(f'@INPUT{input_counter}@') input_counter += 1 - cmd: T.List[T.Union[ExternalProgram, build.LocalProgram, str]] - cmd = [self.tools['moc'], '--collect-json', '-o', '@OUTPUT@'] - cmd.extend(input_args) return build.CustomTarget( f'moc_collect_json_{target_name}', state.subdir, state.subproject, state.environment, - cmd, + self.tools['moc'].get_command() + ['--collect-json', '-o', '@OUTPUT@'] + input_args, moc_json, [f'{target_name}_json_collect.json'], description=f'Collecting json type information for {target_name}', @@ -920,17 +908,12 @@ class QtBaseModule(ExtensionModule): ressource_path = os.path.join('/', kwargs['module_prefix'], source_basename) cachegen_inputs.append(ressource_path) - cmd: T.List[T.Union[ExternalProgram, build.LocalProgram, str]] - cmd = [self.tools['qmlcachegen'], '-o', '@OUTPUT@', '--resource-name', f'qmlcache_{target_name}'] - cmd.extend(kwargs['extra_args']) - cmd.append('--resource=@INPUT@') - cmd.extend(cachegen_inputs) cacheloader_target = build.CustomTarget( f'cacheloader_{target_name}', state.subdir, state.subproject, state.environment, - cmd, + self.tools['qmlcachegen'].get_command() + ['-o', '@OUTPUT@'] + ['--resource-name', f'qmlcache_{target_name}'] + kwargs['extra_args'] + ['--resource=@INPUT@'] + cachegen_inputs, [kwargs['qml_qrc']], #output name format matters here [f'{target_name}_qmlcache_loader.cpp'], @@ -958,12 +941,11 @@ class QtBaseModule(ExtensionModule): install_dir: T.List[T.Union[str, Literal[False]]] = [False] install_tag: T.List[T.Union[str, None]] = [None] - cmd = [ - self.tools['qmltyperegistrar'], + cmd = self.tools['qmltyperegistrar'].get_command() + [ '--import-name', import_name, '--major-version', major_version, '--minor-version', minor_version, - '-o', '@OUTPUT0@' + '-o', '@OUTPUT0@', ] cmd.extend(kwargs['extra_args']) diff --git a/mesonbuild/modules/dlang.py b/mesonbuild/modules/dlang.py index 860a62481..35ce86be8 100644 --- a/mesonbuild/modules/dlang.py +++ b/mesonbuild/modules/dlang.py @@ -12,7 +12,7 @@ import typing as T from . import ExtensionModule, ModuleInfo from .. import mlog -from ..build import InvalidArguments, LocalProgram +from ..build import InvalidArguments from ..dependencies import Dependency from ..dependencies.dub import DubDependency from ..interpreterbase import typed_pos_args @@ -22,11 +22,12 @@ if T.TYPE_CHECKING: from typing_extensions import Literal, TypeAlias from . import ModuleState + from ..build import OverrideExecutable from ..interpreter.interpreter import Interpreter from ..interpreterbase.baseobjects import TYPE_kwargs - from ..programs import ExternalProgram + from ..programs import ExternalProgram, OverrideProgram - _AnyProgram: TypeAlias = T.Union[ExternalProgram, LocalProgram] + _AnyProgram: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram] _JSONTypes: TypeAlias = T.Union[str, int, bool, None, T.List['_JSONTypes'], T.Dict[str, '_JSONTypes']] diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 6219e4ce0..9e525601e 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -22,7 +22,7 @@ from .. import build from .. import interpreter from .. import mesonlib from .. import mlog -from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, LocalProgram +from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments from ..dependencies import Dependency, InternalDependency from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, ENV_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, DEPENDENCY_SOURCES_KW, in_set_validator @@ -33,6 +33,7 @@ from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, quote_arg ) from ..options import OptionKey +from ..programs import OverrideProgram from ..scripts.gettext import read_linguas if T.TYPE_CHECKING: @@ -197,7 +198,7 @@ if T.TYPE_CHECKING: vtail: T.Optional[str] depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] - ToolType: TypeAlias = T.Union[ExternalProgram, LocalProgram] + ToolType: TypeAlias = T.Union[Executable, ExternalProgram, OverrideProgram] # Differs from the CustomTarget version in that it straight defaults to True @@ -788,7 +789,8 @@ class GnomeModule(ExtensionModule): if self.devenv is not None: b.devenv.append(self.devenv) - def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, ToolType, ToolType]: + def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[Executable, 'ExternalProgram', 'OverrideProgram'], + T.Union[Executable, 'ExternalProgram', 'OverrideProgram']]: if not self.gir_dep: self.gir_dep = state.dependency('gobject-introspection-1.0') self.giscanner = self._find_tool(state, 'g-ir-scanner') @@ -808,7 +810,7 @@ class GnomeModule(ExtensionModule): @functools.lru_cache(maxsize=None) def _gir_has_option(self, option: str) -> bool: exe = self.giscanner - if isinstance(exe, LocalProgram): + if isinstance(exe, (Executable, OverrideProgram)): # Handle overridden g-ir-scanner assert option in {'--extra-library', '--sources-top-dirs'} return True @@ -969,7 +971,7 @@ class GnomeModule(ExtensionModule): self, state: 'ModuleState', girfile: str, - scan_command: T.Sequence[T.Union['FileOrString', Executable, ToolType]], + scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]], generated_files: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes', build.StructuredSources]], env_flags: T.Sequence[str], @@ -1018,7 +1020,7 @@ class GnomeModule(ExtensionModule): @staticmethod def _make_typelib_target(state: 'ModuleState', typelib_output: str, - typelib_cmd: T.Sequence[T.Union[str, CustomTarget, ToolType]], + typelib_cmd: T.Sequence[T.Union[str, Executable, ExternalProgram, CustomTarget]], generated_files: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], kwargs: T.Dict[str, T.Any]) -> TypelibTarget: install = kwargs['install_typelib'] @@ -1192,7 +1194,7 @@ class GnomeModule(ExtensionModule): gir_inc_dirs: T.List[str] = [] - scan_command: T.List[T.Union[str, ToolType, Executable]] = [giscanner] + scan_command: T.List[T.Union[str, Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner] scan_command += ['--quiet'] scan_command += ['--no-libtool'] scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion] @@ -1345,7 +1347,7 @@ class GnomeModule(ExtensionModule): pot_file = os.path.join('@SOURCE_ROOT@', state.subdir, 'C', project_id + '.pot') pot_sources = [os.path.join('@SOURCE_ROOT@', state.subdir, 'C', s) for s in sources] - pot_args: T.List[T.Union[ToolType, str]] = [itstool, '-o', pot_file] + pot_args: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] = [itstool, '-o', pot_file] pot_args.extend(pot_sources) pottarget = build.RunTarget(f'help-{project_id}-pot', pot_args, [], os.path.join(state.subdir, 'C'), state.subproject, @@ -1377,7 +1379,7 @@ class GnomeModule(ExtensionModule): targets.append(l_data) po_file = l + '.po' - po_args: T.List[T.Union[ToolType, str]] = [ + po_args: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] = [ msgmerge, '-q', '-o', os.path.join('@SOURCE_ROOT@', l_subdir, po_file), os.path.join('@SOURCE_ROOT@', l_subdir, po_file), pot_file] @@ -2240,7 +2242,7 @@ class GnomeModule(ExtensionModule): build_dir = os.path.join(state.environment.get_build_dir(), state.subdir) source_dir = os.path.join(state.environment.get_source_dir(), state.subdir) pkg_cmd, vapi_depends, vapi_packages, vapi_includes, packages = self._extract_vapi_packages(state, kwargs['packages']) - cmd: T.List[T.Union[ToolType, str]] + cmd: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] cmd = [state.find_program('vapigen'), '--quiet', f'--library={library}', f'--directory={build_dir}'] cmd.extend([f'--vapidir={d}' for d in kwargs['vapi_dirs']]) cmd.extend([f'--metadatadir={d}' for d in kwargs['metadata_dirs']]) diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 06e891714..2d8d04d3e 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -259,7 +259,7 @@ class I18nModule(ExtensionModule): 'itstool_join': self.itstool_join, 'xgettext': self.xgettext, }) - self.tools: T.Dict[str, T.Optional[T.Union[ExternalProgram, build.LocalProgram]]] = { + self.tools: T.Dict[str, T.Optional[T.Union[ExternalProgram, build.Executable]]] = { 'itstool': None, 'msgfmt': None, 'msginit': None, diff --git a/mesonbuild/modules/icestorm.py b/mesonbuild/modules/icestorm.py index 86af78d8b..18bf0e202 100644 --- a/mesonbuild/modules/icestorm.py +++ b/mesonbuild/modules/icestorm.py @@ -29,7 +29,7 @@ class IceStormModule(ExtensionModule): def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) - self.tools: T.Dict[str, T.Union[ExternalProgram, build.LocalProgram]] = {} + self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = {} self.methods.update({ 'project': self.project, }) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index c637e5f5e..6f5a63a0b 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -15,7 +15,7 @@ from ..dependencies import NotFoundDependency from ..dependencies.detect import get_dep_identifier, find_external_dependency from ..dependencies.python import BasicPythonExternalProgram, python_factory, _PythonDependencyBase from ..interpreter import extract_required_kwarg, permitted_dependency_kwargs, primitives as P_OBJ -from ..interpreter.interpreterobjects import BaseProgramHolder +from ..interpreter.interpreterobjects import _ExternalProgramHolder from ..interpreter.type_checking import NoneType, DEPENDENCY_KWS, PRESERVE_PATH_KW, SHARED_MOD_KWS from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo, @@ -109,9 +109,9 @@ _SUBDIR_KW = KwargInfo('subdir', str, default='') _LIMITED_API_KW = KwargInfo('limited_api', str, default='', since='1.3.0') _DEFAULTABLE_SUBDIR_KW = KwargInfo('subdir', (str, NoneType)) -class PythonInstallation(BaseProgramHolder['PythonExternalProgram']): +class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'): - BaseProgramHolder.__init__(self, python, interpreter) + _ExternalProgramHolder.__init__(self, python, interpreter) info = python.info prefix = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('prefix')) assert isinstance(prefix, str), 'for mypy' diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 73094f571..89c20230b 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -13,7 +13,7 @@ from mesonbuild.interpreterbase.decorators import FeatureNew from . import ExtensionModule, ModuleReturnValue, ModuleInfo from .. import mesonlib, mlog from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, - CustomTarget, InvalidArguments, Jar, LocalProgram, StructuredSources, SharedLibrary, StaticLibrary) + CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary) from ..compilers.compilers import are_asserts_disabled_for_subproject, lang_suffixes from ..interpreter.type_checking import ( DEPENDENCIES_KW, LINK_WITH_KW, LINK_WHOLE_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS, @@ -33,6 +33,7 @@ if T.TYPE_CHECKING: from ..interpreter import kwargs as _kwargs from ..interpreter.interpreter import SourceInputs, SourceOutputs from ..interpreter.interpreterobjects import Test + from ..programs import OverrideProgram from ..interpreter.type_checking import SourcesVarargsType from typing_extensions import TypedDict, Literal @@ -90,7 +91,7 @@ class RustModule(ExtensionModule): def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) - self._bindgen_bin: T.Optional[T.Union[ExternalProgram, LocalProgram]] = None + self._bindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None if 'rust' in interpreter.compilers.host: rustc = T.cast('RustCompiler', interpreter.compilers.host['rust']) self._bindgen_rust_target = 'nightly' if rustc.is_nightly else rustc.version @@ -374,7 +375,10 @@ class RustModule(ExtensionModule): if self._bindgen_bin is None: self._bindgen_bin = state.find_program('bindgen', wanted=kwargs['bindgen_version']) if self._bindgen_rust_target is not None: - _, _, err = mesonlib.Popen_safe(self._bindgen_bin.get_command() + ['--rust-target', self._bindgen_rust_target]) + # ExternalCommand.command's type is bonkers + _, _, err = mesonlib.Popen_safe( + T.cast('T.List[str]', self._bindgen_bin.get_command()) + + ['--rust-target', self._bindgen_rust_target]) # < 0.71: Sometimes this is "invalid Rust target" and # sometimes "invalid # rust target" # >= 0.71: error: invalid value '...' for '--rust-target <RUST_TARGET>': "..." is not a valid Rust target, accepted values are of the form ... @@ -382,7 +386,9 @@ class RustModule(ExtensionModule): if 'Got an invalid' in err or 'is not a valid Rust target' in err: self._bindgen_rust_target = None - self._bindgen_set_std = mesonlib.version_compare(self._bindgen_bin.get_version(), '>= 0.71') + # TODO: Executable needs to learn about get_version + if isinstance(self._bindgen_bin, ExternalProgram): + self._bindgen_set_std = mesonlib.version_compare(self._bindgen_bin.get_version(), '>= 0.71') name: str if isinstance(header, File): diff --git a/mesonbuild/modules/wayland.py b/mesonbuild/modules/wayland.py index 675a6d974..94c6f819d 100644 --- a/mesonbuild/modules/wayland.py +++ b/mesonbuild/modules/wayland.py @@ -6,7 +6,7 @@ import os import typing as T from . import ExtensionModule, ModuleReturnValue, ModuleInfo -from ..build import CustomTarget, LocalProgram +from ..build import CustomTarget from ..interpreter.type_checking import NoneType, in_set_validator from ..interpreterbase import typed_pos_args, typed_kwargs, KwargInfo, FeatureNew from ..mesonlib import File, MesonException @@ -15,6 +15,7 @@ if T.TYPE_CHECKING: from typing_extensions import Literal, TypedDict from . import ModuleState + from ..build import Executable from ..dependencies import Dependency from ..interpreter import Interpreter from ..programs import ExternalProgram @@ -41,7 +42,7 @@ class WaylandModule(ExtensionModule): self.protocols_dep: T.Optional[Dependency] = None self.pkgdatadir: T.Optional[str] = None - self.scanner_bin: T.Optional[T.Union[ExternalProgram, LocalProgram]] = None + self.scanner_bin: T.Optional[T.Union[ExternalProgram, Executable]] = None self.methods.update({ 'scan_xml': self.scan_xml, diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index 16c12c85a..0abf09720 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -13,7 +13,6 @@ import sys import re import typing as T from pathlib import Path -from abc import ABCMeta, abstractmethod from . import mesonlib from . import mlog @@ -24,33 +23,7 @@ if T.TYPE_CHECKING: from .interpreter import Interpreter -class BaseProgram(mesonlib.HoldableObject, metaclass=ABCMeta): - ''' A base class for LocalProgram and ExternalProgram.''' - - name: str - - @abstractmethod - def found(self) -> bool: - pass - - @abstractmethod - def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str: - pass - - @abstractmethod - def get_command(self) -> T.List[str]: - pass - - @abstractmethod - def get_path(self) -> T.Optional[str]: - pass - - @abstractmethod - def description(self) -> str: - '''Human friendly description of the command''' - - -class ExternalProgram(BaseProgram): +class ExternalProgram(mesonlib.HoldableObject): """A program that is found on the system. :param name: The name of the program @@ -384,6 +357,17 @@ class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-i return False +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_dirs: T.Optional[T.List[T.Optional[str]]] = None, + exclude_paths: T.Optional[T.List[str]] = None): + super().__init__(name, command=command, silent=silent, + search_dirs=search_dirs, exclude_paths=exclude_paths) + self.cached_version = version + 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, diff --git a/test cases/common/285 local program/meson.build b/test cases/common/285 local program/meson.build deleted file mode 100644 index fba59e8e5..000000000 --- a/test cases/common/285 local program/meson.build +++ /dev/null @@ -1,56 +0,0 @@ -project('local program', version: '2.0') - -python3 = find_program('python3') - -# A module imported by prog but only available at build time. -pymod = custom_target( - input: 'pymod.py.in', - output: 'pymod.py', - command: [python3, '-c', 'import shutil,sys;shutil.copy(sys.argv[1], sys.argv[2])', '@INPUT@', '@OUTPUT@'], - build_by_default: false, -) - -# Copy into builddir to have the same location as pymod.py -prog = configure_file( - input: 'prog.py', - output: 'prog.py', - copy: true, -) - -# Without the dependency it can't be run, but it should have the project version. -prog1 = local_program(prog) -assert(prog1.version() == '2.0') -assert(prog1.found()) - -prog2 = local_program(prog, depends: pymod) -assert(prog2.version() == '2.0') -assert(prog2.found()) - -meson.override_find_program('prog', prog2) -prog3 = find_program('prog') -assert(prog3.version() == '2.0') -assert(prog3.found()) - -# This should have the pymod dependency -custom_target( - output: 'out.txt', - command: [prog3], - capture: true, - build_by_default: true, -) - -test('test-prog3', prog3) - -# Custom target as local program. Meson cannot parse the shebang at configure time, -# so we need to specify it otherwise it won't run on Windows. -prog_ct = custom_target( - input: 'prog.py', - output: 'prog-ct.py', - command: [python3, '-c', 'import shutil,sys;shutil.copy(sys.argv[1], sys.argv[2])', '@INPUT@', '@OUTPUT@'], - depends: pymod, -) -meson.override_find_program('prog4', local_program(prog_ct, interpreter: python3)) -prog4 = find_program('prog4') -assert(prog4.version() == '2.0') -assert(prog4.found()) -test('test-prog4', prog4) diff --git a/test cases/common/285 local program/prog.py b/test cases/common/285 local program/prog.py deleted file mode 100755 index bd1042a2f..000000000 --- a/test cases/common/285 local program/prog.py +++ /dev/null @@ -1,5 +0,0 @@ -#! /usr/bin/env python3 - -import pymod - -raise SystemExit(pymod.foo()) diff --git a/test cases/common/285 local program/pymod.py.in b/test cases/common/285 local program/pymod.py.in deleted file mode 100644 index 3cc1da0c3..000000000 --- a/test cases/common/285 local program/pymod.py.in +++ /dev/null @@ -1,2 +0,0 @@ -def foo() -> None: - return 0 |
