diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim index 905e9fe64fa6..c4181c3f1dd5 100644 --- a/data/syntax-highlighting/vim/syntax/meson.vim +++ b/data/syntax-highlighting/vim/syntax/meson.vim @@ -93,6 +93,7 @@ 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 new file mode 100644 index 000000000000..6ca092873de4 --- /dev/null +++ b/docs/markdown/snippets/local_program.md @@ -0,0 +1,17 @@ +## 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. + +It is also now possible to pass a [[@custom_tgt]] or [[@custom_idx]] directly +to [[meson.override_find_program]], but be aware that an interpreter could be +required on Windows in which case [[local_program]] should be used. diff --git a/docs/yaml/builtins/meson.yaml b/docs/yaml/builtins/meson.yaml index eac83685bb8b..0234eadbb68d 100644 --- a/docs/yaml/builtins/meson.yaml +++ b/docs/yaml/builtins/meson.yaml @@ -383,6 +383,10 @@ methods: check is passed to [[find_program]] for a program that has been overridden with an executable, the current project version is used. + *(since 1.10.0)* It is also possible to override with a [[@custom_tgt]] or + [[@custom_idx]]. In that case, be aware that an interpreter could be required + on Windows in which case [[local_program]] should be used. + posargs: progname: type: str diff --git a/docs/yaml/functions/local_program.yaml b/docs/yaml/functions/local_program.yaml new file mode 100644 index 000000000000..3e6671c98b1e --- /dev/null +++ b/docs/yaml/functions/local_program.yaml @@ -0,0 +1,45 @@ +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 | array[external_program | str] + 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. + If arguments are needed for the interpreter, an array can be passed where + the first element is the interpreter program and the rest are strings. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 2f82a4a84ecd..17ca15dd7e96 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -200,6 +200,7 @@ def __init__(self, source_root: str, subdir: str, subproject: SubProject, subpro '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 19f856b68cfd..3cdf384308e3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -528,7 +528,7 @@ def determine_swift_dep_dirs(self, target: build.BuildTarget) -> T.List[str]: return result def get_executable_serialisation( - self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]], + self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, File, str, build.LocalProgram]], workdir: T.Optional[str] = None, extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, capture: T.Optional[str] = None, @@ -541,13 +541,15 @@ def get_executable_serialisation( # 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): + elif isinstance(exe, (build.CustomTarget, build.CustomTargetIndex)): # 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. @@ -564,9 +566,11 @@ def get_executable_serialisation( 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)): + elif isinstance(c, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): 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 cf4f31a1af89..862ad4bebdef 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -363,7 +363,7 @@ def __init__(self, environment: Environment): self.stdlibs = PerMachine({}, {}) self.test_setups: T.Dict[str, TestSetup] = {} self.test_setup_default_name = None - self.find_overrides: T.Dict[str, T.Union['OverrideExecutable', programs.ExternalProgram, programs.OverrideProgram]] = {} + self.find_overrides: T.Dict[str, T.Union[programs.ExternalProgram, LocalProgram]] = {} 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 @@ -2001,7 +2001,7 @@ def __str__(self) -> str: class Generator(HoldableObject): def __init__(self, env: Environment, - exe: T.Union['Executable', programs.ExternalProgram], + exe: T.Union[Executable, programs.ExternalProgram, LocalProgram, CustomTarget, CustomTargetIndex], arguments: T.List[str], output: T.List[str], # how2dataclass @@ -2011,10 +2011,14 @@ def __init__(self, env: Environment, depends: T.Optional[T.List[BuildTargetTypes]] = None, name: str = 'Generator'): self.environment = env + 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 @@ -2023,7 +2027,7 @@ def __repr__(self) -> str: repr_str = "<{0}: {1}>" return repr_str.format(self.__class__.__name__, self.exe) - def get_exe(self) -> T.Union['Executable', programs.ExternalProgram]: + def get_exe(self) -> T.Union[Executable, programs.ExternalProgram, CustomTarget, CustomTargetIndex]: return self.exe def get_base_outnames(self, inname: str) -> T.List[str]: @@ -2263,10 +2267,6 @@ def process_kwargs(self, kwargs: ExecutableKeywordArguments) -> None: 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" @@ -2289,21 +2289,6 @@ def get_debug_filename(self) -> T.Optional[str]: 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 @@ -2807,11 +2792,15 @@ 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]]) -> \ + def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes, LocalProgram]]) -> \ 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): @@ -2877,7 +2866,7 @@ def __init__(self, environment: Environment, command: T.Sequence[T.Union[ str, BuildTargetTypes, GeneratedList, - programs.ExternalProgram, File]], + programs.ExternalProgram, File, LocalProgram]], sources: T.Sequence[T.Union[ str, File, BuildTargetTypes, ExtractedObjects, GeneratedList, programs.ExternalProgram]], @@ -3137,7 +3126,7 @@ class RunTarget(Target, CommandBase): typename = 'run' def __init__(self, name: str, - command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram]], + command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram, LocalProgram]], dependencies: T.Sequence[AnyTargetType], subdir: str, subproject: str, @@ -3360,17 +3349,46 @@ def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: def keys(self) -> T.Iterator[str]: return self.values.keys() -class OverrideExecutable(Executable): - def __init__(self, executable: Executable, version: str): - self._executable = executable - self._version = version +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 - def __getattr__(self, name: str) -> T.Any: - _executable = object.__getattribute__(self, '_executable') - return getattr(_executable, name) + def found(self) -> bool: + return True def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str: - return self._version + 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 # 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 e2ccce479093..5186b842bcbb 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -20,7 +20,6 @@ 'SubprojectHolder', 'DependencyHolder', 'GeneratedListHolder', - 'ExternalProgramHolder', 'extract_required_kwarg', 'ArrayHolder', @@ -35,8 +34,7 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, CustomTargetIndexHolder, MachineHolder, Test, ConfigurationDataHolder, SubprojectHolder, DependencyHolder, - GeneratedListHolder, ExternalProgramHolder, - extract_required_kwarg) + GeneratedListHolder, extract_required_kwarg) from .primitives import ( ArrayHolder, diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d3a9142fea71..d8db5b2af5ab 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -21,7 +21,7 @@ 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 +from ..programs import ExternalProgram, NonExistingExternalProgram, BaseProgram from ..dependencies import Dependency from ..depfile import DepFile from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args @@ -118,7 +118,6 @@ 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 @@ -130,7 +129,7 @@ BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources] - ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str] + ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.LocalProgram]], str] TestClass = T.TypeVar('TestClass', bound=Test) @@ -369,6 +368,7 @@ def build_func_dict(self) -> None: '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,7 +440,6 @@ def build_holder_map(self) -> None: 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, @@ -470,7 +469,7 @@ def build_holder_map(self) -> None: ''' self.bound_holder_map.update({ dependencies.Dependency: OBJ.DependencyHolder, - ExternalProgram: OBJ.ExternalProgramHolder, + BaseProgram: OBJ.BaseProgramHolder, compilers.Compiler: compilerOBJ.CompilerHolder, ModuleObject: OBJ.ModuleObjectHolder, MutableModuleObject: OBJ.MutableModuleObjectHolder, @@ -769,8 +768,8 @@ def validate_arguments(self, args, argcount, arg_types): # better error messages when overridden @typed_pos_args( 'run_command', - (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str), - varargs=(build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str)) + (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram), + varargs=(build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram)) @typed_kwargs( 'run_command', KwargInfo('check', (bool, NoneType), since='0.47.0'), @@ -778,14 +777,21 @@ def validate_arguments(self, args, argcount, arg_types): 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], - T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]], + 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]]], 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], - T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]], + 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]]], kwargs: 'kwtypes.RunCommand', in_builddir: bool = False) -> RunProcess: cmd, cargs = args @@ -799,19 +805,17 @@ def run_command_impl(self, 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.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 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 not cmd.found(): raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable') elif isinstance(cmd, compilers.Compiler): @@ -843,8 +847,15 @@ def run_command_impl(self, 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: - raise InterpreterException(overridden_msg.format(a.name, cmd.description())) + self._compiled_exe_error(a) # If any file that was used as an argument to the command # changes, we must re-run the configuration step. @@ -1609,7 +1620,7 @@ def program_from_system(self, args: T.List[mesonlib.FileOrString], search_dirs: def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString], extra_info: T.List['mlog.TV_Loggable'] - ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.OverrideExecutable]]: + ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: for name in command_names: if not isinstance(name, str): continue @@ -1624,7 +1635,7 @@ def store_name_lookups(self, command_names: T.List[mesonlib.FileOrString]) -> No if isinstance(name, str): self.build.searched_programs.add(name) - def add_find_program_override(self, name: str, exe: T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']) -> None: + def add_find_program_override(self, name: str, exe: T.Union[ExternalProgram, build.LocalProgram]) -> 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: @@ -1649,7 +1660,7 @@ def find_program_impl(self, args: T.List[mesonlib.FileOrString], search_dirs: T.Optional[T.List[str]] = None, version_arg: T.Optional[str] = '', version_func: T.Optional[ProgramVersionFunc] = None - ) -> T.Union['ExternalProgram', 'build.OverrideExecutable', 'OverrideProgram']: + ) -> T.Union[ExternalProgram, build.LocalProgram]: args = mesonlib.listify(args) extra_info: T.List[mlog.TV_Loggable] = [] @@ -1681,7 +1692,7 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi version_arg: T.Optional[str], version_func: T.Optional[ProgramVersionFunc], extra_info: T.List[mlog.TV_Loggable] - ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: + ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: progobj = self.program_from_overrides(args, extra_info) if progobj: return progobj @@ -1717,7 +1728,7 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi return progobj - def check_program_version(self, progobj: T.Union[ExternalProgram, build.Executable, OverrideProgram], + def check_program_version(self, progobj: T.Union[ExternalProgram, build.LocalProgram], wanted: T.Union[str, T.List[str]], version_func: T.Optional[ProgramVersionFunc], extra_info: T.List[mlog.TV_Loggable]) -> bool: @@ -1744,7 +1755,7 @@ def check_program_version(self, progobj: T.Union[ExternalProgram, build.Executab 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.Executable, OverrideProgram]]: + ) -> T.Optional[T.Union[ExternalProgram, build.LocalProgram]]: mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', mlog.bold(' '.join(args))) sp_kwargs: kwtypes.DoSubproject = { @@ -1771,7 +1782,7 @@ def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrStrin @disablerIfNotFound def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: 'kwtypes.FindProgram', - ) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: + ) -> T.Union[ExternalProgram, build.LocalProgram]: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: assert feature, 'for mypy' @@ -1784,6 +1795,50 @@ def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonli 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', (NoneType, ExternalProgram, ContainerTypeInfo(list, (ExternalProgram, str))), 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[T.Union[ExternalProgram, T.List[T.Union[ExternalProgram, str]]]] = 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 isinstance(interpreter, list): + interpreter, args = interpreter[0], interpreter[1:] + if not isinstance(interpreter, ExternalProgram) or any(not isinstance(a, str) for a in args): + raise InvalidArguments('Interpreter must be an ExternalProgram followed by strings.') + else: + args = [] + 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() + args + [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']) @@ -2206,7 +2261,7 @@ def func_alias_target(self, node: mparser.BaseNode, args: T.Tuple[str, T.List[T. self.add_target(name, tg) return tg - @typed_pos_args('generator', (build.Executable, ExternalProgram)) + @typed_pos_args('generator', (build.Executable, ExternalProgram, build.LocalProgram)) @typed_kwargs( 'generator', KwargInfo('arguments', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), @@ -2216,7 +2271,7 @@ def func_alias_target(self, node: mparser.BaseNode, args: T.Tuple[str, T.List[T. KwargInfo('capture', bool, default=False, since='0.43.0'), ) def func_generator(self, node: mparser.FunctionNode, - args: T.Tuple[T.Union[build.Executable, ExternalProgram]], + args: T.Tuple[T.Union[build.Executable, ExternalProgram, build.LocalProgram]], kwargs: 'kwtypes.FuncGenerator') -> build.Generator: for rule in kwargs['output']: if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: @@ -2230,17 +2285,17 @@ def func_generator(self, node: mparser.FunctionNode, return build.Generator(self.environment, args[0], **kwargs) - @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) + @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram)) @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]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.LocalProgram]], 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)) + @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram)) @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]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram]], kwargs: 'kwtypes.FuncTest') -> None: self.add_test(node, args, kwargs, True) @@ -2254,7 +2309,7 @@ def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariables, T.Dict[str, 'TY 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]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.LocalProgram]], kwargs: 'kwtypes.BaseTest', klass: T.Type[TestClass] = Test) -> TestClass: name = args[0] @@ -2263,15 +2318,20 @@ def make_test(self, node: mparser.BaseNode, location=node) name = name.replace(':', '_') exe = args[1] - if isinstance(exe, ExternalProgram): + 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 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): - kwargs.setdefault('depends', []).append(exe) + depends.append(exe) elif isinstance(exe, build.CustomTargetIndex): - kwargs.setdefault('depends', []).append(exe.target) + depends.append(exe.target) env = self.unpack_env_kwarg(kwargs) @@ -2290,7 +2350,7 @@ def make_test(self, node: mparser.BaseNode, prj, suite, exe, - kwargs['depends'], + depends, kwargs.get('is_parallel', False), kwargs['args'], env, @@ -2615,7 +2675,7 @@ def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str], KwargInfo('capture', bool, default=False, since='0.41.0'), KwargInfo( 'command', - (ContainerTypeInfo(list, (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str), allow_empty=False), NoneType), + (ContainerTypeInfo(list, (build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str, build.LocalProgram), allow_empty=False), NoneType), listify=True, ), KwargInfo( diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index d5a78c10ee65..306e082d1f52 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -23,7 +23,7 @@ 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 +from ..programs import ExternalProgram, BaseProgram from ..mesonlib import HoldableObject, listify, Popen_safe import typing as T @@ -605,10 +605,10 @@ def as_shared_method(self, args: T.List[TYPE_var], kwargs: InternalDependencyAsK raise InterpreterException('as_shared method is only supported on declare_dependency() objects') return self.held_object.get_as_shared(kwargs['recursive']) -_EXTPROG = T.TypeVar('_EXTPROG', bound=ExternalProgram) +_BASEPROG = T.TypeVar('_BASEPROG', bound=BaseProgram) -class _ExternalProgramHolder(ObjectHolder[_EXTPROG]): - def __init__(self, ep: _EXTPROG, interpreter: 'Interpreter') -> None: +class BaseProgramHolder(ObjectHolder[_BASEPROG]): + def __init__(self, ep: _BASEPROG, interpreter: 'Interpreter') -> None: super().__init__(ep, interpreter) @noPosargs @@ -619,15 +619,15 @@ def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: @noPosargs @noKwargs - @FeatureDeprecated('ExternalProgram.path', '0.55.0', - 'use ExternalProgram.full_path() instead') + @FeatureDeprecated('Program.path', '0.55.0', + 'use Program.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('ExternalProgram.full_path', '0.55.0') + @FeatureNew('Program.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,9 +641,11 @@ def _full_path(self) -> str: @noPosargs @noKwargs - @FeatureNew('ExternalProgram.version', '0.62.0') + @FeatureNew('Program.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: @@ -654,8 +656,6 @@ def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 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,11 +1163,3 @@ 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 e95d4734133e..22dde255d736 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -171,7 +171,8 @@ class FuncAddLanguages(ExtractRequired): class RunTarget(TypedDict): - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, File]] + command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, + File, LocalProgram]] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] env: EnvironmentVariables @@ -182,7 +183,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]] + command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, File, LocalProgram]] console: bool depend_files: T.List[FileOrString] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] @@ -246,6 +247,13 @@ 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 d22d36bf0613..32043714ba0a 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -14,7 +14,7 @@ from ..mesonlib import MachineChoice from ..options import OptionKey -from ..programs import OverrideProgram, ExternalProgram +from ..programs import 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,21 +57,23 @@ def __init__(self, build: 'build.Build', interpreter: 'Interpreter'): self.interpreter = interpreter def _find_source_script( - 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]] = [] + 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]] = [] 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) - largs.append(prog) - else: + elif isinstance(prog, (str, mesonlib.File)): 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) - found = self.interpreter.find_program_impl([prog]) - largs.append(found) + 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) + largs.append(prog) largs.extend(args) es = self.interpreter.backend.get_executable_serialisation(largs, verbose=True) es.subproject = self.interpreter.subproject @@ -116,7 +118,7 @@ def _process_script_args( @typed_pos_args( 'meson.add_install_script', - (str, mesonlib.File, build.Executable, ExternalProgram), + (str, mesonlib.File, build.Executable, ExternalProgram, build.LocalProgram), varargs=(str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, ExternalProgram) ) @typed_kwargs( @@ -132,7 +134,7 @@ def add_install_script_method( 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) + script = self._find_source_script('add_install_script', args[0], script_args, allow_built_program=True) script.skip_if_destdir = kwargs['skip_if_destdir'] script.tag = kwargs['install_tag'] script.dry_run = kwargs['dry_run'] @@ -147,7 +149,7 @@ def add_install_script_method( @InterpreterObject.method('add_postconf_script') def add_postconf_script_method( self, - args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], + args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram, build.LocalProgram], T.List[T.Union[str, mesonlib.File, ExternalProgram]]], kwargs: 'TYPE_kwargs') -> None: script_args = self._process_script_args('add_postconf_script', args[1]) @@ -164,7 +166,7 @@ def add_postconf_script_method( @InterpreterObject.method('add_dist_script') def add_dist_script_method( self, - args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], + args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram, build.LocalProgram], T.List[T.Union[str, mesonlib.File, ExternalProgram]]], kwargs: 'TYPE_kwargs') -> None: if args[1]: @@ -312,19 +314,19 @@ def install_dependency_manifest_method(self, args: T.Tuple[str], kwargs: 'TYPE_k 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)) + @typed_pos_args('meson.override_find_program', str, (mesonlib.File, ExternalProgram, build.Executable, build.LocalProgram, + build.CustomTarget, build.CustomTargetIndex)) @noKwargs @InterpreterObject.method('override_find_program') - def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable]], kwargs: 'TYPE_kwargs') -> None: + def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable, + build.LocalProgram, build.CustomTarget, build.CustomTargetIndex]], + kwargs: 'TYPE_kwargs') -> None: name, exe = args - 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) + if isinstance(exe, (build.CustomTarget, build.CustomTargetIndex)): + FeatureNew.single_use('Overriding find_program with a CustomTarget or CustomTargetIndex', '1.10.0', + self.subproject, location=self.current_node) + if not isinstance(exe, (ExternalProgram, build.LocalProgram)): + exe = self.interpreter._local_program_impl(exe) 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 4f961bb608ff..a54a69ff02c2 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -10,7 +10,8 @@ from .. import compilers from ..build import (CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, - BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) + BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources, + LocalProgram) from ..options import OptionKey, UserFeatureOption from ..dependencies import Dependency, InternalDependency from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo, FeatureBroken @@ -285,9 +286,9 @@ def _env_convertor(value: _FullEnvInitValueType) -> EnvironmentVariables: default=[], ) -COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTargetTypes, ExternalProgram, File]]] = KwargInfo( +COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTargetTypes, ExternalProgram, File, LocalProgram]]] = KwargInfo( 'command', - ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File), allow_empty=False), + ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File, LocalProgram), allow_empty=False), required=True, listify=True, default=[], diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 87892e6d7b30..5f088e9ed17f 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,7 +18,6 @@ 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 @@ -75,14 +74,14 @@ def find_program(self, prog: T.Union[mesonlib.FileOrString, T.List[mesonlib.File 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.OverrideExecutable, OverrideProgram]: + for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.LocalProgram]: 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[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: + wanted: T.Optional[str] = None) -> T.Union[ExternalProgram, build.LocalProgram]: # Look in overrides in case it's built as subproject progobj = self._interpreter.program_from_overrides([name], []) if progobj is not None: @@ -118,7 +117,7 @@ def dependency(self, depname: str, native: bool = False, required: bool = True, # 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', mesonlib.File]], + def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, build.LocalProgram, 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 ef3193b9ce98..d70f87f1fc32 100644 --- a/mesonbuild/modules/_qt.py +++ b/mesonbuild/modules/_qt.py @@ -208,7 +208,7 @@ def __init__(self, interpreter: Interpreter, qt_version: int = 5): 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.Executable]] = { + self.tools: T.Dict[str, T.Union[ExternalProgram, build.LocalProgram]] = { tool: NonExistingExternalProgram(tool) for tool in self._set_of_qt_tools } self.methods.update({ @@ -250,7 +250,7 @@ def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: arg = ['-v'] # Ensure that the version of qt and each tool are the same - def get_version(p: T.Union[ExternalProgram, build.Executable]) -> str: + def get_version(p: T.Union[ExternalProgram, build.LocalProgram]) -> str: _, out, err = Popen_safe(p.get_command() + arg) if name == 'lrelease' or not qt_dep.version.startswith('4'): care = out @@ -445,12 +445,17 @@ def _compile_resources_impl(self, state: 'ModuleState', kwargs: 'ResourceCompile 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, - self.tools['rcc'].get_command() + ['-name', name, '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + cmd, sources, [f'{name}.cpp'], depend_files=qrc_deps, @@ -466,12 +471,16 @@ def _compile_resources_impl(self, state: 'ModuleState', kwargs: 'ResourceCompile 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, - self.tools['rcc'].get_command() + ['-name', '@BASENAME@', '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + cmd, [rcc_file], [f'{name}.cpp'], depend_files=qrc_deps, @@ -726,7 +735,7 @@ def compile_translations(self, state: ModuleState, args: T.Tuple, kwargs: Compil ts = os.path.basename(ts) else: outdir = state.subdir - cmd: T.List[T.Union[ExternalProgram, build.Executable, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] + cmd: T.List[T.Union[ExternalProgram, build.LocalProgram, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] lrelease_target = build.CustomTarget( f'qt{self.qt_version}-compile-{ts}', outdir, @@ -866,12 +875,15 @@ def _moc_json_collect(self, state: ModuleState, kwargs: MocJsonCollectKwArgs) -> 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, - self.tools['moc'].get_command() + ['--collect-json', '-o', '@OUTPUT@'] + input_args, + cmd, moc_json, [f'{target_name}_json_collect.json'], description=f'Collecting json type information for {target_name}', @@ -911,12 +923,17 @@ def _gen_qml_cachegen(self, state: ModuleState, kwargs: GenQmlCachegenKwArgs) -> 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, - self.tools['qmlcachegen'].get_command() + ['-o', '@OUTPUT@'] + ['--resource-name', f'qmlcache_{target_name}'] + kwargs['extra_args'] + ['--resource=@INPUT@'] + cachegen_inputs, + cmd, [kwargs['qml_qrc']], #output name format matters here [f'{target_name}_qmlcache_loader.cpp'], @@ -944,11 +961,12 @@ def _qml_type_registrar(self, state: ModuleState, kwargs: GenQmlTypeRegistrarKwA install_dir: T.List[T.Union[str, Literal[False]]] = [False] install_tag: T.List[T.Union[str, None]] = [None] - cmd = self.tools['qmltyperegistrar'].get_command() + [ + cmd = [ + self.tools['qmltyperegistrar'], '--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 35ce86be81f1..860a62481cef 100644 --- a/mesonbuild/modules/dlang.py +++ b/mesonbuild/modules/dlang.py @@ -12,7 +12,7 @@ from . import ExtensionModule, ModuleInfo from .. import mlog -from ..build import InvalidArguments +from ..build import InvalidArguments, LocalProgram from ..dependencies import Dependency from ..dependencies.dub import DubDependency from ..interpreterbase import typed_pos_args @@ -22,12 +22,11 @@ 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, OverrideProgram + from ..programs import ExternalProgram - _AnyProgram: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram] + _AnyProgram: TypeAlias = T.Union[ExternalProgram, LocalProgram] _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 9e525601ee81..6219e4ce0bbf 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -22,7 +22,7 @@ from .. import interpreter from .. import mesonlib from .. import mlog -from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments +from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, LocalProgram 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,7 +33,6 @@ 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: @@ -198,7 +197,7 @@ class MkEnums(_MkEnumsCommon): vtail: T.Optional[str] depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] - ToolType: TypeAlias = T.Union[Executable, ExternalProgram, OverrideProgram] + ToolType: TypeAlias = T.Union[ExternalProgram, LocalProgram] # Differs from the CustomTarget version in that it straight defaults to True @@ -789,8 +788,7 @@ def postconf_hook(self, b: build.Build) -> None: if self.devenv is not None: b.devenv.append(self.devenv) - def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[Executable, 'ExternalProgram', 'OverrideProgram'], - T.Union[Executable, 'ExternalProgram', 'OverrideProgram']]: + def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, ToolType, ToolType]: if not self.gir_dep: self.gir_dep = state.dependency('gobject-introspection-1.0') self.giscanner = self._find_tool(state, 'g-ir-scanner') @@ -810,7 +808,7 @@ def _giscanner_version_compare(self, state: 'ModuleState', cmp: str) -> bool: @functools.lru_cache(maxsize=None) def _gir_has_option(self, option: str) -> bool: exe = self.giscanner - if isinstance(exe, (Executable, OverrideProgram)): + if isinstance(exe, LocalProgram): # Handle overridden g-ir-scanner assert option in {'--extra-library', '--sources-top-dirs'} return True @@ -971,7 +969,7 @@ def _make_gir_target( self, state: 'ModuleState', girfile: str, - scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]], + scan_command: T.Sequence[T.Union['FileOrString', Executable, ToolType]], 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], @@ -1020,7 +1018,7 @@ def _make_gir_target( @staticmethod def _make_typelib_target(state: 'ModuleState', typelib_output: str, - typelib_cmd: T.Sequence[T.Union[str, Executable, ExternalProgram, CustomTarget]], + typelib_cmd: T.Sequence[T.Union[str, CustomTarget, ToolType]], generated_files: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], kwargs: T.Dict[str, T.Any]) -> TypelibTarget: install = kwargs['install_typelib'] @@ -1194,7 +1192,7 @@ def generate_gir(self, state: 'ModuleState', args: T.Tuple[T.List[T.Union[Execut gir_inc_dirs: T.List[str] = [] - scan_command: T.List[T.Union[str, Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner] + scan_command: T.List[T.Union[str, ToolType, Executable]] = [giscanner] scan_command += ['--quiet'] scan_command += ['--no-libtool'] scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion] @@ -1347,7 +1345,7 @@ def yelp(self, state: 'ModuleState', args: T.Tuple[str, T.List[str]], kwargs: 'Y 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[ExternalProgram, Executable, OverrideProgram, str]] = [itstool, '-o', pot_file] + pot_args: T.List[T.Union[ToolType, 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, @@ -1379,7 +1377,7 @@ def yelp(self, state: 'ModuleState', args: T.Tuple[str, T.List[str]], kwargs: 'Y targets.append(l_data) po_file = l + '.po' - po_args: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] = [ + po_args: T.List[T.Union[ToolType, str]] = [ msgmerge, '-q', '-o', os.path.join('@SOURCE_ROOT@', l_subdir, po_file), os.path.join('@SOURCE_ROOT@', l_subdir, po_file), pot_file] @@ -2242,7 +2240,7 @@ def generate_vapi(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'Gener 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[ExternalProgram, Executable, OverrideProgram, str]] + cmd: T.List[T.Union[ToolType, 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 2d8d04d3ec30..06e891714d24 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -259,7 +259,7 @@ def __init__(self, interpreter: 'Interpreter'): 'itstool_join': self.itstool_join, 'xgettext': self.xgettext, }) - self.tools: T.Dict[str, T.Optional[T.Union[ExternalProgram, build.Executable]]] = { + self.tools: T.Dict[str, T.Optional[T.Union[ExternalProgram, build.LocalProgram]]] = { 'itstool': None, 'msgfmt': None, 'msginit': None, diff --git a/mesonbuild/modules/icestorm.py b/mesonbuild/modules/icestorm.py index 18bf0e2022c8..86af78d8b73c 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.Executable]] = {} + self.tools: T.Dict[str, T.Union[ExternalProgram, build.LocalProgram]] = {} self.methods.update({ 'project': self.project, }) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 6f5a63a0b3a4..c637e5f5e306 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -15,7 +15,7 @@ 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 _ExternalProgramHolder +from ..interpreter.interpreterobjects import BaseProgramHolder 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 @@ def _get_path(self, state: T.Optional['ModuleState'], key: str) -> str: _LIMITED_API_KW = KwargInfo('limited_api', str, default='', since='1.3.0') _DEFAULTABLE_SUBDIR_KW = KwargInfo('subdir', (str, NoneType)) -class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): +class PythonInstallation(BaseProgramHolder['PythonExternalProgram']): def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'): - _ExternalProgramHolder.__init__(self, python, interpreter) + BaseProgramHolder.__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 89c20230b425..73094f5717bc 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -13,7 +13,7 @@ from . import ExtensionModule, ModuleReturnValue, ModuleInfo from .. import mesonlib, mlog from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, - CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary) + CustomTarget, InvalidArguments, Jar, LocalProgram, 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,7 +33,6 @@ 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 @@ -91,7 +90,7 @@ class RustModule(ExtensionModule): def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) - self._bindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None + self._bindgen_bin: T.Optional[T.Union[ExternalProgram, LocalProgram]] = 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 @@ -375,10 +374,7 @@ def bindgen(self, state: ModuleState, args: T.List, kwargs: FuncBindgen) -> Modu 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: - # 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]) + _, _, err = mesonlib.Popen_safe(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 ': "..." is not a valid Rust target, accepted values are of the form ... @@ -386,9 +382,7 @@ def bindgen(self, state: ModuleState, args: T.List, kwargs: FuncBindgen) -> Modu if 'Got an invalid' in err or 'is not a valid Rust target' in err: self._bindgen_rust_target = None - # 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') + 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 94c6f819db5e..675a6d9740e0 100644 --- a/mesonbuild/modules/wayland.py +++ b/mesonbuild/modules/wayland.py @@ -6,7 +6,7 @@ import typing as T from . import ExtensionModule, ModuleReturnValue, ModuleInfo -from ..build import CustomTarget +from ..build import CustomTarget, LocalProgram 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,7 +15,6 @@ 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 @@ -42,7 +41,7 @@ def __init__(self, interpreter: Interpreter) -> None: self.protocols_dep: T.Optional[Dependency] = None self.pkgdatadir: T.Optional[str] = None - self.scanner_bin: T.Optional[T.Union[ExternalProgram, Executable]] = None + self.scanner_bin: T.Optional[T.Union[ExternalProgram, LocalProgram]] = None self.methods.update({ 'scan_xml': self.scan_xml, diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index 0abf09720340..16c12c85a1c6 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -13,6 +13,7 @@ import re import typing as T from pathlib import Path +from abc import ABCMeta, abstractmethod from . import mesonlib from . import mlog @@ -23,7 +24,33 @@ from .interpreter import Interpreter -class ExternalProgram(mesonlib.HoldableObject): +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): """A program that is found on the system. :param name: The name of the program @@ -357,17 +384,6 @@ def found(self) -> bool: 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 new file mode 100644 index 000000000000..ee4463fa7f20 --- /dev/null +++ b/test cases/common/285 local program/meson.build @@ -0,0 +1,68 @@ +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) + +# A custom target can be passed directly, but it won't run on Windows. +meson.override_find_program('prog5', prog_ct) +prog5 = find_program('prog5') +assert(prog5.version() == '2.0') +assert(prog5.found()) +if host_machine.system() != 'windows' + test('test-prog5', prog5) +endif + +prog6 = local_program(prog_ct, interpreter: [python3, '-B']) +test('test-prog6', prog6) diff --git a/test cases/common/285 local program/prog.py b/test cases/common/285 local program/prog.py new file mode 100755 index 000000000000..bd1042a2f568 --- /dev/null +++ b/test cases/common/285 local program/prog.py @@ -0,0 +1,5 @@ +#! /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 new file mode 100644 index 000000000000..3cc1da0c394b --- /dev/null +++ b/test cases/common/285 local program/pymod.py.in @@ -0,0 +1,2 @@ +def foo() -> None: + return 0