summaryrefslogtreecommitdiffstats
path: root/mesonbuild/mdevenv.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/mdevenv.py')
-rw-r--r--mesonbuild/mdevenv.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py
new file mode 100644
index 0000000..05779e8
--- /dev/null
+++ b/mesonbuild/mdevenv.py
@@ -0,0 +1,215 @@
+from __future__ import annotations
+
+import os, subprocess
+import argparse
+import tempfile
+import shutil
+import itertools
+
+from pathlib import Path
+from . import build, minstall, dependencies
+from .mesonlib import (MesonException, is_windows, setup_vsenv, OptionKey,
+ get_wine_shortpath, MachineChoice)
+from . import mlog
+
+import typing as T
+if T.TYPE_CHECKING:
+ from .backends import InstallData
+
+POWERSHELL_EXES = {'pwsh.exe', 'powershell.exe'}
+
+def add_arguments(parser: argparse.ArgumentParser) -> None:
+ parser.add_argument('-C', dest='builddir', type=Path, default='.',
+ 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('devcmd', nargs=argparse.REMAINDER, metavar='command',
+ help='Command to run in developer environment (default: interactive shell)')
+
+def get_windows_shell() -> T.Optional[str]:
+ mesonbuild = Path(__file__).parent
+ script = mesonbuild / 'scripts' / 'cmd_or_ps.ps1'
+ for shell in POWERSHELL_EXES:
+ try:
+ command = [shell, '-noprofile', '-executionpolicy', 'bypass', '-file', str(script)]
+ result = subprocess.check_output(command)
+ return result.decode().strip()
+ except (subprocess.CalledProcessError, OSError):
+ pass
+ return None
+
+def reduce_winepath(env: T.Dict[str, str]) -> None:
+ winepath = env.get('WINEPATH')
+ if not winepath:
+ return
+ winecmd = shutil.which('wine64') or shutil.which('wine')
+ if not winecmd:
+ return
+ 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]]:
+ extra_env = build.EnvironmentVariables()
+ extra_env.set('MESON_DEVENV', ['1'])
+ extra_env.set('MESON_PROJECT_NAME', [b.project_name])
+
+ sysroot = b.environment.properties[MachineChoice.HOST].get_sys_root()
+ if sysroot:
+ extra_env.set('QEMU_LD_PREFIX', [sysroot])
+
+ env = {} if dump else os.environ.copy()
+ varnames = set()
+ for i in itertools.chain(b.devenv, {extra_env}):
+ env = i.get_env(env, dump)
+ varnames |= i.get_names()
+
+ reduce_winepath(env)
+
+ return env, varnames
+
+def bash_completion_files(b: build.Build, install_data: 'InstallData') -> T.List[str]:
+ result = []
+ dep = dependencies.PkgConfigDependency('bash-completion', b.environment,
+ {'required': False, 'silent': True, 'version': '>=2.10'})
+ if dep.found():
+ prefix = b.environment.coredata.get_option(OptionKey('prefix'))
+ assert isinstance(prefix, str), 'for mypy'
+ datadir = b.environment.coredata.get_option(OptionKey('datadir'))
+ assert isinstance(datadir, str), 'for mypy'
+ datadir_abs = os.path.join(prefix, datadir)
+ completionsdir = dep.get_variable(pkgconfig='completionsdir', pkgconfig_define=['datadir', datadir_abs])
+ assert isinstance(completionsdir, str), 'for mypy'
+ completionsdir_path = Path(completionsdir)
+ for f in install_data.data:
+ if completionsdir_path in Path(f.install_path).parents:
+ result.append(f.path)
+ return result
+
+def add_gdb_auto_load(autoload_path: Path, gdb_helper: str, fname: Path) -> None:
+ # Copy or symlink the GDB helper into our private directory tree
+ destdir = autoload_path / fname.parent
+ destdir.mkdir(parents=True, exist_ok=True)
+ try:
+ if is_windows():
+ shutil.copy(gdb_helper, str(destdir / os.path.basename(gdb_helper)))
+ else:
+ os.symlink(gdb_helper, str(destdir / os.path.basename(gdb_helper)))
+ except (FileExistsError, shutil.SameFileError):
+ pass
+
+def write_gdb_script(privatedir: Path, install_data: 'InstallData', workdir: Path) -> None:
+ if not shutil.which('gdb'):
+ return
+ bdir = privatedir.parent
+ autoload_basedir = privatedir / 'gdb-auto-load'
+ autoload_path = Path(autoload_basedir, *bdir.parts[1:])
+ have_gdb_helpers = False
+ for d in install_data.data:
+ if d.path.endswith('-gdb.py') or d.path.endswith('-gdb.gdb') or d.path.endswith('-gdb.scm'):
+ # This GDB helper is made for a specific shared library, search if
+ # we have it in our builddir.
+ libname = Path(d.path).name.rsplit('-', 1)[0]
+ for t in install_data.targets:
+ path = Path(t.fname)
+ if path.name == libname:
+ add_gdb_auto_load(autoload_path, d.path, path)
+ have_gdb_helpers = True
+ if have_gdb_helpers:
+ gdbinit_line = f'add-auto-load-scripts-directory {autoload_basedir}\n'
+ gdbinit_path = bdir / '.gdbinit'
+ first_time = False
+ try:
+ with gdbinit_path.open('r+', encoding='utf-8') as f:
+ if gdbinit_line not in f.readlines():
+ f.write(gdbinit_line)
+ first_time = True
+ except FileNotFoundError:
+ gdbinit_path.write_text(gdbinit_line, encoding='utf-8')
+ first_time = True
+ if first_time:
+ gdbinit_path = gdbinit_path.resolve()
+ workdir_path = workdir.resolve()
+ rel_path = gdbinit_path.relative_to(workdir_path)
+ mlog.log('Meson detected GDB helpers and added config in', mlog.bold(str(rel_path)))
+ mlog.log('To load it automatically you might need to:')
+ mlog.log(' - Add', mlog.bold(f'add-auto-load-safe-path {gdbinit_path.parent}'),
+ 'in', mlog.bold('~/.gdbinit'))
+ if gdbinit_path.parent != workdir_path:
+ mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)),
+ 'or use', mlog.bold(f'--init-command {rel_path}'))
+
+def run(options: argparse.Namespace) -> int:
+ privatedir = Path(options.builddir) / 'meson-private'
+ buildfile = privatedir / 'build.dat'
+ if not buildfile.is_file():
+ raise MesonException(f'Directory {options.builddir!r} does not seem to be a Meson build directory.')
+ b = build.load(options.builddir)
+ workdir = options.workdir or options.builddir
+
+ setup_vsenv(b.need_vsenv) # Call it before get_env to get vsenv vars as well
+ devenv, varnames = get_env(b, options.dump)
+ 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}')
+ return 0
+
+ if b.environment.need_exe_wrapper():
+ m = 'An executable wrapper could be required'
+ exe_wrapper = b.environment.get_exe_wrapper()
+ if exe_wrapper:
+ cmd = ' '.join(exe_wrapper.get_command())
+ m += f': {cmd}'
+ mlog.log(m)
+
+ install_data = minstall.load_install_data(str(privatedir / 'install.dat'))
+ write_gdb_script(privatedir, install_data, workdir)
+
+ args = options.devcmd
+ if not args:
+ prompt_prefix = f'[{b.project_name}]'
+ shell_env = os.environ.get("SHELL")
+ # Prefer $SHELL in a MSYS2 bash despite it being Windows
+ if shell_env and os.path.exists(shell_env):
+ args = [shell_env]
+ elif is_windows():
+ shell = get_windows_shell()
+ if not shell:
+ mlog.warning('Failed to determine Windows shell, fallback to cmd.exe')
+ if shell in POWERSHELL_EXES:
+ args = [shell, '-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]:
+ # Let the GC remove the tmp file
+ tmprc = tempfile.NamedTemporaryFile(mode='w')
+ tmprc.write('[ -e ~/.bashrc ] && . ~/.bashrc\n')
+ if not os.environ.get("MESON_DISABLE_PS1_OVERRIDE"):
+ tmprc.write(f'export PS1="{prompt_prefix} $PS1"\n')
+ for f in bash_completion_files(b, install_data):
+ tmprc.write(f'. "{f}"\n')
+ tmprc.flush()
+ args.append("--rcfile")
+ args.append(tmprc.name)
+ else:
+ # Try to resolve executable using devenv's PATH
+ abs_path = shutil.which(args[0], path=devenv.get('PATH', None))
+ args[0] = abs_path or args[0]
+
+ try:
+ return subprocess.call(args, close_fds=False,
+ env=devenv,
+ cwd=workdir)
+ except subprocess.CalledProcessError as e:
+ return e.returncode
+ except FileNotFoundError:
+ raise MesonException(f'Command not found: {args[0]}')