From 1a0eff005483b63259e365bd4d51be7c4bd3b729 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Fri, 13 Jan 2023 16:42:09 -0500 Subject: [PATCH] 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. --- docs/markdown/Commands.md | 17 ++++++++++--- docs/markdown/snippets/devenv.md | 11 ++++++++ mesonbuild/mdevenv.py | 32 +++++++++++++++++------- mesonbuild/utils/core.py | 4 +-- test cases/unit/90 devenv/meson.build | 5 ++++ test cases/unit/90 devenv/test-devenv.py | 1 + unittests/allplatformstests.py | 27 ++++++++++++++++++++ 7 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 docs/markdown/snippets/devenv.md diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md index 4a00c4f4c..b7b72c495 100644 --- a/docs/markdown/Commands.md +++ b/docs/markdown/Commands.md @@ -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 `/.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 []` 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 }} diff --git a/docs/markdown/snippets/devenv.md b/docs/markdown/snippets/devenv.md new file mode 100644 index 000000000..c2ce2adec --- /dev/null +++ b/docs/markdown/snippets/devenv.md @@ -0,0 +1,11 @@ +## Dump devenv into file and select format + +`meson devenv --dump []` 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. diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py index b25d73b81..ddf53d92e 100644 --- a/mesonbuild/mdevenv.py +++ b/mesonbuild/mdevenv.py @@ -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(): diff --git a/mesonbuild/utils/core.py b/mesonbuild/utils/core.py index 310061ab4..b0c2b312f 100644 --- a/mesonbuild/utils/core.py +++ b/mesonbuild/utils/core.py @@ -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 diff --git a/test cases/unit/90 devenv/meson.build b/test cases/unit/90 devenv/meson.build index 3b0bb6a24..72d8fdc33 100644 --- a/test cases/unit/90 devenv/meson.build +++ b/test cases/unit/90 devenv/meson.build @@ -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) diff --git a/test cases/unit/90 devenv/test-devenv.py b/test cases/unit/90 devenv/test-devenv.py index 75497ff3b..07bcf612a 100755 --- a/test cases/unit/90 devenv/test-devenv.py +++ b/test cases/unit/90 devenv/test-devenv.py @@ -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']) diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 351f79de2..a98cd9414 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -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')