Allow setting method/separator in environment() and meson.add_devenv()

pull/10047/head
Xavier Claessens 3 years ago committed by Xavier Claessens
parent ac4f8d0088
commit 6acfe48f32
  1. 20
      docs/markdown/snippets/devenv.md
  2. 24
      docs/yaml/builtins/meson.yaml
  3. 23
      docs/yaml/functions/environment.yaml
  4. 9
      mesonbuild/build.py
  5. 9
      mesonbuild/interpreter/interpreter.py
  6. 12
      mesonbuild/interpreter/interpreterobjects.py
  7. 14
      mesonbuild/interpreter/mesonmain.py
  8. 43
      mesonbuild/interpreter/type_checking.py
  9. 5
      test cases/unit/91 devenv/meson.build
  10. 2
      test cases/unit/91 devenv/test-devenv.py

@ -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)
```

@ -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.

@ -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.

@ -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}>"

@ -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)

@ -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)

@ -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)

@ -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')

@ -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)

@ -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'

Loading…
Cancel
Save