The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

216 lines
8.8 KiB

import os, subprocess
import argparse
import tempfile
import shutil
import itertools
from pathlib import Path
from . import build, minstall, dependencies
from .mesonlib import MesonException, RealPathAction, is_windows, setup_vsenv, OptionKey, quote_arg
from . import mlog
import typing as T
if T.TYPE_CHECKING:
from .backends import InstallData
def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', dest='wd', action=RealPathAction,
help='Directory to cd into before running')
parser.add_argument('--dump', action='store_true',
help='Only print required environment (Since 0.62.0)')
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_wine_shortpath(build_dir: str, winecmd: str, wine_paths: T.List[str]) -> T.List[str]:
'''
WINEPATH size is limited to 1024 bytes which can easily be exceeded when
adding the path to every dll inside build directory. See
https://bugs.winehq.org/show_bug.cgi?id=45810.
To shorthen it as much as possible we use path relative to builddir
where possible and convert absolute paths to Windows shortpath (e.g.
"/usr/x86_64-w64-mingw32/lib" to "Z:\\usr\\X86_~FWL\\lib").
'''
rel_paths = []
abs_paths = []
builddir = Path(build_dir)
for p in wine_paths:
try:
rel = Path(p).relative_to(builddir)
rel_paths.append(str(rel))
except ValueError:
abs_paths.append(p)
if not abs_paths:
return rel_paths
with tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False) as bat_file:
bat_file.write('''
@ECHO OFF
for %%x in (%*) do (
echo|set /p=;%~sx
)
''')
try:
stdout = subprocess.check_output([winecmd, 'cmd', '/C', bat_file.name] + abs_paths,
encoding='utf-8', stderr=subprocess.DEVNULL)
return rel_paths + [p for p in set(stdout.split(';')) if p]
except subprocess.CalledProcessError:
return rel_paths + abs_paths
finally:
os.unlink(bat_file.name)
def reduce_winepath(build_dir: str, 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
winepath = ';'.join(get_wine_shortpath(build_dir, winecmd, winepath.split(';')))
if len(winepath) > 1024:
mlog.warning(f'WINEPATH exceeds 1024 characters which could cause issues:\n{winepath}')
env['WINEPATH'] = winepath
mlog.log('Meson detected wine and has set WINEPATH accordingly')
def get_env(b: build.Build, build_dir: 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])
env = os.environ.copy()
varnames = set()
for i in itertools.chain(b.devenv, {extra_env}):
env = i.get_env(env)
varnames |= i.get_names()
reduce_winepath(build_dir, 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') -> 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:
mlog.log('Meson detected GDB helpers and added config in', mlog.bold(str(gdbinit_path)))
def run(options: argparse.Namespace) -> int:
privatedir = Path(options.wd) / 'meson-private'
buildfile = privatedir / '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, varnames = get_env(b, options.wd)
if options.dump:
if options.command:
raise MesonException('--dump option does not allow running other command.')
for name in varnames:
print(f'{name}={quote_arg(devenv[name])}')
print(f'export {name}')
return 0
install_data = minstall.load_install_data(str(privatedir / 'install.dat'))
write_gdb_script(privatedir, install_data)
setup_vsenv(b.need_vsenv)
args = options.command
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 shell in ['powershell.exe', 'pwsh.exe']:
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)
try:
return subprocess.call(args, close_fds=False,
env=devenv,
cwd=options.wd)
except subprocess.CalledProcessError as e:
return e.returncode
except FileNotFoundError:
raise MesonException(f'Command not found: {args[0]}')