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 @@ -86,6 +86,7 @@ syn keyword mesonBuiltin
\ custom_target
\ debug
\ declare_dependency
\ default
\ dependency
\ disabler
\ environment
Expand Down
13 changes: 13 additions & 0 deletions docs/markdown/snippets/default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Add a new `default()` function and object

This function and object allow setting a keyword argument to it's default value.
This is especially useful for cases when a non-default value is wanted in some
cases but not all.

For example:
```meson
library(
...
name_prefix : host_machine.system() == 'windows' ? '' : default(),
)
```
10 changes: 6 additions & 4 deletions docs/yaml/functions/_build_target_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -223,18 +223,19 @@ kwargs:
object files had to be placed in `sources`.

name_prefix:
type: str | array[void]
type: str

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be str | default strictly speaking? This is what I meant with the other comment. I don't see much of a difference in typing annotations between introducing default() and some form of nil / null / none.

description: |
The string that will be used as the prefix for the
target output filename by overriding the default (only used for
libraries). By default this is `lib` on all platforms and compilers,
except for MSVC shared libraries where it is omitted to follow
convention, and Cygwin shared libraries where it is `cyg`.

Set this to `[]`, or omit the keyword argument for the default behaviour.
To conditionally keep the default behavior use a [[default]] object.
For Meson < 1.12, use `[]`.

name_suffix:
type: str | array[void]
type: str
description: |
The string that will be used as the extension for the
target by overriding the default. By default on Windows this is
Expand All @@ -247,7 +248,8 @@ kwargs:
potential name clash with shared libraries which also generate
import libraries with a `lib` suffix.

Set this to `[]`, or omit the keyword argument for the default behaviour.
To conditionally keep the default behavior use a [[default]] object.
For Meson < 1.12, use `[]`.

override_options:
type: array[str] | dict[str | bool | int | array[str]]
Expand Down
4 changes: 4 additions & 0 deletions docs/yaml/functions/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: default
returns: default
description: Returns a [[@default]] object.
since: 1.12.0
5 changes: 5 additions & 0 deletions docs/yaml/objects/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: default
long_name: Default
description: |
A default object can be passed to keyword arguments to get the default value.

1 change: 1 addition & 0 deletions mesonbuild/ast/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ def __init__(self, source_root: str, subdir: str, subproject: SubProject, subpro
'is_disabler': self.func_do_nothing,
'is_variable': self.func_do_nothing,
'disabler': self.func_do_nothing,
'default': self.func_do_nothing,
'jar': self.func_do_nothing,
'warning': self.func_do_nothing,
'shared_module': self.func_do_nothing,
Expand Down
13 changes: 10 additions & 3 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken, FeatureNewKwargs
from ..interpreterbase import ObjectHolder, ContextManagerObject
from ..interpreterbase import ObjectHolder, ContextManagerObject, DefaultObject
from ..interpreterbase import stringifyUserArguments, Feature, FeatureValue
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule, __path__ as modules_path
from ..optinterpreter import optname_regex
Expand Down Expand Up @@ -390,6 +390,7 @@ def build_func_dict(self) -> None:
'declare_dependency': self.func_declare_dependency,
'dependency': self.func_dependency,
'disabler': self.func_disabler,
'default': self.func_default,
'environment': self.func_environment,
'error': self.func_error,
'executable': self.func_executable,
Expand Down Expand Up @@ -1972,6 +1973,12 @@ def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kw
f.use(self.subproject, node)
return d

@FeatureNew('default', '1.12.0')
@noKwargs
@noPosargs
def func_default(self, node: mparser.BaseNode, args: list[TYPE_var], kwargs: TYPE_kwargs) -> DefaultObject:
return DefaultObject()

@FeatureNew('disabler', '0.44.0')
@noKwargs
@noPosargs
Expand Down Expand Up @@ -4056,7 +4063,7 @@ def check_for_jar_sources(self, sources: T.Union[T.List[str], T.Sequence[T.Union
def is_subproject(self) -> bool:
return self.subproject != ''

@typed_pos_args('set_variable', str, object)
@typed_pos_args('set_variable', str, (object, DefaultObject))
@noKwargs
@noArgsFlattening
@noSecondLevelHolderResolving
Expand All @@ -4066,7 +4073,7 @@ def func_set_variable(self, node: mparser.BaseNode, args: T.Tuple[str, TYPE_var
raise InvalidCode('Invalid variable name: ' + varname)
self.set_variable(varname, value, holderify=True)

@typed_pos_args('get_variable', (str, Disabler), optargs=[object])
@typed_pos_args('get_variable', (str, Disabler, DefaultObject), optargs=[object])
@noKwargs
@noArgsFlattening
@unholder_return
Expand Down
1 change: 1 addition & 0 deletions mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ def _name_suffix_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[
(str, NoneType, list),
validator=_name_validator,
convertor=lambda x: None if isinstance(x, list) else x,
deprecated_values={list: ('1.12.0', 'use the `default()` function instead')},
)


Expand Down
2 changes: 2 additions & 0 deletions mesonbuild/interpreterbase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'IterableObject',
'MutableInterpreterObject',
'ContextManagerObject',
'DefaultObject',

'MesonOperator',

Expand Down Expand Up @@ -70,6 +71,7 @@
IterableObject,
MutableInterpreterObject,
ContextManagerObject,
DefaultObject,

TV_func,
TYPE_elementary,
Expand Down
4 changes: 4 additions & 0 deletions mesonbuild/interpreterbase/baseobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,7 @@ def size(self) -> int:
class ContextManagerObject(MesonInterpreterObject, AbstractContextManager):
def __init__(self, subproject: 'SubProject') -> None:
super().__init__(subproject=subproject)


class DefaultObject(MesonInterpreterObject):
"""A default value to a keyword argument."""
23 changes: 23 additions & 0 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .. import coredata, mesonlib, mlog
from .disabler import Disabler
from .baseobjects import DefaultObject
from .exceptions import InterpreterException, InvalidArguments
from ._unholder import _unholder

Expand Down Expand Up @@ -216,6 +217,7 @@ def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
num_args = len(args)
num_types = len(types)
a_types = types
last_pos = num_args

if varargs:
min_args = num_types + min_varargs
Expand All @@ -238,6 +240,18 @@ def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:

for i, (arg, type_) in enumerate(itertools.zip_longest(args, a_types, fillvalue=varargs), start=1):
if not isinstance(arg, type_):
# if DefaultObject is an explicit allowed allowed type allow
# it through.
if isinstance(arg, DefaultObject):
if i >= last_pos:
if varargs:
msg = 'not allowed for variadic arguments'
else:
msg = 'not allowed for optional positional arguments'
else:
msg = 'not allowed for required positional arguments'
raise InvalidArguments(f'default() objects are {msg}')

if isinstance(type_, tuple):
shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in type_))
else:
Expand Down Expand Up @@ -565,6 +579,15 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
for info in types:
types_tuple = info.types if isinstance(info.types, tuple) else (info.types,)
value = kwargs.get(info.name)
if isinstance(value, DefaultObject):
# Ensure that default() is not used for required options
# Otherwise, set the value to None, which will send us down
# the "unset" path
if info.required:
raise InvalidArguments(f'"{name}" got a default() value for the required keyword argument "{info.name}". '
'default() may not be used for required keyword arguments.')
value = None
Comment thread
bonzini marked this conversation as resolved.

if value is not None:
if info.since:
feature_name = info.name + ' arg in ' + name
Expand Down
8 changes: 7 additions & 1 deletion mesonbuild/interpreterbase/interpreterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ObjectHolder,
IterableObject,
ContextManagerObject,
DefaultObject,

HoldableTypes,
)
Expand Down Expand Up @@ -612,7 +613,12 @@ def reduce_arguments(
def expand_default_kwargs(self, kwargs: T.Dict[str, T.Optional[InterpreterObject]]) -> T.Dict[str, T.Optional[InterpreterObject]]:
if 'kwargs' not in kwargs:
return kwargs
to_expand = _unholder(kwargs.pop('kwargs'))

kwargs_var = kwargs.pop('kwargs')
if isinstance(kwargs_var, DefaultObject):
return kwargs

to_expand = _unholder(kwargs_var)
if not isinstance(to_expand, dict):
raise InterpreterException('Value of "kwargs" must be dictionary.')
if 'kwargs' in to_expand:
Expand Down
48 changes: 48 additions & 0 deletions test cases/common/300 default/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2026 Intel Corporation

project(
'default',
'cpp',
meson_version : '>= 1.12.0',
default_options : {'cpp_std' : 'c++11'},
)

exe = executable(
'prog',
'prog.cpp',
override_options : default(),
)

exe2 = executable(
'prog2',
'prog.cpp',
kwargs : default(),
)

test_kwargs = {
'protocol' : default(),
}

test('exe_test', exe, kwargs : test_kwargs)

testcase expect_error('default() objects are not allowed for required positional arguments')
executable(default(), 'prog.cpp')
endtestcase

testcase expect_error('default() objects are not allowed for variadic arguments')
add_languages(default())
endtestcase

testcase expect_error('default() objects are not allowed for optional positional arguments')
assert(true, default())
endtestcase

testcase expect_error('"install_symlink" got a default() value for the required keyword argument "install_dir". default() may not be used for required keyword arguments.')
install_symlink('foo', pointing_to : '/foo', install_dir : default())
endtestcase

foo = default()
get_variable('foo')
unset_variable('foo')
set_variable('var', default())
8 changes: 8 additions & 0 deletions test cases/common/300 default/prog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright © 2026 Intel Corporation
*/

int main() {
return __cplusplus == 201103L ? 0 : 1;
}
Loading