Skip to content

Commit

Permalink
Allow overriding programs from the command line
Browse files Browse the repository at this point in the history
People coming from autotools are used to having easy access for
overriding programs. This should ease that transition.
  • Loading branch information
tristan957 committed Feb 23, 2024
1 parent db8246b commit 153d476
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 1 deletion.
2 changes: 2 additions & 0 deletions data/shell-completions/bash/meson
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ _meson_common_setup_configure_longopts=(
cmake-prefix-path
build.cmake-prefix-path
clearcache
native-program
cross-program
)

_meson-setup() {
Expand Down
2 changes: 2 additions & 0 deletions mesonbuild/ast/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class IntrospectionHelper:
def __init__(self, cross_file: T.Optional[str]):
self.cross_file = [cross_file] if cross_file is not None else []
self.native_file: T.List[str] = []
self.cross_program: T.List[str] = []
self.native_program: T.List[str] = []
self.cmd_line_options: T.Dict[OptionKey, str] = {}
self.projectoptions: T.List[str] = []

Expand Down
27 changes: 27 additions & 0 deletions mesonbuild/coredata.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class SharedCMDOptions(Protocol):
projectoptions: T.List[str]
cross_file: T.List[str]
native_file: T.List[str]
cross_program: T.List[str]
native_program: T.List[str]

OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], 'OptionsView']
MutableKeyedOptionDictType = T.Dict['OptionKey', 'UserOption[T.Any]']
Expand Down Expand Up @@ -581,6 +583,9 @@ def __init__(self, options: SharedCMDOptions, scratch_dir: str, meson_command: T
self.options: 'MutableKeyedOptionDictType' = {}
self.cross_files = self.__load_config_files(options, scratch_dir, 'cross')
self.compilers: PerMachine[T.Dict[str, Compiler]] = PerMachine(OrderedDict(), OrderedDict())
self.program_overrides: PerMachine[T.Dict[str, str]] = PerMachine(
self.__parse_program_overrides(options, MachineChoice.BUILD),
self.__parse_program_overrides(options, MachineChoice.HOST))

# Set of subprojects that have already been initialized once, this is
# required to be stored and reloaded with the coredata, as we don't
Expand All @@ -604,6 +609,25 @@ def __init__(self, options: SharedCMDOptions, scratch_dir: str, meson_command: T
self.builtin_options_libdir_cross_fixup()
self.init_builtins('')

@staticmethod
def __parse_program_overrides(options: SharedCMDOptions, for_machine: MachineChoice) -> OrderedDict[str, str]:
overrides_map: OrderedDict[str, str] = OrderedDict()
overrides: T.List[str]
if for_machine == MachineChoice.BUILD:
overrides = options.native_program
elif for_machine == MachineChoice.HOST:
overrides = options.cross_program

for o in overrides:
parts = o.split('=', 1)
if len(parts) == 1:
word = 'native' if for_machine == MachineChoice.HOST else 'cross'
raise MesonException(f'Invalid value for --{word}-program: {o}. The override name and value must be separated by an "=" in the form of xxx=path/to/yyy.')

overrides_map[parts[0]] = parts[1]

return overrides_map

@staticmethod
def __load_config_files(options: SharedCMDOptions, scratch_dir: str, ftype: str) -> T.List[str]:
# Need to try and make the passed filenames absolute because when the
Expand Down Expand Up @@ -1051,6 +1075,9 @@ def emit_base_options_warnings(self, enabled_opts: T.List[OptionKey]) -> None:
mlog.warning('Base option \'b_bitcode\' is enabled, which is incompatible with many linker options. Incompatible options such as \'b_asneeded\' have been disabled.', fatal=False)
mlog.warning('Please see https://mesonbuild.com/Builtin-options.html#Notes_about_Apple_Bitcode_support for more details.', fatal=False)

def get_program_overrides(self, for_machine: MachineChoice) -> T.Dict[str, str]:
return self.program_overrides[for_machine]

class CmdLineFileParser(configparser.ConfigParser):
def __init__(self) -> None:
# We don't want ':' as key delimiter, otherwise it would break when
Expand Down
26 changes: 26 additions & 0 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,28 @@ def add_languages_for(self, args: T.List[str], required: bool, for_machine: Mach

return success

def program_from_override(self, for_machine: MachineChoice, prognames: T.List[mesonlib.FileOrString],
extra_info: T.List[mlog.TV_Loggable]) -> T.Optional[ExternalProgram]:
program_overrides = self.coredata.get_program_overrides(for_machine)
if not program_overrides:
return None

for p in prognames:
if isinstance(p, mesonlib.File):
continue # Always points to a local (i.e. self generated) file.
if not isinstance(p, str):
raise InterpreterException('Executable name must be a string')

override = program_overrides.get(p, None)
if not override:
return None

prog = ExternalProgram(p, command=override, silent=True)
if not isinstance(prog, NonExistingExternalProgram):
extra_info.append(mlog.blue('(overridden)'))
return prog
return None

def program_from_file_for(self, for_machine: MachineChoice, prognames: T.List[mesonlib.FileOrString]
) -> T.Optional[ExternalProgram]:
for p in prognames:
Expand Down Expand Up @@ -1656,6 +1678,10 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi
if progobj:
return progobj

progobj = self.program_from_override(for_machine, args, extra_info)
if progobj:
return progobj

if args[0] == 'meson':
# Override find_program('meson') to return what we were invoked with
return ExternalProgram('meson', self.environment.get_build_command(), silent=True)
Expand Down
12 changes: 12 additions & 0 deletions mesonbuild/msetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from pathlib import Path
import typing as T

from mesonbuild.utils.universal import MachineChoice

from . import build, coredata, environment, interpreter, mesonlib, mintro, mlog
from .mesonlib import MesonException

Expand Down Expand Up @@ -65,6 +67,14 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
'newer version of meson.')
parser.add_argument('--clearcache', action='store_true', default=False,
help='Clear cached state (e.g. found dependencies). Since 1.3.0.')
parser.add_argument('--native-program',
default=[],
action='append',
help='Provide a native program override. The override must be of the form binary=/path/to/override. Since 1.4.0.')
parser.add_argument('--cross-program',
default=[],
action='append',
help='Provide a cross program override. The override must be of the form binary=/path/to/override. Since 1.4.0.')
parser.add_argument('builddir', nargs='?', default=None)
parser.add_argument('sourcedir', nargs='?', default=None)

Expand Down Expand Up @@ -256,6 +266,8 @@ def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.O
# read from a pipe and wrote into a private file.
self.options.cross_file = env.coredata.cross_files
self.options.native_file = env.coredata.config_files
self.options.cross_program = list(map(lambda i: f'{i[0]}={i[1]}', env.coredata.get_program_overrides(MachineChoice.HOST).items()))
self.options.native_program = list(map(lambda i: f'{i[0]}={i[1]}', env.coredata.get_program_overrides(MachineChoice.BUILD).items()))
coredata.write_cmd_line_file(self.build_dir, self.options)
else:
coredata.update_cmd_line_file(self.build_dir, self.options)
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ExternalProgram(mesonlib.HoldableObject):
windows_exts = ('exe', 'msc', 'com', 'bat', 'cmd')
for_machine = MachineChoice.BUILD

def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
def __init__(self, name: str, command: T.Optional[T.Union[str, T.List[str]]] = None,
silent: bool = False, search_dir: T.Optional[str] = None,
extra_search_dirs: T.Optional[T.List[str]] = None):
self.name = name
Expand Down
5 changes: 5 additions & 0 deletions test cases/unit/122 program overrides/cd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

echo "This is the wrapped cd"

cd "$1" || exit 1
14 changes: 14 additions & 0 deletions test cases/unit/122 program overrides/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
project('program overrides')

fs = import('fs')

native_cd = find_program('cd', native: true)
cross_cd = find_program('cd', native: false)

if get_option('machine') == 'build'
assert(fs.name(native_cd.full_path()) == 'cd.sh', 'override did not occur')
assert(fs.name(cross_cd.full_path()) == 'cd', 'override did not occur')
else
assert(fs.name(cross_cd.full_path()) == 'cd.sh', 'override did not occur')
assert(fs.name(native_cd.full_path()) == 'cd', 'override did not occur')
endif
1 change: 1 addition & 0 deletions test cases/unit/122 program overrides/meson_options.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
option('machine', type: 'combo', choices: ['host', 'build'], value: 'host')
6 changes: 6 additions & 0 deletions unittests/baseplatformtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def setUp(self):
self.meson_args = ['--backend=' + self.backend_name]
self.meson_native_files = []
self.meson_cross_files = []
self.meson_native_programs = []
self.meson_cross_programs = []
self.meson_command = python_command + [get_meson_script()]
self.setup_command = self.meson_command + ['setup'] + self.meson_args
self.mconf_command = self.meson_command + ['configure']
Expand Down Expand Up @@ -207,6 +209,10 @@ def init(self, srcdir, *,
args += ['--native-file', f]
for f in self.meson_cross_files:
args += ['--cross-file', f]
for o in self.meson_native_programs:
args += ['--native-program', o]
for o in self.meson_cross_programs:
args += ['--cross-program', o]
self.privatedir = os.path.join(self.builddir, 'meson-private')
if inprocess:
try:
Expand Down
18 changes: 18 additions & 0 deletions unittests/platformagnostictests.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,21 @@ def test_error_configuring_subdir(self):
self.assertIn('first statement must be a call to project()', out)
# provide guidance diagnostics by finding a file whose first AST statement is project()
self.assertIn(f'Did you mean to run meson from the directory: "{testdir}"?', out)

def test_native_program(self):
testdir = os.path.join(self.unit_test_dir, '122 program overrides')

with self.assertRaises(subprocess.CalledProcessError):
self.init(testdir, extra_args=['--native-program', 'xxx'])

my_cd = os.path.join(testdir, "cd.sh")
self.init(testdir, extra_args=['-Dmachine=build', '--native-program', f'cd={my_cd}'])

def test_cross_program(self):
testdir = os.path.join(self.unit_test_dir, '122 program overrides')

with self.assertRaises(subprocess.CalledProcessError):
self.init(testdir, extra_args=['--cross-program', 'xxx'])

my_cd = os.path.join(testdir, "cd.sh")
self.init(testdir, extra_args=['-Dmachine=host', '--cross-program', f'cd={my_cd}'])

0 comments on commit 153d476

Please sign in to comment.