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.
This commit is contained in:
Xavier Claessens 2023-01-13 16:42:09 -05:00 committed by Eli Schwartz
parent 5a4168c410
commit 1a0eff0054
7 changed files with 83 additions and 14 deletions

View File

@ -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 - `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. 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. 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 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 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 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 python scripts that needs to load other python modules, `PYTHONPATH` may need
to be modified using `meson.add_devenv()`. 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 of `PATH` which allows running Windows executables using wine. Note that since
`WINEPATH` size is currently limited to 1024 characters, paths relative to the `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 root of build directory are used. That means current workdir must be the root of
build directory when running wine. 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 }} {{ devenv_arguments.inc }}

View File

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

View File

@ -21,8 +21,12 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
help='Path to build directory') help='Path to build directory')
parser.add_argument('--workdir', '-w', type=Path, default=None, parser.add_argument('--workdir', '-w', type=Path, default=None,
help='Directory to cd into before running (default: builddir, Since 1.0.0)') help='Directory to cd into before running (default: builddir, Since 1.0.0)')
parser.add_argument('--dump', action='store_true', parser.add_argument('--dump', nargs='?', const=True,
help='Only print required environment (Since 0.62.0)') 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', parser.add_argument('devcmd', nargs=argparse.REMAINDER, metavar='command',
help='Command to run in developer environment (default: interactive shell)') 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(';')) env['WINEPATH'] = get_wine_shortpath([winecmd], winepath.split(';'))
mlog.log('Meson detected wine and has set WINEPATH accordingly') 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 = build.EnvironmentVariables()
extra_env.set('MESON_DEVENV', ['1']) extra_env.set('MESON_DEVENV', ['1'])
extra_env.set('MESON_PROJECT_NAME', [b.project_name]) 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: if sysroot:
extra_env.set('QEMU_LD_PREFIX', [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() varnames = set()
for i in itertools.chain(b.devenv, {extra_env}): 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() varnames |= i.get_names()
reduce_winepath(env) 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)), mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)),
'or use', mlog.bold(f'--init-command {rel_path}')) '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: def run(options: argparse.Namespace) -> int:
privatedir = Path(options.builddir) / 'meson-private' privatedir = Path(options.builddir) / 'meson-private'
buildfile = privatedir / 'build.dat' buildfile = privatedir / 'build.dat'
@ -146,13 +157,16 @@ def run(options: argparse.Namespace) -> int:
b = build.load(options.builddir) b = build.load(options.builddir)
workdir = options.workdir or 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.dump:
if options.devcmd: if options.devcmd:
raise MesonException('--dump option does not allow running other command.') raise MesonException('--dump option does not allow running other command.')
for name in varnames: if options.dump is True:
print(f'{name}="{devenv[name]}"') dump(devenv, varnames, dump_fmt)
print(f'export {name}') else:
with open(options.dump, "w", encoding='utf-8') as output:
dump(devenv, varnames, dump_fmt, output)
return 0 return 0
if b.environment.need_exe_wrapper(): if b.environment.need_exe_wrapper():

View File

@ -135,10 +135,10 @@ class EnvironmentVariables(HoldableObject):
curr = env.get(name, default_value) curr = env.get(name, default_value)
return separator.join(values if curr is None else values + [curr]) 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() env = full_env.copy()
for method, name, values, separator in self.envvars: 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) env[name] = method(env, name, values, separator, default_value)
return env return env

View File

@ -15,3 +15,8 @@ meson.add_devenv(env)
# This exe links on a library built in another directory. On Windows this means # 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. # PATH must contain builddir/subprojects/sub to be able to run it.
executable('app', 'main.c', dependencies: foo_dep, install: true) 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)

View File

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

View File

@ -3973,6 +3973,33 @@ class AllPlatformTests(BasePlatformTests):
self._run(cmd + python_command + [script]) self._run(cmd + python_command + [script])
self.assertEqual('This is text.', self._run(cmd + [app]).strip()) 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): def test_clang_format_check(self):
if self.backend is not Backend.ninja: if self.backend is not Backend.ninja:
raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend') raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend')