summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-02-20 12:04:01 -0500
committerXavier Claessens <xclaesse@gmail.com>2021-03-16 09:00:50 -0400
commit598e968993da58c89f773dc732c708a54b0ec8db (patch)
tree7aff62faa24d580ea64fdcf65922c7f0d109a712
parent567c96b68b1dfe3cd6b52b0d26dfc78e5c0e6b76 (diff)
downloadmeson-598e968993da58c89f773dc732c708a54b0ec8db.tar.gz
Add `meson devenv` command and meson.add_devenv()
-rw-r--r--docs/markdown/Commands.md34
-rw-r--r--docs/markdown/Reference-manual.md13
-rw-r--r--docs/markdown/snippets/devenv.md29
-rw-r--r--mesonbuild/backend/backends.py19
-rw-r--r--mesonbuild/build.py3
-rw-r--r--mesonbuild/interpreter.py19
-rw-r--r--mesonbuild/mdevenv.py79
-rw-r--r--mesonbuild/mesonmain.py4
-rw-r--r--mesonbuild/msetup.py1
-rw-r--r--mesonbuild/scripts/cmd_or_ps.ps122
-rwxr-xr-xrun_mypy.py1
-rwxr-xr-xrun_unittests.py11
-rw-r--r--test cases/unit/91 devenv/main.c14
-rw-r--r--test cases/unit/91 devenv/meson.build12
-rw-r--r--test cases/unit/91 devenv/subprojects/sub/foo.c10
-rw-r--r--test cases/unit/91 devenv/subprojects/sub/meson.build6
-rwxr-xr-xtest cases/unit/91 devenv/test-devenv.py8
17 files changed, 277 insertions, 8 deletions
diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md
index 8989165a6..0751aede5 100644
--- a/docs/markdown/Commands.md
+++ b/docs/markdown/Commands.md
@@ -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 }}
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index 47e1afdd7..3af00c45a 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -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
diff --git a/docs/markdown/snippets/devenv.md b/docs/markdown/snippets/devenv.md
new file mode 100644
index 000000000..c3bac10e5
--- /dev/null
+++ b/docs/markdown/snippets/devenv.md
@@ -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.
+
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 9f7f45de7..e2297a30c 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -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
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index e3fad3d6f..b81e5dd94 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -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()
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 84d5e5cd5..3e39720ef 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -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
diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py
new file mode 100644
index 000000000..7594db2f2
--- /dev/null
+++ b/mesonbuild/mdevenv.py
@@ -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
diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py
index 173e9981b..208cfe414 100644
--- a/mesonbuild/mesonmain.py
+++ b/mesonbuild/mesonmain.py
@@ -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,
diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py
index f42d013ef..139b47609 100644
--- a/mesonbuild/msetup.py
+++ b/mesonbuild/msetup.py
@@ -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)
diff --git a/mesonbuild/scripts/cmd_or_ps.ps1 b/mesonbuild/scripts/cmd_or_ps.ps1
new file mode 100644
index 000000000..ccef8e84d
--- /dev/null
+++ b/mesonbuild/scripts/cmd_or_ps.ps1
@@ -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++
+}
diff --git a/run_mypy.py b/run_mypy.py
index 1c886f88f..a31afb20d 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -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',
diff --git a/run_unittests.py b/run_unittests.py
index 7e7ec9329..dd59ceb00 100755
--- a/run_unittests.py
+++ b/run_unittests.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):
'''
diff --git a/test cases/unit/91 devenv/main.c b/test cases/unit/91 devenv/main.c
new file mode 100644
index 000000000..271059356
--- /dev/null
+++ b/test cases/unit/91 devenv/main.c
@@ -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();
+}
diff --git a/test cases/unit/91 devenv/meson.build b/test cases/unit/91 devenv/meson.build
new file mode 100644
index 000000000..40b9c861d
--- /dev/null
+++ b/test cases/unit/91 devenv/meson.build
@@ -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)
diff --git a/test cases/unit/91 devenv/subprojects/sub/foo.c b/test cases/unit/91 devenv/subprojects/sub/foo.c
new file mode 100644
index 000000000..46cb845f9
--- /dev/null
+++ b/test cases/unit/91 devenv/subprojects/sub/foo.c
@@ -0,0 +1,10 @@
+#ifdef _WIN32
+ #define DO_EXPORT __declspec(dllexport)
+#else
+ #define DO_EXPORT
+#endif
+
+DO_EXPORT int foo(void)
+{
+ return 0;
+}
diff --git a/test cases/unit/91 devenv/subprojects/sub/meson.build b/test cases/unit/91 devenv/subprojects/sub/meson.build
new file mode 100644
index 000000000..5cb123209
--- /dev/null
+++ b/test cases/unit/91 devenv/subprojects/sub/meson.build
@@ -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))
diff --git a/test cases/unit/91 devenv/test-devenv.py b/test cases/unit/91 devenv/test-devenv.py
new file mode 100755
index 000000000..4e5be9776
--- /dev/null
+++ b/test cases/unit/91 devenv/test-devenv.py
@@ -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')