devenv: Allow dumping into file and select a format

It is often more useful to generate shell script than dumping to stdout.
It is also important to be able to select the shell format.

Formats currently implemented:
- sh: Basic VAR=prepend_value:$VAR
- export: Same as 'sh', but also export VAR
- vscode: Same as 'sh', but without substitutions because they don't
  seems to work. To be used in launch.json's envFile.
pull/11288/head
Xavier Claessens 2 years ago committed by Eli Schwartz
parent 5a4168c410
commit 1a0eff0054
  1. 17
      docs/markdown/Commands.md
  2. 11
      docs/markdown/snippets/devenv.md
  3. 32
      mesonbuild/mdevenv.py
  4. 4
      mesonbuild/utils/core.py
  5. 5
      test cases/unit/90 devenv/meson.build
  6. 1
      test cases/unit/90 devenv/test-devenv.py
  7. 27
      unittests/allplatformstests.py

@ -348,20 +348,31 @@ These variables are set in environment in addition to those set using [[meson.ad
- `QEMU_LD_PREFIX` *Since 1.0.0* is set to the `sys_root` value from cross file
when cross compiling and that property is defined.
Since *Since 0.62.0* if bash-completion scripts are being installed and the
*Since 0.62.0* if bash-completion scripts are being installed and the
shell is bash, they will be automatically sourced.
Since *Since 0.62.0* when GDB helper scripts (*-gdb.py, *-gdb.gdb, and *-gdb.csm)
*Since 0.62.0* when GDB helper scripts (*-gdb.py, *-gdb.gdb, and *-gdb.csm)
are installed with a library name that matches one being built, Meson adds the
needed auto-load commands into `<builddir>/.gdbinit` file. When running gdb from
top build directory, that file is loaded by gdb automatically. In the case of
python scripts that needs to load other python modules, `PYTHONPATH` may need
to be modified using `meson.add_devenv()`.
Since *Since 0.63.0* when cross compiling for Windows `WINEPATH` is used instead
*Since 0.63.0* when cross compiling for Windows `WINEPATH` is used instead
of `PATH` which allows running Windows executables using wine. Note that since
`WINEPATH` size is currently limited to 1024 characters, paths relative to the
root of build directory are used. That means current workdir must be the root of
build directory when running wine.
*Since 1.1.0* `meson devenv --dump [<filename>]` command takes an optional
filename argument to write the environment into a file instead of printing to
stdout.
*Since 1.1.0* `--dump-format` argument has been added to select which shell
format should be used. There are currently 3 formats supported:
- `sh`: Lines are in the format `VAR=/prepend:$VAR:/append`.
- `export`: Same as `sh` but with extra `export VAR` lines.
- `vscode`: Same as `sh` but without `$VAR` substitution because they do not
seems to be properly supported by vscode.
{{ devenv_arguments.inc }}

@ -0,0 +1,11 @@
## Dump devenv into file and select format
`meson devenv --dump [<filename>]` command now takes an option filename argument
to write the environment into a file instead of printing to stdout.
A new `--dump-format` argument has been added to select which shell format
should be used. There are currently 3 formats supported:
- `sh`: Lines are in the format `VAR=/prepend:$VAR:/append`.
- `export`: Same as `sh` but with extra `export VAR` lines.
- `vscode`: Same as `sh` but without `$VAR` substitution because they do not
seems to be properly supported by vscode.

@ -21,8 +21,12 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
help='Path to build directory')
parser.add_argument('--workdir', '-w', type=Path, default=None,
help='Directory to cd into before running (default: builddir, Since 1.0.0)')
parser.add_argument('--dump', action='store_true',
help='Only print required environment (Since 0.62.0)')
parser.add_argument('--dump', nargs='?', const=True,
help='Only print required environment (Since 0.62.0) ' +
'Takes an optional file path (Since 1.1.0)')
parser.add_argument('--dump-format', default='export',
choices=['sh', 'export', 'vscode'],
help='Format used with --dump (Since 1.1.0)')
parser.add_argument('devcmd', nargs=argparse.REMAINDER, metavar='command',
help='Command to run in developer environment (default: interactive shell)')
@ -48,7 +52,7 @@ def reduce_winepath(env: T.Dict[str, str]) -> None:
env['WINEPATH'] = get_wine_shortpath([winecmd], winepath.split(';'))
mlog.log('Meson detected wine and has set WINEPATH accordingly')
def get_env(b: build.Build, dump: bool) -> T.Tuple[T.Dict[str, str], T.Set[str]]:
def get_env(b: build.Build, dump_fmt: T.Optional[str]) -> T.Tuple[T.Dict[str, str], T.Set[str]]:
extra_env = build.EnvironmentVariables()
extra_env.set('MESON_DEVENV', ['1'])
extra_env.set('MESON_PROJECT_NAME', [b.project_name])
@ -57,10 +61,11 @@ def get_env(b: build.Build, dump: bool) -> T.Tuple[T.Dict[str, str], T.Set[str]]
if sysroot:
extra_env.set('QEMU_LD_PREFIX', [sysroot])
env = {} if dump else os.environ.copy()
env = {} if dump_fmt else os.environ.copy()
default_fmt = '${0}' if dump_fmt in {'sh', 'export'} else None
varnames = set()
for i in itertools.chain(b.devenv, {extra_env}):
env = i.get_env(env, dump)
env = i.get_env(env, default_fmt)
varnames |= i.get_names()
reduce_winepath(env)
@ -138,6 +143,12 @@ def write_gdb_script(privatedir: Path, install_data: 'InstallData', workdir: Pat
mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)),
'or use', mlog.bold(f'--init-command {rel_path}'))
def dump(devenv: T.Dict[str, str], varnames: T.Set[str], dump_format: T.Optional[str], output: T.Optional[T.TextIO] = None) -> None:
for name in varnames:
print(f'{name}="{devenv[name]}"', file=output)
if dump_format == 'export':
print(f'export {name}', file=output)
def run(options: argparse.Namespace) -> int:
privatedir = Path(options.builddir) / 'meson-private'
buildfile = privatedir / 'build.dat'
@ -146,13 +157,16 @@ def run(options: argparse.Namespace) -> int:
b = build.load(options.builddir)
workdir = options.workdir or options.builddir
devenv, varnames = get_env(b, options.dump)
dump_fmt = options.dump_format if options.dump else None
devenv, varnames = get_env(b, dump_fmt)
if options.dump:
if options.devcmd:
raise MesonException('--dump option does not allow running other command.')
for name in varnames:
print(f'{name}="{devenv[name]}"')
print(f'export {name}')
if options.dump is True:
dump(devenv, varnames, dump_fmt)
else:
with open(options.dump, "w", encoding='utf-8') as output:
dump(devenv, varnames, dump_fmt, output)
return 0
if b.environment.need_exe_wrapper():

@ -135,10 +135,10 @@ class EnvironmentVariables(HoldableObject):
curr = env.get(name, default_value)
return separator.join(values if curr is None else values + [curr])
def get_env(self, full_env: EnvironOrDict, dump: bool = False) -> T.Dict[str, str]:
def get_env(self, full_env: EnvironOrDict, default_fmt: T.Optional[str] = None) -> T.Dict[str, str]:
env = full_env.copy()
for method, name, values, separator in self.envvars:
default_value = f'${name}' if dump else None
default_value = default_fmt.format(name) if default_fmt else None
env[name] = method(env, name, values, separator, default_value)
return env

@ -15,3 +15,8 @@ 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)
env = environment({'TEST_C': ['/prefix']}, method: 'prepend')
meson.add_devenv(env)
env = environment({'TEST_C': ['/suffix']}, method: 'append')
meson.add_devenv(env)

@ -6,3 +6,4 @@ assert os.environ['MESON_DEVENV'] == '1'
assert os.environ['MESON_PROJECT_NAME'] == 'devenv'
assert os.environ['TEST_A'] == '1'
assert os.environ['TEST_B'] == '0+1+2+3+4'
assert os.environ['TEST_C'] == os.pathsep.join(['/prefix', '/suffix'])

@ -3973,6 +3973,33 @@ class AllPlatformTests(BasePlatformTests):
self._run(cmd + python_command + [script])
self.assertEqual('This is text.', self._run(cmd + [app]).strip())
cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump']
o = self._run(cmd)
expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
self.assertIn(f'TEST_C="{expected}"', o)
self.assertIn('export TEST_C', o)
cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', '--dump-format', 'sh']
o = self._run(cmd)
expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
self.assertIn(f'TEST_C="{expected}"', o)
self.assertNotIn('export', o)
cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', '--dump-format', 'vscode']
o = self._run(cmd)
expected = os.pathsep.join(['/prefix', '/suffix'])
self.assertIn(f'TEST_C="{expected}"', o)
self.assertNotIn('export', o)
fname = os.path.join(self.builddir, 'dump.env')
cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', fname]
o = self._run(cmd)
self.assertEqual(o, '')
o = Path(fname).read_text()
expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
self.assertIn(f'TEST_C="{expected}"', o)
self.assertIn('export TEST_C', o)
def test_clang_format_check(self):
if self.backend is not Backend.ninja:
raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend')

Loading…
Cancel
Save