Skip to content

Commit

Permalink
Merge pull request #10990 from xclaesse/devenv
Browse files Browse the repository at this point in the history
devenv: various improvements
  • Loading branch information
jpakkane authored Dec 9, 2022
2 parents 7b2c47e + 09cbc53 commit 2ec3fe7
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 63 deletions.
2 changes: 2 additions & 0 deletions docs/markdown/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ These variables are set in environment in addition to those set using [[meson.ad
schemas is compiled. This is automatically set when using `gnome.compile_schemas()`.
Note that this requires GLib >= 2.64 when `gnome.compile_schemas()` is used in
more than one directory.
- `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
shell is bash, they will be automatically sourced.
Expand Down
15 changes: 15 additions & 0 deletions docs/markdown/snippets/devenv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Developer environment improvements

When cross compiling, the developer environment now sets all environment
variables for the HOST machine. It now also sets `QEMU_LD_PREFIX` to the
`sys_root` value from cross file if property is defined. That means that cross
compiled executables can often be run transparently on the build machine, for
example when cross compiling for aarch64 linux from x86_64 linux.

A new argument `--workdir` has been added, by default it is set to build
directory. For example, `meson devenv -C builddir --workdir .` can be used to
remain in the current dir (often source dir) instead.

`--dump` now prints shell commands like `FOO="/prepend/path:$FOO:/append/path"`,
using the litteral `$FOO` instead of current value of `FOO` from environment.
This makes easier to evaluate those expressions in a different environment.
28 changes: 14 additions & 14 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,10 +1108,7 @@ def create_test_serialisation(self, tests: T.List['Test']) -> T.List[TestSeriali
break

is_cross = self.environment.is_cross_build(test_for_machine)
if is_cross and self.environment.need_exe_wrapper():
exe_wrapper = self.environment.get_exe_wrapper()
else:
exe_wrapper = None
exe_wrapper = self.environment.get_exe_wrapper()
machine = self.environment.machines[exe.for_machine]
if machine.is_windows() or machine.is_cygwin():
extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget]] = []
Expand Down Expand Up @@ -1850,14 +1847,12 @@ def get_devenv(self) -> build.EnvironmentVariables:
env = build.EnvironmentVariables()
extra_paths = set()
library_paths = set()
build_machine = self.environment.machines[MachineChoice.BUILD]
host_machine = self.environment.machines[MachineChoice.HOST]
need_exe_wrapper = self.environment.need_exe_wrapper()
need_wine = need_exe_wrapper and host_machine.is_windows()
need_wine = not build_machine.is_windows() and host_machine.is_windows()
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 need_exe_wrapper or need_wine
in_default_dir = t.should_install() and not t.get_install_dir()[2]
if not can_run or not in_default_dir:
if t.for_machine != MachineChoice.HOST or not in_default_dir:
continue
tdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t))
if isinstance(t, build.Executable):
Expand All @@ -1874,18 +1869,23 @@ def get_devenv(self) -> build.EnvironmentVariables:
# LD_LIBRARY_PATH. This allows running system applications using
# that library.
library_paths.add(tdir)
if need_wine:
# Executable paths should be in both PATH and WINEPATH.
# - Having them in PATH makes bash completion find it,
# and make running "foo.exe" find it when wine-binfmt is installed.
# - Having them in WINEPATH makes "wine foo.exe" find it.
library_paths.update(extra_paths)
if library_paths:
if host_machine.is_windows() or host_machine.is_cygwin():
if need_wine:
env.prepend('WINEPATH', list(library_paths), separator=';')
elif host_machine.is_windows() or host_machine.is_cygwin():
extra_paths.update(library_paths)
elif host_machine.is_darwin():
env.prepend('DYLD_LIBRARY_PATH', list(library_paths))
else:
env.prepend('LD_LIBRARY_PATH', list(library_paths))
if extra_paths:
if need_wine:
env.prepend('WINEPATH', list(extra_paths), separator=';')
else:
env.prepend('PATH', list(extra_paths))
env.prepend('PATH', list(extra_paths))
return env

def compiler_to_generator(self, target: build.BuildTarget,
Expand Down
8 changes: 3 additions & 5 deletions mesonbuild/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
search_version, MesonBugException
)
from . import mlog
from .programs import (
ExternalProgram, EmptyExternalProgram
)
from .programs import ExternalProgram

from .envconfig import (
BinaryTable, MachineInfo, Properties, known_cpu_families, CMakeVariables,
Expand Down Expand Up @@ -852,7 +850,7 @@ def need_exe_wrapper(self, for_machine: MachineChoice = MachineChoice.HOST):
return value
return not machine_info_can_run(self.machines[for_machine])

def get_exe_wrapper(self) -> ExternalProgram:
def get_exe_wrapper(self) -> T.Optional[ExternalProgram]:
if not self.need_exe_wrapper():
return EmptyExternalProgram()
return None
return self.exe_wrapper
55 changes: 40 additions & 15 deletions mesonbuild/mdevenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from pathlib import Path
from . import build, minstall, dependencies
from .mesonlib import MesonException, RealPathAction, is_windows, setup_vsenv, OptionKey, quote_arg, get_wine_shortpath
from .mesonlib import (MesonException, is_windows, setup_vsenv, OptionKey,
get_wine_shortpath, MachineChoice)
from . import mlog

import typing as T
Expand All @@ -16,8 +17,10 @@
POWERSHELL_EXES = {'pwsh.exe', 'powershell.exe'}

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('-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',
Expand Down Expand Up @@ -45,15 +48,19 @@ 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) -> T.Tuple[T.Dict[str, str], T.Set[str]]:
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])

env = os.environ.copy()
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)
env = i.get_env(env, dump)
varnames |= i.get_names()

reduce_winepath(env)
Expand Down Expand Up @@ -90,7 +97,7 @@ def add_gdb_auto_load(autoload_path: Path, gdb_helper: str, fname: Path) -> None
except (FileExistsError, shutil.SameFileError):
pass

def write_gdb_script(privatedir: Path, install_data: 'InstallData') -> None:
def write_gdb_script(privatedir: Path, install_data: 'InstallData', workdir: Path) -> None:
if not shutil.which('gdb'):
return
bdir = privatedir.parent
Expand Down Expand Up @@ -120,26 +127,44 @@ def write_gdb_script(privatedir: Path, install_data: 'InstallData') -> None:
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)))
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.wd) / 'meson-private'
privatedir = Path(options.builddir) / '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)
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

devenv, varnames = get_env(b)
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}={quote_arg(devenv[name])}')
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)
write_gdb_script(privatedir, install_data, workdir)

setup_vsenv(b.need_vsenv)

Expand Down Expand Up @@ -182,7 +207,7 @@ def run(options: argparse.Namespace) -> int:
try:
return subprocess.call(args, close_fds=False,
env=devenv,
cwd=options.wd)
cwd=workdir)
except subprocess.CalledProcessError as e:
return e.returncode
except FileNotFoundError:
Expand Down
5 changes: 2 additions & 3 deletions mesonbuild/modules/gnome.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from ..mesonlib import (
MachineChoice, MesonException, OrderedSet, Popen_safe, join_args,
)
from ..programs import OverrideProgram, EmptyExternalProgram
from ..programs import OverrideProgram
from ..scripts.gettext import read_linguas

if T.TYPE_CHECKING:
Expand Down Expand Up @@ -1464,9 +1464,8 @@ def abs_filenames(files: T.Iterable['FileOrString']) -> T.Iterator[str]:
t_args.append(f'--{program_name}={path}')
if namespace:
t_args.append('--namespace=' + namespace)
# if not need_exe_wrapper, we get an EmptyExternalProgram. If none provided, we get NoneType
exe_wrapper = state.environment.get_exe_wrapper()
if not isinstance(exe_wrapper, (NoneType, EmptyExternalProgram)):
if exe_wrapper:
t_args.append('--run=' + ' '.join(exe_wrapper.get_command()))
t_args.append(f'--htmlargs={"@@".join(kwargs["html_args"])}')
t_args.append(f'--scanargs={"@@".join(kwargs["scan_args"])}')
Expand Down
19 changes: 0 additions & 19 deletions mesonbuild/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,25 +345,6 @@ def found(self) -> bool:
return False


class EmptyExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
'''
A program object that returns an empty list of commands. Used for cases
such as a cross file exe_wrapper to represent that it's not required.
'''

def __init__(self) -> None:
self.name = None
self.command = []
self.path = None

def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)

def found(self) -> bool:
return True


class OverrideProgram(ExternalProgram):

"""A script overriding a program."""
Expand Down
15 changes: 8 additions & 7 deletions mesonbuild/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,23 +119,24 @@ def prepend(self, name: str, values: T.List[str], separator: str = os.pathsep) -
self.envvars.append((self._prepend, name, values, separator))

@staticmethod
def _set(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str:
def _set(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str:
return separator.join(values)

@staticmethod
def _append(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str:
curr = env.get(name)
def _append(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str:
curr = env.get(name, default_value)
return separator.join(values if curr is None else [curr] + values)

@staticmethod
def _prepend(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str:
curr = env.get(name)
def _prepend(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str:
curr = env.get(name, default_value)
return separator.join(values if curr is None else values + [curr])

def get_env(self, full_env: T.MutableMapping[str, str]) -> T.Dict[str, str]:
def get_env(self, full_env: T.MutableMapping[str, str], dump: bool = False) -> T.Dict[str, str]:
env = full_env.copy()
for method, name, values, separator in self.envvars:
env[name] = method(env, name, values, separator)
default_value = f'${name}' if dump else None
env[name] = method(env, name, values, separator, default_value)
return env


Expand Down

0 comments on commit 2ec3fe7

Please sign in to comment.