diff options
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 |
