Add `meson devenv` command and meson.add_devenv()

pull/8539/head
Xavier Claessens 4 years ago committed by Xavier Claessens
parent 567c96b68b
commit 598e968993
  1. 34
      docs/markdown/Commands.md
  2. 13
      docs/markdown/Reference-manual.md
  3. 29
      docs/markdown/snippets/devenv.md
  4. 19
      mesonbuild/backend/backends.py
  5. 3
      mesonbuild/build.py
  6. 19
      mesonbuild/interpreter.py
  7. 79
      mesonbuild/mdevenv.py
  8. 4
      mesonbuild/mesonmain.py
  9. 1
      mesonbuild/msetup.py
  10. 22
      mesonbuild/scripts/cmd_or_ps.ps1
  11. 1
      run_mypy.py
  12. 11
      run_unittests.py
  13. 14
      test cases/unit/91 devenv/main.c
  14. 12
      test cases/unit/91 devenv/meson.build
  15. 10
      test cases/unit/91 devenv/subprojects/sub/foo.c
  16. 6
      test cases/unit/91 devenv/subprojects/sub/meson.build
  17. 8
      test cases/unit/91 devenv/test-devenv.py

@ -263,3 +263,37 @@ An utility to manage WrapDB dependencies.
{{ wrap_arguments.inc }}
See [the WrapDB tool documentation](Using-wraptool.md) for more info.
### devenv
*(since 0.58.0)*
{{ devenv_usage.inc }}
Runs a command, or open interactive shell if no command is provided, with
environment setup to run project from the build directory, without installation.
We automatically handle `bash` and set `$PS1` accordingly. If the automatic `$PS1`
override is not desired (maybe you have a fancy custom prompt), set the
`$MESON_DISABLE_PS1_OVERRIDE` environment variable and use `$MESON_PROJECT_NAME`
when setting the custom prompt, for example with a snippet like the following:
```bash
...
if [[ -n "${MESON_PROJECT_NAME-}" ]];
then
PS1+="[ ${MESON_PROJECT_NAME} ]"
fi
...
```
These variables are set in environment in addition to those set using `meson.add_devenv()`:
- `MESON_DEVENV` is defined to `'1'`.
- `MESON_PROJECT_NAME` is defined to the main project's name.
- `PKG_CONFIG_PATH` includes the directory where Meson generates `-uninstalled.pc`
files.
- `PATH` includes every directory where there is an executable that would be
installed into `bindir`. On windows it also includes every directory where there
is a DLL needed to run those executables.
{{ devenv_arguments.inc }}

@ -2055,6 +2055,19 @@ the following methods.
- `version()`: return a string with the version of Meson.
- `add_devenv()`: *(Since 0.58.0)* add an [`environment()`](#environment) object
to the list of environments that will be applied when using [`meson devenv`](Commands.md#devenv)
command line. This is useful for developpers who wish to use the project without
installing it, it is often needed to set for example the path to plugins
directory, etc. Alternatively, a list or dictionary can be passed as first
argument.
``` meson
devenv = environment()
devenv.set('PLUGINS_PATH', meson.current_build_dir())
...
meson.add_devenv(devenv)
```
### `build_machine` object
Provides information about the build machine — the machine that is

@ -0,0 +1,29 @@
## Developer environment
New method `meson.add_devenv()` adds an [`environment()`](#environment) object
to the list of environments that will be applied when using `meson devenv`
command line. This is useful for developpers who wish to use the project without
installing it, it is often needed to set for example the path to plugins
directory, etc. Alternatively, a list or dictionary can be passed as first
argument.
``` meson
devenv = environment()
devenv.set('PLUGINS_PATH', meson.current_build_dir())
...
meson.add_devenv(devenv)
```
New command line has been added: `meson devenv -C builddir [<command>]`.
It runs a command, or open interactive shell if no command is provided, with
environment setup to run project from the build directory, without installation.
These variables are set in environment in addition to those set using `meson.add_devenv()`:
- `MESON_DEVENV` is defined to `'1'`.
- `MESON_PROJECT_NAME` is defined to the main project's name.
- `PKG_CONFIG_PATH` includes the directory where Meson generates `-uninstalled.pc`
files.
- `PATH` includes every directory where there is an executable that would be
installed into `bindir`. On windows it also includes every directory where there
is a DLL needed to run those executables.

@ -1491,3 +1491,22 @@ class Backend:
}]
return []
def get_devenv(self) -> build.EnvironmentVariables:
env = build.EnvironmentVariables()
extra_paths = set()
for t in self.build.get_targets().values():
cross_built = not self.environment.machines.matches_build_machine(t.for_machine)
can_run = not cross_built or not self.environment.need_exe_wrapper()
in_bindir = t.should_install() and not t.get_install_dir(self.environment)[1]
if isinstance(t, build.Executable) and can_run and in_bindir:
# Add binaries that are going to be installed in bindir into PATH
# so they get used by default instead of searching on system when
# in developer environment.
extra_paths.add(os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)))
if mesonlib.is_windows() or mesonlib.is_cygwin():
# On windows we cannot rely on rpath to run executables from build
# directory. We have to add in PATH the location of every DLL needed.
extra_paths.update(self.determine_windows_extra_paths(t, []))
env.prepend('PATH', list(extra_paths))
return env

@ -229,6 +229,7 @@ class Build:
self.find_overrides = {}
self.searched_programs = set() # The list of all programs that have been searched for.
self.dependency_overrides = PerMachine({}, {})
self.devenv: T.List[EnvironmentVariables] = []
def get_build_targets(self):
build_targets = OrderedDict()
@ -393,7 +394,7 @@ class ExtractedObjects:
]
class EnvironmentVariables:
def __init__(self):
def __init__(self) -> None:
self.envvars = []
# The set of all env vars we have operations for. Only used for self.has_name()
self.varnames = set()

@ -32,7 +32,6 @@ from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabl
from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
from .interpreterbase import ObjectHolder, MesonVersionString
from .interpreterbase import TYPE_var, TYPE_nkwargs
from .interpreterbase import typed_pos_args
from .modules import ModuleReturnValue, ModuleObject, ModuleState
from .cmake import CMakeInterpreter
from .backend.backends import TestProtocol, Backend, ExecutableSerialisation
@ -256,8 +255,8 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En
if isinstance(initial_values, dict):
for k, v in initial_values.items():
self.set_method([k, v], {})
elif isinstance(initial_values, list):
for e in initial_values:
elif initial_values is not None:
for e in mesonlib.stringlistify(initial_values):
if '=' not in e:
raise InterpreterException('Env var definition must be of type key=val.')
(k, val) = e.split('=', 1)
@ -266,8 +265,6 @@ class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder[build.En
if ' ' in k:
raise InterpreterException('Env var key must not have spaces in it.')
self.set_method([k, val], {})
elif initial_values:
raise AssertionError('Unsupported EnvironmentVariablesHolder initial_values')
def __repr__(self) -> str:
repr_str = "<{0}: {1}>"
@ -1915,6 +1912,7 @@ class MesonMain(InterpreterObject):
'get_external_property': self.get_external_property_method,
'has_external_property': self.has_external_property_method,
'backend': self.backend_method,
'add_devenv': self.add_devenv_method,
})
def _find_source_script(self, prog: T.Union[str, mesonlib.File, ExecutableHolder], args):
@ -2241,6 +2239,16 @@ class MesonMain(InterpreterObject):
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
return prop_name in self.interpreter.environment.properties[for_machine]
@FeatureNew('add_devenv', '0.58.0')
@noKwargs
@typed_pos_args('add_devenv', (str, list, dict, EnvironmentVariablesHolder))
def add_devenv_method(self, args: T.Union[str, list, dict, EnvironmentVariablesHolder], kwargs: T.Dict[str, T.Any]) -> None:
env = args[0]
if isinstance(env, (str, list, dict)):
env = EnvironmentVariablesHolder(env)
self.build.devenv.append(env.held_object)
known_library_kwargs = (
build.known_shlib_kwargs |
build.known_stlib_kwargs
@ -4084,7 +4092,6 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
env = EnvironmentVariablesHolder(envlist)
env = env.held_object
else:
envlist = listify(envlist)
# Convert from array to environment object
env = EnvironmentVariablesHolder(envlist)
env = env.held_object

@ -0,0 +1,79 @@
import os, subprocess
import argparse
import tempfile
from pathlib import Path
from . import build
from .mesonlib import MesonException, is_windows
import typing as T
def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running')
parser.add_argument('command', nargs=argparse.REMAINDER,
help='Command to run in developer environment (default: interactive shell)')
def get_windows_shell() -> str:
mesonbuild = Path(__file__).parent
script = mesonbuild / 'scripts' / 'cmd_or_ps.ps1'
command = ['powershell.exe', '-noprofile', '-executionpolicy', 'bypass', '-file', str(script)]
result = subprocess.check_output(command)
return result.decode().strip()
def get_env(b: build.Build, build_dir: str) -> T.Dict[str, str]:
env = os.environ.copy()
for i in b.devenv:
env = i.get_env(env)
extra_env = build.EnvironmentVariables()
extra_env.set('MESON_DEVENV', ['1'])
extra_env.set('MESON_PROJECT_NAME', [b.project_name])
meson_uninstalled = Path(build_dir) / 'meson-uninstalled'
if meson_uninstalled.is_dir():
extra_env.prepend('PKG_CONFIG_PATH', [str(meson_uninstalled)])
return extra_env.get_env(env)
def run(options: argparse.Namespace) -> int:
options.wd = os.path.abspath(options.wd)
buildfile = Path(options.wd) / 'meson-private' / 'build.dat'
if not buildfile.is_file():
raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.')
b = build.load(options.wd)
devenv = get_env(b, options.wd)
args = options.command
if not args:
prompt_prefix = f'[{b.project_name}]'
if is_windows():
shell = get_windows_shell()
if shell == 'powershell.exe':
args = ['powershell.exe']
args += ['-NoLogo', '-NoExit']
prompt = f'function global:prompt {{ "{prompt_prefix} PS " + $PWD + "> "}}'
args += ['-Command', prompt]
else:
args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")]
args += ['/k', f'prompt {prompt_prefix} $P$G']
else:
args = [os.environ.get("SHELL", os.path.realpath("/bin/sh"))]
if "bash" in args[0] and not os.environ.get("MESON_DISABLE_PS1_OVERRIDE"):
tmprc = tempfile.NamedTemporaryFile(mode='w')
bashrc = os.path.expanduser('~/.bashrc')
if os.path.exists(bashrc):
tmprc.write(f'. {bashrc}\n')
tmprc.write(f'export PS1="{prompt_prefix} $PS1"')
tmprc.flush()
# Let the GC remove the tmp file
args.append("--rcfile")
args.append(tmprc.name)
try:
return subprocess.call(args, close_fds=False,
env=devenv,
cwd=options.wd)
except subprocess.CalledProcessError as e:
return e.returncode

@ -22,7 +22,7 @@ import shutil
from . import mesonlib
from . import mlog
from . import mconf, mdist, minit, minstall, mintro, msetup, mtest, rewriter, msubprojects, munstable_coredata, mcompile
from . import mconf, mdist, minit, minstall, mintro, msetup, mtest, rewriter, msubprojects, munstable_coredata, mcompile, mdevenv
from .mesonlib import MesonException
from .environment import detect_msys2_arch
from .wrap import wraptool
@ -64,6 +64,8 @@ class CommandLineParser:
help_msg='Modify the project definition')
self.add_command('compile', mcompile.add_arguments, mcompile.run,
help_msg='Build the project')
self.add_command('devenv', mdevenv.add_arguments, mdevenv.run,
help_msg='Run commands in developer environment')
# Hidden commands
self.add_command('runpython', self.add_runpython_arguments, self.run_runpython_command,

@ -243,6 +243,7 @@ class MesonApp:
profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname)
else:
intr.backend.generate()
b.devenv.append(intr.backend.get_devenv())
build.save(b, dumpfile)
if env.first_invocation:
coredata.write_cmd_line_file(self.build_dir, self.options)

@ -0,0 +1,22 @@
# Copyied from GStreamer project
# Author: Seungha Yang <seungha.yang@navercorp.com>
$i=1
$ppid=(gwmi win32_process -Filter "processid='$pid'").parentprocessid
$pname=(Get-Process -id $ppid).Name
While($true) {
if($pname -eq "cmd" -Or $pname -eq "powershell") {
Write-Host ("{0}.exe" -f $pname)
Break
}
# 10 times iteration seems to be sufficient
if($i -gt 10) {
Break
}
# not found yet, find grand parant
$ppid=(gwmi win32_process -Filter "processid='$ppid'").parentprocessid
$pname=(Get-Process -id $ppid).Name
$i++
}

@ -25,6 +25,7 @@ modules = [
'mesonbuild/interpreterbase.py',
'mesonbuild/linkers.py',
'mesonbuild/mcompile.py',
'mesonbuild/mdevenv.py',
'mesonbuild/mesonlib/platform.py',
'mesonbuild/mesonlib/universal.py',
'mesonbuild/minit.py',

@ -5579,6 +5579,17 @@ class AllPlatformTests(BasePlatformTests):
self.setconf('-Duse-sub=true')
self.build()
def test_devenv(self):
testdir = os.path.join(self.unit_test_dir, '91 devenv')
self.init(testdir)
self.build()
cmd = self.meson_command + ['devenv', '-C', self.builddir]
script = os.path.join(testdir, 'test-devenv.py')
app = os.path.join(self.builddir, 'app')
self._run(cmd + python_command + [script])
self.assertEqual('This is text.', self._run(cmd + [app]).strip())
class FailureTests(BasePlatformTests):
'''

@ -0,0 +1,14 @@
#include <stdio.h>
#ifdef _WIN32
#define DO_IMPORT __declspec(dllimport)
#else
#define DO_IMPORT
#endif
DO_IMPORT int foo(void);
int main(void) {
printf("This is text.\n");
return foo();
}

@ -0,0 +1,12 @@
project('devenv', 'c')
meson.add_devenv('TEST_A=1')
foo_dep = dependency('foo', fallback: 'sub')
env = environment()
env.append('TEST_B', ['2', '3'], separator: '+')
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)

@ -0,0 +1,10 @@
#ifdef _WIN32
#define DO_EXPORT __declspec(dllexport)
#else
#define DO_EXPORT
#endif
DO_EXPORT int foo(void)
{
return 0;
}

@ -0,0 +1,6 @@
project('sub', 'c')
meson.add_devenv({'TEST_B': '1'})
libfoo = shared_library('foo', 'foo.c')
meson.override_dependency('foo', declare_dependency(link_with: libfoo))

@ -0,0 +1,8 @@
#! /usr/bin/python
import os
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')
Loading…
Cancel
Save