aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/snippets/devenv.md20
-rw-r--r--docs/yaml/builtins/meson.yaml24
-rw-r--r--docs/yaml/functions/environment.yaml23
-rw-r--r--mesonbuild/build.py9
-rw-r--r--mesonbuild/interpreter/interpreter.py9
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py12
-rw-r--r--mesonbuild/interpreter/mesonmain.py14
-rw-r--r--mesonbuild/interpreter/type_checking.py43
-rw-r--r--test cases/unit/91 devenv/meson.build5
-rwxr-xr-xtest cases/unit/91 devenv/test-devenv.py2
10 files changed, 131 insertions, 30 deletions
diff --git a/docs/markdown/snippets/devenv.md b/docs/markdown/snippets/devenv.md
index 505f971..104cfd9 100644
--- a/docs/markdown/snippets/devenv.md
+++ b/docs/markdown/snippets/devenv.md
@@ -21,3 +21,23 @@ directory, that file is loaded by gdb automatically.
With `--dump` option, all envorinment variables that have been modified are
printed instead of starting an interactive shell. It can be used by shell
scripts that wish to setup their environment themself.
+
+## New `method` and `separator` kwargs on `environment()` and `meson.add_devenv()`
+
+It simplifies this common pattern:
+```meson
+env = environment()
+env.prepend('FOO', ['a', 'b'], separator: ',')
+meson.add_devenv(env)
+```
+
+becomes one line:
+```meson
+meson.add_devenv({'FOO': ['a', 'b']}, method: 'prepend', separator: ',')
+```
+
+or two lines:
+```meson
+env = environment({'FOO': ['a', 'b']}, method: 'prepend', separator: ',')
+meson.add_devenv(env)
+```
diff --git a/docs/yaml/builtins/meson.yaml b/docs/yaml/builtins/meson.yaml
index 92fc90e..09e5dbb 100644
--- a/docs/yaml/builtins/meson.yaml
+++ b/docs/yaml/builtins/meson.yaml
@@ -444,5 +444,25 @@ methods:
posargs:
env:
- type: env
- description: The [[@env]] object to add.
+ type: env | str | list[str] | dict[str] | dict[list[str]]
+ description: |
+ The [[@env]] object to add.
+ Since *0.62.0* list of strings is allowed in dictionnary values. In that
+ case values are joined using the separator.
+ kwargs:
+ separator:
+ type: str
+ since: 0.62.0
+ description: |
+ The separator to use for the initial values defined in
+ the first positional argument. If not explicitly specified, the default
+ path separator for the host operating system will be used, i.e. ';' for
+ Windows and ':' for UNIX/POSIX systems.
+ method:
+ type: str
+ since: 0.62.0
+ description: |
+ Must be one of 'set', 'prepend', or 'append'
+ (defaults to 'set'). Controls if initial values defined in the first
+ positional argument are prepended, appended or repace the current value
+ of the environment variable.
diff --git a/docs/yaml/functions/environment.yaml b/docs/yaml/functions/environment.yaml
index 99c8a45..5fb81e3 100644
--- a/docs/yaml/functions/environment.yaml
+++ b/docs/yaml/functions/environment.yaml
@@ -5,8 +5,29 @@ description: Returns an empty [[@env]] object.
optargs:
env:
- type: dict[str]
+ type: str | list[str] | dict[str] | dict[list[str]]
since: 0.52.0
description: |
If provided, each key/value pair is added into the [[@env]] object
as if [[env.set]] method was called for each of them.
+ Since *0.62.0* list of strings is allowed in dictionnary values. In that
+ case values are joined using the separator.
+
+kwargs:
+ separator:
+ type: str
+ since: 0.62.0
+ description: |
+ The separator to use for the initial values defined in
+ the first positional argument. If not explicitly specified, the default
+ path separator for the host operating system will be used, i.e. ';' for
+ Windows and ':' for UNIX/POSIX systems.
+
+ method:
+ type: str
+ since: 0.62.0
+ description: |
+ Must be one of 'set', 'prepend', or 'append'
+ (defaults to 'set'). Controls if initial values defined in the first
+ positional argument are prepended, appended or repace the current value
+ of the environment variable.
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 35e5e66..6bb487b 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -46,6 +46,7 @@ from .linkers import StaticLinker
from .interpreterbase import FeatureNew, FeatureDeprecated
if T.TYPE_CHECKING:
+ from typing_extensions import Literal
from ._typing import ImmutableListProtocol, ImmutableSetProtocol
from .backend.backends import Backend, ExecutableSerialisation
from .interpreter.interpreter import Test, SourceOutputs, Interpreter
@@ -451,15 +452,19 @@ class ExtractedObjects(HoldableObject):
for source in self.get_sources(self.srclist, self.genlist)
]
+EnvInitValueType = T.Dict[str, T.Union[str, T.List[str]]]
+
class EnvironmentVariables(HoldableObject):
- def __init__(self, values: T.Optional[T.Dict[str, str]] = None) -> None:
+ def __init__(self, values: T.Optional[EnvValueType] = None,
+ init_method: Literal['set', 'prepend', 'append'] = 'set', separator: str = os.pathsep) -> None:
self.envvars: T.List[T.Tuple[T.Callable[[T.Dict[str, str], str, T.List[str], str], str], str, T.List[str], str]] = []
# The set of all env vars we have operations for. Only used for self.has_name()
self.varnames: T.Set[str] = set()
if values:
+ init_func = getattr(self, init_method)
for name, value in values.items():
- self.set(name, [value])
+ init_func(name, listify(value), separator)
def __repr__(self) -> str:
repr_str = "<{0}: {1}>"
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 30d0e4c..380cc1d 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -63,6 +63,8 @@ from .type_checking import (
DEPFILE_KW,
DISABLER_KW,
ENV_KW,
+ ENV_METHOD_KW,
+ ENV_SEPARATOR_KW,
INSTALL_KW,
INSTALL_MODE_KW,
CT_INSTALL_TAG_KW,
@@ -71,6 +73,7 @@ from .type_checking import (
REQUIRED_KW,
NoneType,
in_set_validator,
+ env_convertor_with_method
)
from . import primitives as P_OBJ
@@ -2610,9 +2613,9 @@ external dependencies (including libraries) must go to "dependencies".''')
for lang in kwargs['language']:
argsdict[lang] = argsdict.get(lang, []) + args
- @noKwargs
@noArgsFlattening
@typed_pos_args('environment', optargs=[(str, list, dict)])
+ @typed_kwargs('environment', ENV_METHOD_KW, ENV_SEPARATOR_KW.evolve(since='0.62.0'))
def func_environment(self, node: mparser.FunctionNode, args: T.Tuple[T.Union[None, str, T.List['TYPE_var'], T.Dict[str, 'TYPE_var']]],
kwargs: 'TYPE_kwargs') -> build.EnvironmentVariables:
init = args[0]
@@ -2621,7 +2624,9 @@ external dependencies (including libraries) must go to "dependencies".''')
msg = ENV_KW.validator(init)
if msg:
raise InvalidArguments(f'"environment": {msg}')
- return ENV_KW.convertor(init)
+ if isinstance(init, dict) and any(i for i in init.values() if isinstance(i, list)):
+ FeatureNew.single_use('List of string in dictionary value', '0.62.0', self.subproject, location=node)
+ return env_convertor_with_method(init, kwargs['method'], kwargs['separator'])
return build.EnvironmentVariables()
@typed_pos_args('join_paths', varargs=str, min_varargs=1)
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py
index 989877e..4520f09 100644
--- a/mesonbuild/interpreter/interpreterobjects.py
+++ b/mesonbuild/interpreter/interpreterobjects.py
@@ -20,7 +20,7 @@ from ..interpreterbase import (
typed_pos_args, typed_kwargs, typed_operator,
noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs,
flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
-from ..interpreter.type_checking import NoneType
+from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW
from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram
from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe
@@ -232,10 +232,6 @@ class RunProcess(MesonInterpreterObject):
def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.stderr
-
-_ENV_SEPARATOR_KW = KwargInfo('separator', str, default=os.pathsep)
-
-
class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject):
def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'):
@@ -260,20 +256,20 @@ class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], Mutab
FeatureNew(m, '0.58.0', location=self.current_node).use(self.subproject)
@typed_pos_args('environment.set', str, varargs=str, min_varargs=1)
- @typed_kwargs('environment.set', _ENV_SEPARATOR_KW)
+ @typed_kwargs('environment.set', ENV_SEPARATOR_KW)
def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.held_object.set(name, values, kwargs['separator'])
@typed_pos_args('environment.append', str, varargs=str, min_varargs=1)
- @typed_kwargs('environment.append', _ENV_SEPARATOR_KW)
+ @typed_kwargs('environment.append', ENV_SEPARATOR_KW)
def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.warn_if_has_name(name)
self.held_object.append(name, values, kwargs['separator'])
@typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1)
- @typed_kwargs('environment.prepend', _ENV_SEPARATOR_KW)
+ @typed_kwargs('environment.prepend', ENV_SEPARATOR_KW)
def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.warn_if_has_name(name)
diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py
index 5781e03..4533c4a 100644
--- a/mesonbuild/interpreter/mesonmain.py
+++ b/mesonbuild/interpreter/mesonmain.py
@@ -12,7 +12,7 @@ from .. import mlog
from ..mesonlib import MachineChoice, OptionKey
from ..programs import OverrideProgram, ExternalProgram
-from ..interpreter.type_checking import ENV_KW
+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,
typed_kwargs, KwargInfo, InterpreterException)
@@ -20,6 +20,7 @@ from .primitives import MesonVersionString
from .type_checking import NATIVE_KW, NoneType
if T.TYPE_CHECKING:
+ from typing_extensions import Literal
from ..backend.backends import ExecutableSerialisation
from ..compilers import Compiler
from ..interpreterbase import TYPE_kwargs, TYPE_var
@@ -41,6 +42,10 @@ if T.TYPE_CHECKING:
native: mesonlib.MachineChoice
+ class AddDevenvKW(TypedDict):
+ method: Literal['set', 'prepend', 'append']
+ separator: str
+
class MesonMain(MesonInterpreterObject):
def __init__(self, build: 'build.Build', interpreter: 'Interpreter'):
@@ -438,13 +443,14 @@ class MesonMain(MesonInterpreterObject):
return prop_name in self.interpreter.environment.properties[kwargs['native']]
@FeatureNew('add_devenv', '0.58.0')
- @noKwargs
+ @typed_kwargs('environment', ENV_METHOD_KW, ENV_SEPARATOR_KW.evolve(since='0.62.0'))
@typed_pos_args('add_devenv', (str, list, dict, build.EnvironmentVariables))
- def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, build.EnvironmentVariables]], kwargs: 'TYPE_kwargs') -> None:
+ def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, build.EnvironmentVariables]],
+ kwargs: 'AddDevenvKW') -> None:
env = args[0]
msg = ENV_KW.validator(env)
if msg:
raise build.InvalidArguments(f'"add_devenv": {msg}')
- converted = ENV_KW.convertor(env)
+ converted = env_convertor_with_method(env, kwargs['method'], kwargs['separator'])
assert isinstance(converted, build.EnvironmentVariables)
self.build.devenv.append(converted)
diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py
index e94027b..cce794f 100644
--- a/mesonbuild/interpreter/type_checking.py
+++ b/mesonbuild/interpreter/type_checking.py
@@ -3,10 +3,13 @@
"""Helpers for strict type checking."""
+from __future__ import annotations
+import os
import typing as T
from .. import compilers
-from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs
+from ..build import (EnvironmentVariables, EnvInitValueType, CustomTarget, BuildTarget,
+ CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs)
from ..coredata import UserFeatureOption
from ..interpreterbase import TYPE_var
from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo
@@ -16,6 +19,8 @@ from ..programs import ExternalProgram
# Helper definition for type checks that are `Optional[T]`
NoneType: T.Type[None] = type(None)
+if T.TYPE_CHECKING:
+ from typing_extensions import Literal
def in_set_validator(choices: T.Set[str]) -> T.Callable[[str], T.Optional[str]]:
"""Check that the choice given was one of the given set."""
@@ -131,7 +136,8 @@ REQUIRED_KW: KwargInfo[T.Union[bool, UserFeatureOption]] = KwargInfo(
DISABLER_KW: KwargInfo[bool] = KwargInfo('disabler', bool, default=False)
-def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None]) -> T.Optional[str]:
+def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None],
+ allow_dict_list: bool = True) -> T.Optional[str]:
def _splitter(v: str) -> T.Optional[str]:
split = v.split('=', 1)
if len(split) == 1:
@@ -152,12 +158,18 @@ def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Di
elif isinstance(value, dict):
# We don't need to spilt here, just do the type checking
for k, dv in value.items():
- if not isinstance(dv, str):
+ if allow_dict_list:
+ if any(i for i in listify(dv) if not isinstance(i, str)):
+ return f"Dictionary element {k} must be a string or list of strings not {dv!r}"
+ elif not isinstance(dv, str):
return f"Dictionary element {k} must be a string not {dv!r}"
# We know that otherwise we have an EnvironmentVariables object or None, and
# we're okay at this point
return None
+def _options_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None]) -> T.Optional[str]:
+ # Reusing the env validator is a littl overkill, but nicer than duplicating the code
+ return _env_validator(value, allow_dict_list=False)
def split_equal_string(input: str) -> T.Tuple[str, str]:
"""Split a string in the form `x=y`
@@ -167,18 +179,25 @@ def split_equal_string(input: str) -> T.Tuple[str, str]:
a, b = input.split('=', 1)
return (a, b)
+_FullEnvInitValueType = T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], EnvInitValueType, str, None]
-def _env_convertor(value: T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], T.Dict[str, str], str, None]) -> EnvironmentVariables:
+# Split _env_convertor() and env_convertor_with_method() to make mypy happy.
+# It does not want extra arguments in KwargInfo convertor callable.
+def env_convertor_with_method(value: _FullEnvInitValueType,
+ init_method: Literal['set', 'prepend', 'append'] = 'set',
+ separator: str = os.pathsep) -> EnvironmentVariables:
if isinstance(value, str):
- return EnvironmentVariables(dict([split_equal_string(value)]))
+ return EnvironmentVariables(dict([split_equal_string(value)]), init_method, separator)
elif isinstance(value, list):
- return EnvironmentVariables(dict(split_equal_string(v) for v in listify(value)))
+ return EnvironmentVariables(dict(split_equal_string(v) for v in listify(value)), init_method, separator)
elif isinstance(value, dict):
- return EnvironmentVariables(value)
+ return EnvironmentVariables(value, init_method, separator)
elif value is None:
return EnvironmentVariables()
return value
+def _env_convertor(value: _FullEnvInitValueType) -> EnvironmentVariables:
+ return env_convertor_with_method(value)
ENV_KW: KwargInfo[T.Union[EnvironmentVariables, T.List, T.Dict, str, None]] = KwargInfo(
'env',
@@ -230,8 +249,7 @@ OVERRIDE_OPTIONS_KW: KwargInfo[T.List[str]] = KwargInfo(
ContainerTypeInfo(list, str),
listify=True,
default=[],
- # Reusing the env validator is a littl overkill, but nicer than duplicating the code
- validator=_env_validator,
+ validator=_options_validator,
convertor=_override_options_convertor,
)
@@ -309,5 +327,10 @@ DEFAULT_OPTIONS: KwargInfo[T.List[str]] = KwargInfo(
ContainerTypeInfo(list, (str, IncludeDirs)),
listify=True,
default=[],
- validator=_env_validator,
+ validator=_options_validator,
)
+
+ENV_METHOD_KW = KwargInfo('method', str, default='set', since='0.62.0',
+ validator=in_set_validator({'set', 'prepend', 'append'}))
+
+ENV_SEPARATOR_KW = KwargInfo('separator', str, default=os.pathsep, since='0.62.0')
diff --git a/test cases/unit/91 devenv/meson.build b/test cases/unit/91 devenv/meson.build
index 90c4cee..f5c24d8 100644
--- a/test cases/unit/91 devenv/meson.build
+++ b/test cases/unit/91 devenv/meson.build
@@ -11,6 +11,11 @@ env = environment()
env.append('TEST_B', ['2', '3'], separator: '+')
meson.add_devenv(env)
+meson.add_devenv({'TEST_B': '0'}, separator: '+', method: 'prepend')
+
+env = environment({'TEST_B': ['4']}, separator: '+', method: 'append')
+meson.add_devenv(env)
+
# This exe links on a library built in another directory. On Windows this means
# PATH must contain builddir/subprojects/sub to be able to run it.
executable('app', 'main.c', dependencies: foo_dep, install: true)
diff --git a/test cases/unit/91 devenv/test-devenv.py b/test cases/unit/91 devenv/test-devenv.py
index de7eec2..8273805 100755
--- a/test cases/unit/91 devenv/test-devenv.py
+++ b/test cases/unit/91 devenv/test-devenv.py
@@ -6,7 +6,7 @@ from pathlib import Path
assert os.environ['MESON_DEVENV'] == '1'
assert os.environ['MESON_PROJECT_NAME'] == 'devenv'
assert os.environ['TEST_A'] == '1'
-assert os.environ['TEST_B'] == '1+2+3'
+assert os.environ['TEST_B'] == '0+1+2+3+4'
from mymod.mod import hello
assert hello == 'world'