Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/markdown/snippets/override_program_ct.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Override a program with a custom target

A program can now be generated using a [[custom_target]] and set an override
so subprojects can use it at compile time.

```meson
ct = custom_target(
output : 'program',
command: ['program_generator.py'],
depends: my_pymod,
install: true,
)
meson.override_find_program('program', ct)
```
2 changes: 1 addition & 1 deletion docs/yaml/builtins/meson.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ methods:
description: The name of the program to override.

program:
type: exe | file | external_program
type: exe | file | external_program | custom_tgt | custom_idx
description: The program to set as the override for `progname`.

- name: override_dependency
Expand Down
6 changes: 3 additions & 3 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,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]],
workdir: T.Optional[str] = None,
extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None,
capture: T.Optional[str] = None,
Expand All @@ -548,7 +548,7 @@ def get_executable_serialisation(
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.
Expand All @@ -567,7 +567,7 @@ def get_executable_serialisation(
for c in raw_cmd_args:
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))
Expand Down
20 changes: 16 additions & 4 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from ._typing import ImmutableListProtocol
from .backend.backends import Backend
from .compilers import Compiler
from .interpreter.interpreter import SourceOutputs, Interpreter
from .interpreter.interpreter import SourceOutputs, Interpreter, ProgramType
from .interpreter.interpreterobjects import Test, Doctest
from .interpreterbase import SubProject
from .linkers.linkers import StaticLinker
Expand Down Expand Up @@ -291,7 +291,7 @@ def __init__(self, environment: 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, ProgramType] = {}
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
Expand Down Expand Up @@ -1981,7 +1981,7 @@ def __str__(self) -> str:
return self.fname

class Generator(HoldableObject):
def __init__(self, exe: T.Union['Executable', programs.ExternalProgram],
def __init__(self, exe: ProgramType,
arguments: T.List[str],
output: T.List[str],
# how2dataclass
Expand All @@ -2002,7 +2002,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) -> ProgramType:
return self.exe

def get_base_outnames(self, inname: str) -> T.List[str]:
Expand Down Expand Up @@ -3356,6 +3356,18 @@ def __getattr__(self, name: str) -> T.Any:
def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str:
return self._version

class OverrideCustomTarget(CustomTargetIndex):
def __init__(self, target: CustomTargetIndex, version: str):
self._target = target
self._version = version

def __getattr__(self, name: str) -> T.Any:
_target = object.__getattribute__(self, '_target')
return getattr(_target, name)

def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str:
return self._version

# A bit poorly named, but this represents plain data files to copy
# during install.
@dataclass(eq=False)
Expand Down
17 changes: 10 additions & 7 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@

BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources]

ProgramType = T.Union[ExternalProgram, OverrideProgram, build.OverrideExecutable, build.OverrideCustomTarget]

ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str]

TestClass = T.TypeVar('TestClass', bound=Test)
Expand Down Expand Up @@ -428,6 +430,7 @@ def build_holder_map(self) -> None:
build.GeneratedList: OBJ.GeneratedListHolder,
build.ExtractedObjects: OBJ.GeneratedObjectsHolder,
build.OverrideExecutable: OBJ.OverrideExecutableHolder,
build.OverrideCustomTarget: OBJ.OverrideCustomTargetHolder,
build.RunTarget: OBJ.RunTargetHolder,
build.AliasTarget: OBJ.AliasTargetHolder,
build.Headers: OBJ.HeadersHolder,
Expand Down Expand Up @@ -1589,7 +1592,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[ProgramType]:
for name in command_names:
if not isinstance(name, str):
continue
Expand All @@ -1604,7 +1607,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: ProgramType) -> 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:
Expand All @@ -1629,7 +1632,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']:
) -> ProgramType:
args = mesonlib.listify(args)

extra_info: T.List[mlog.TV_Loggable] = []
Expand Down Expand Up @@ -1661,7 +1664,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[ProgramType]:
progobj = self.program_from_overrides(args, extra_info)
if progobj:
return progobj
Expand Down Expand Up @@ -1697,7 +1700,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: ProgramType,
wanted: T.Union[str, T.List[str]],
version_func: T.Optional[ProgramVersionFunc],
extra_info: T.List[mlog.TV_Loggable]) -> bool:
Expand All @@ -1724,7 +1727,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[ProgramType]:
mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program',
mlog.bold(' '.join(args)))
sp_kwargs: kwtypes.DoSubproject = {
Expand All @@ -1751,7 +1754,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']:
) -> ProgramType:
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
assert feature, 'for mypy'
Expand Down
15 changes: 15 additions & 0 deletions mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1171,3 +1171,18 @@ class OverrideExecutableHolder(BuildTargetHolder[build.OverrideExecutable]):
@InterpreterObject.method('version')
def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.held_object.get_version(self.interpreter)

class OverrideCustomTargetHolder(ObjectHolder[build.OverrideCustomTarget]):
@noPosargs
@noKwargs
@FeatureNew('OverrideCustomTarget.version', '1.10.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)

@noPosargs
@noKwargs
@FeatureNew('OverrideCustomTarget.found', '1.10.0')
@InterpreterObject.method('found')
def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
return True
14 changes: 11 additions & 3 deletions mesonbuild/interpreter/mesonmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(self, build: 'build.Build', 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]] = []
largs: T.List[T.Union[str, build.Executable, ExternalProgram, build.CustomTargetIndex]] = []

if isinstance(prog, (build.Executable, ExternalProgram)):
FeatureNew.single_use(f'Passing executable/found program object to script parameter of {name}',
Expand Down Expand Up @@ -312,10 +312,10 @@ 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.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.CustomTarget, build.CustomTargetIndex]], kwargs: 'TYPE_kwargs') -> None:
name, exe = args
if isinstance(exe, mesonlib.File):
abspath = exe.absolute_path(self.interpreter.environment.source_dir,
Expand All @@ -325,6 +325,14 @@ def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File,
exe = OverrideProgram(name, self.interpreter.project_version, command=[abspath])
elif isinstance(exe, build.Executable):
exe = build.OverrideExecutable(exe, self.interpreter.project_version)
elif isinstance(exe, build.CustomTarget):
FeatureNew.single_use('Overriding program with a CustomTarget', '1.10.0', self.subproject)
if len(exe.get_outputs()) != 1:
raise InterpreterException(f'Can only override {name} with a CustomTarget that has exactly one output file.')
exe = build.OverrideCustomTarget(exe[0], self.interpreter.project_version)
elif isinstance(exe, build.CustomTargetIndex):
FeatureNew.single_use('Overriding program with a CustomTargetIndex', '1.10.0', self.subproject)
exe = build.OverrideCustomTarget(exe, self.interpreter.project_version)
self.interpreter.add_find_program_override(name, exe)

@typed_kwargs(
Expand Down
9 changes: 4 additions & 5 deletions mesonbuild/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@

if T.TYPE_CHECKING:
from ..interpreter import Interpreter
from ..interpreter.interpreter import ProgramVersionFunc
from ..interpreter.interpreter import ProgramVersionFunc, ProgramType
from ..interpreterbase import TYPE_var, TYPE_kwargs
from ..programs import OverrideProgram
from ..dependencies import Dependency
from ..options import ElementaryOptionValues

Expand Down Expand Up @@ -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) -> ProgramType:
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) -> ProgramType:
# Look in overrides in case it's built as subproject
progobj = self._interpreter.program_from_overrides([name], [])
if progobj is not None:
Expand Down Expand Up @@ -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[ProgramType, build.Jar, 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:
Expand Down
8 changes: 4 additions & 4 deletions mesonbuild/modules/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
if T.TYPE_CHECKING:
from . import ModuleState
from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency
from ..interpreter import Interpreter
from ..interpreter import kwargs
from ..interpreter import Interpreter, kwargs
from ..interpreter.interpreter import ProgramType
from ..mesonlib import FileOrString
from ..programs import ExternalProgram
from typing_extensions import Literal
Expand Down Expand Up @@ -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, ProgramType] = {
tool: NonExistingExternalProgram(tool) for tool in self._set_of_qt_tools
}
self.methods.update({
Expand Down Expand Up @@ -724,7 +724,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[ProgramType, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@']
lrelease_target = build.CustomTarget(
f'qt{self.qt_version}-compile-{ts}',
outdir,
Expand Down
11 changes: 4 additions & 7 deletions mesonbuild/modules/dlang.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,17 @@
from typing_extensions import Literal, TypeAlias

from . import ModuleState
from ..build import OverrideExecutable
from ..interpreter.interpreter import Interpreter
from ..interpreter.interpreter import Interpreter, ProgramType
from ..interpreterbase.baseobjects import TYPE_kwargs
from ..programs import ExternalProgram, OverrideProgram

_AnyProgram: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram]
_JSONTypes: TypeAlias = T.Union[str, int, bool, None, T.List['_JSONTypes'], T.Dict[str, '_JSONTypes']]


class DlangModule(ExtensionModule):
class_dubbin: T.Union[_AnyProgram, Literal[False], None] = None
class_dubbin: T.Union[ProgramType, Literal[False], None] = None
init_dub = False

dubbin: T.Union[_AnyProgram, Literal[False], None]
dubbin: T.Union[ProgramType, Literal[False], None]

INFO = ModuleInfo('dlang', '0.48.0')

Expand Down Expand Up @@ -122,7 +119,7 @@ def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Mapping[str, str]] =
p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2]
return p.returncode, out.strip()

def check_dub(self, state: ModuleState) -> T.Union[_AnyProgram, Literal[False]]:
def check_dub(self, state: ModuleState) -> T.Union[ProgramType, Literal[False]]:
dubbin = state.find_program('dub', silent=True)
if dubbin.found():
try:
Expand Down
Loading
Loading