Skip to content
Open
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
1 change: 1 addition & 0 deletions data/syntax-highlighting/vim/syntax/meson.vim
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ syn keyword mesonBuiltin
\ executable
\ files
\ find_program
\ local_program
\ generator
\ get_option
\ get_variable
Expand Down
17 changes: 17 additions & 0 deletions docs/markdown/snippets/local_program.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions docs/yaml/builtins/meson.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions docs/yaml/functions/local_program.yaml
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions mesonbuild/ast/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 7 additions & 3 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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))
Expand Down
86 changes: 52 additions & 34 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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]:
Expand Down Expand Up @@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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]],
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions mesonbuild/interpreter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
'SubprojectHolder',
'DependencyHolder',
'GeneratedListHolder',
'ExternalProgramHolder',
'extract_required_kwarg',

'ArrayHolder',
Expand All @@ -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,
Expand Down
Loading
Loading