diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2021-01-26 13:51:23 -0500 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2021-01-30 09:51:06 +0000 |
commit | 0626465ea8aa65b10776d5c4064e881fe0d6fa25 (patch) | |
tree | be11f7029bd7bd418494d45cf7f3de52cbaf435f | |
parent | c321339b24f896d02b0839d1b1e5008eae405858 (diff) | |
download | meson-0626465ea8aa65b10776d5c4064e881fe0d6fa25.zip meson-0626465ea8aa65b10776d5c4064e881fe0d6fa25.tar.gz meson-0626465ea8aa65b10776d5c4064e881fe0d6fa25.tar.bz2 |
Fix executable as script on Windows
On Windows this would fail because of missing DLL:
```
mylib = library(...)
exe = executable(..., link_with: mylib)
meson.add_install_script(exe)
```
The reason is on Windows we cannot rely on rpath to find libraries from
build directory, they are searched in $PATH. We already have all that
mechanism in place for custom_target() using ExecutableSerialisation
class, so reuse it for install/dist/postconf scripts too.
This has bonus side effect to also use exe_wrapper for those scripts.
Fixes: #8187
-rw-r--r-- | mesonbuild/backend/backends.py | 69 | ||||
-rw-r--r-- | mesonbuild/build.py | 8 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 15 | ||||
-rw-r--r-- | mesonbuild/mdist.py | 9 | ||||
-rw-r--r-- | mesonbuild/minstall.py | 10 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 4 | ||||
-rw-r--r-- | mesonbuild/modules/hotdoc.py | 7 | ||||
-rw-r--r-- | mesonbuild/modules/i18n.py | 2 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_exe.py | 26 | ||||
-rw-r--r-- | test cases/common/54 install script/meson.build | 12 | ||||
-rw-r--r-- | test cases/common/54 install script/prog.c | 10 | ||||
-rw-r--r-- | test cases/common/54 install script/src/foo.c | 10 | ||||
-rw-r--r-- | test cases/common/54 install script/src/meson.build | 2 |
13 files changed, 103 insertions, 81 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b5e5cdf..caec761 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -22,9 +22,10 @@ import os import pickle import re import shlex -import subprocess import textwrap import typing as T +import hashlib +import copy from .. import build from .. import dependencies @@ -105,7 +106,7 @@ class InstallData: self.data: 'InstallType' = [] self.po_package_name: str = '' self.po = [] - self.install_scripts: T.List[build.RunScript] = [] + self.install_scripts: T.List[ExecutableSerialisation] = [] self.install_subdirs: 'InstallSubdirsType' = [] self.mesonintrospect = mesonintrospect self.version = version @@ -377,13 +378,11 @@ class Backend: raise MesonException('Unknown data type in object list.') return obj_list - def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None, - extra_bdeps=None, capture=None, force_serialize=False, - env: T.Optional[build.EnvironmentVariables] = None): - ''' - Serialize an executable for running with a generator or a custom target - ''' - import hashlib + def get_executable_serialisation(self, cmd, workdir=None, + extra_bdeps=None, capture=None, + env: T.Optional[build.EnvironmentVariables] = None): + exe = cmd[0] + cmd_args = cmd[1:] if isinstance(exe, dependencies.ExternalProgram): exe_cmd = exe.get_command() exe_for_machine = exe.for_machine @@ -403,11 +402,10 @@ class Backend: is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) if is_cross_built and self.environment.need_exe_wrapper(): exe_wrapper = self.environment.get_exe_wrapper() - if not exe_wrapper.found(): - msg = 'The exe_wrapper {!r} defined in the cross file is ' \ - 'needed by target {!r}, but was not found. Please ' \ - 'check the command and/or add it to PATH.' - raise MesonException(msg.format(exe_wrapper.name, tname)) + if not exe_wrapper or not exe_wrapper.found(): + msg = 'An exe_wrapper is needed but was not found. Please define one ' \ + 'in cross file and check the command and/or add it to PATH.' + raise MesonException(msg) else: if exe_cmd[0].endswith('.jar'): exe_cmd = ['java', '-jar'] + exe_cmd @@ -415,11 +413,24 @@ class Backend: exe_cmd = ['mono'] + exe_cmd exe_wrapper = None + workdir = workdir or self.environment.get_build_dir() + return ExecutableSerialisation(exe_cmd + cmd_args, env, + exe_wrapper, workdir, + extra_paths, capture) + + def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None, + extra_bdeps=None, capture=None, force_serialize=False, + env: T.Optional[build.EnvironmentVariables] = None): + ''' + Serialize an executable for running with a generator or a custom target + ''' + cmd = [exe] + cmd_args + es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, env) reasons = [] - if extra_paths: + if es.extra_paths: reasons.append('to set PATH') - if exe_wrapper: + if es.exe_runner: reasons.append('to use exe_wrapper') if workdir: @@ -440,10 +451,9 @@ class Backend: if not capture: return None, '' return ((self.environment.get_build_command() + - ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args), + ['--internal', 'exe', '--capture', capture, '--'] + es.cmd_args), ', '.join(reasons)) - workdir = workdir or self.environment.get_build_dir() if isinstance(exe, (dependencies.ExternalProgram, build.BuildTarget, build.CustomTarget)): basename = exe.name @@ -454,15 +464,12 @@ class Backend: # Take a digest of the cmd args, env, workdir, and capture. This avoids # collisions and also makes the name deterministic over regenerations # which avoids a rebuild by Ninja because the cmdline stays the same. - data = bytes(str(env) + str(cmd_args) + str(workdir) + str(capture), + data = bytes(str(env) + str(cmd_args) + str(es.workdir) + str(capture), encoding='utf-8') digest = hashlib.sha1(data).hexdigest() scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest) exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) with open(exe_data, 'wb') as f: - es = ExecutableSerialisation(exe_cmd + cmd_args, env, - exe_wrapper, workdir, - extra_paths, capture) pickle.dump(es, f) return (self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data], ', '.join(reasons)) @@ -1186,16 +1193,16 @@ class Backend: return inputs, outputs, cmd def run_postconf_scripts(self) -> None: + from ..scripts.meson_exe import run_exe env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 'MESON_BUILD_ROOT': self.environment.get_build_dir(), 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]), } - child_env = os.environ.copy() - child_env.update(env) for s in self.build.postconf_scripts: - cmd = s['exe'] + s['args'] - subprocess.check_call(cmd, env=child_env) + name = ' '.join(s.cmd_args) + mlog.log('Running postconf script {!r}'.format(name)) + run_exe(s, env) def create_install_data(self) -> InstallData: strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip') @@ -1324,18 +1331,18 @@ class Backend: d.targets.append(i) def generate_custom_install_script(self, d: InstallData) -> None: - result: T.List[build.RunScript] = [] + result: T.List[ExecutableSerialisation] = [] srcdir = self.environment.get_source_dir() builddir = self.environment.get_build_dir() for i in self.build.install_scripts: - exe = i['exe'] - args = i['args'] fixed_args = [] - for a in args: + for a in i.cmd_args: a = a.replace('@SOURCE_ROOT@', srcdir) a = a.replace('@BUILD_ROOT@', builddir) fixed_args.append(a) - result.append(build.RunScript(exe, fixed_args)) + es = copy.copy(i) + es.cmd_args = fixed_args + result.append(es) d.install_scripts = result def generate_header_install(self, d: InstallData) -> None: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 7b15bfe..32daf54 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -2609,14 +2609,6 @@ class Data: else: self.rename = rename -class RunScript(dict): - def __init__(self, script, args): - super().__init__() - assert(isinstance(script, list)) - assert(isinstance(args, list)) - self['exe'] = script - self['args'] = args - class TestSetup: def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, timeout_multiplier: int, env: EnvironmentVariables): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 8924a46..ceaa29a 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -34,7 +34,7 @@ from .interpreterbase import ObjectHolder, MesonVersionString from .interpreterbase import TYPE_var, TYPE_nkwargs from .modules import ModuleReturnValue, ExtensionModule from .cmake import CMakeInterpreter -from .backend.backends import TestProtocol, Backend +from .backend.backends import TestProtocol, Backend, ExecutableSerialisation from pathlib import Path, PurePath import os @@ -1948,11 +1948,8 @@ class MesonMain(InterpreterObject): }) def _find_source_script(self, prog: T.Union[str, mesonlib.File, ExecutableHolder], args): - if isinstance(prog, ExecutableHolder): - prog_path = self.interpreter.backend.get_target_filename(prog.held_object) - return build.RunScript([prog_path], args) - elif isinstance(prog, ExternalProgramHolder): - return build.RunScript(prog.get_command(), args) + if isinstance(prog, (ExecutableHolder, ExternalProgramHolder)): + return self.interpreter.backend.get_executable_serialisation([unholder(prog)] + args) # Prefer scripts in the current source directory search_dir = os.path.join(self.interpreter.environment.source_dir, self.interpreter.subdir) @@ -1970,7 +1967,7 @@ class MesonMain(InterpreterObject): else: m = 'Script or command {!r} not found or not executable' raise InterpreterException(m.format(prog)) - return build.RunScript(found.get_command(), args) + return self.interpreter.backend.get_executable_serialisation([found] + args) def _process_script_args( self, name: str, args: T.List[T.Union[ @@ -2557,7 +2554,7 @@ class Interpreter(InterpreterBase): return GeneratedListHolder(item) elif isinstance(item, build.RunTarget): raise RuntimeError('This is not a pipe.') - elif isinstance(item, build.RunScript): + elif isinstance(item, ExecutableSerialisation): raise RuntimeError('Do not do this.') elif isinstance(item, build.Data): return DataHolder(item) @@ -2584,7 +2581,7 @@ class Interpreter(InterpreterBase): self.module_method_callback(v) elif isinstance(v, build.GeneratedList): pass - elif isinstance(v, build.RunScript): + elif isinstance(v, ExecutableSerialisation): self.build.install_scripts.append(v) elif isinstance(v, build.Data): self.build.data.append(v) diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py index 293eef4..4547b38 100644 --- a/mesonbuild/mdist.py +++ b/mesonbuild/mdist.py @@ -26,6 +26,7 @@ from mesonbuild.environment import detect_ninja from mesonbuild.mesonlib import windows_proof_rmtree, MesonException, quiet_git from mesonbuild.wrap import wrap from mesonbuild import mlog, build +from .scripts.meson_exe import run_exe archive_choices = ['gztar', 'xztar', 'zip'] archive_extension = {'gztar': '.tar.gz', @@ -79,17 +80,15 @@ def process_submodules(dirname): def run_dist_scripts(src_root, bld_root, dist_root, dist_scripts): assert(os.path.isabs(dist_root)) - env = os.environ.copy() + env = {} env['MESON_DIST_ROOT'] = dist_root env['MESON_SOURCE_ROOT'] = src_root env['MESON_BUILD_ROOT'] = bld_root for d in dist_scripts: - script = d['exe'] - args = d['args'] - name = ' '.join(script + args) + name = ' '.join(d.cmd_args) print('Running custom dist script {!r}'.format(name)) try: - rc = subprocess.call(script + args, env=env) + rc = run_exe(d, env) if rc != 0: sys.exit('Dist script errored out') except OSError: diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 3e425eb..98608c7 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -30,6 +30,7 @@ from .coredata import major_versions_differ, MesonVersionMismatchException from .coredata import version as coredata_version from .mesonlib import is_windows, Popen_safe from .scripts import depfixer, destdir_join +from .scripts.meson_exe import run_exe try: from __main__ import __file__ as main_file except ImportError: @@ -485,17 +486,12 @@ class Installer: if self.options.quiet: env['MESON_INSTALL_QUIET'] = '1' - child_env = os.environ.copy() - child_env.update(env) - for i in d.install_scripts: self.did_install_something = True # Custom script must report itself if it does nothing. - script = i['exe'] - args = i['args'] - name = ' '.join(script + args) + name = ' '.join(i.cmd_args) self.log('Running custom install script {!r}'.format(name)) try: - rc = subprocess.call(script + args, env=child_env) + rc = run_exe(i, env) except OSError: print('FAILED: install script \'{}\' could not be run, stopped'.format(name)) # POSIX shells return 127 when a command could not be found diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 5cad9f5..f564eb4 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -899,7 +899,7 @@ class GnomeModule(ExtensionModule): args.append('--media=' + '@@'.join(media)) if langs: args.append('--langs=' + '@@'.join(langs)) - inscript = build.RunScript(script, args) + inscript = state.backend.get_executable_serialisation(script + args) potargs = state.environment.get_build_command() + [ '--internal', 'yelphelper', 'pot', @@ -1051,7 +1051,7 @@ class GnomeModule(ExtensionModule): self.interpreter.add_test(state.current_node, check_args, check_kwargs, True) res = [custom_target, alias_target] if kwargs.get('install', True): - res.append(build.RunScript(command, args)) + res.append(state.backend.get_executable_serialisation(command + args)) return ModuleReturnValue(custom_target, res) def _get_build_args(self, kwargs, state, depends): diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py index 931db12..eda411c 100644 --- a/mesonbuild/modules/hotdoc.py +++ b/mesonbuild/modules/hotdoc.py @@ -350,7 +350,7 @@ class HotdocTargetBuilder: install_script = None if install is True: - install_script = HotdocRunScript(self.build_command, [ + install_script = self.state.backend.get_executable_serialisation(self.build_command + [ "--internal", "hotdoc", "--install", os.path.join(fullname, 'html'), '--name', self.name, @@ -391,11 +391,6 @@ class HotdocTarget(build.CustomTarget): return res -class HotdocRunScript(build.RunScript): - def __init__(self, script, args): - super().__init__(script, args) - - class HotDocModule(ExtensionModule): @FeatureNew('Hotdoc Module', '0.48.0') def __init__(self, interpreter): diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index d48f83b..ae24e6e 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -180,7 +180,7 @@ class I18nModule(ExtensionModule): pkg_arg] if lang_arg: args.append(lang_arg) - iscript = build.RunScript(script, args) + iscript = state.backend.get_executable_serialisation(script + args) targets.append(iscript) return ModuleReturnValue(None, targets) diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 751d39b..620f579 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -30,7 +30,7 @@ def buildparser() -> argparse.ArgumentParser: parser.add_argument('--capture') return parser -def run_exe(exe: ExecutableSerialisation) -> int: +def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[dict] = None) -> int: if exe.exe_runner: if not exe.exe_runner.found(): raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found ' @@ -39,6 +39,8 @@ def run_exe(exe: ExecutableSerialisation) -> int: else: cmd_args = exe.cmd_args child_env = os.environ.copy() + if extra_env: + child_env.update(extra_env) if exe.env: child_env = exe.env.get_env(child_env) if exe.extra_paths: @@ -56,14 +58,21 @@ def run_exe(exe: ExecutableSerialisation) -> int: stderr=subprocess.PIPE) stdout, stderr = p.communicate() - if exe.pickled and p.returncode != 0: - print('while executing {!r}'.format(cmd_args)) - if p.returncode == 0xc0000135: # STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose raise FileNotFoundError('due to missing DLLs') - if exe.capture and p.returncode == 0: + if p.returncode != 0: + if exe.pickled: + print('while executing {!r}'.format(cmd_args)) + if not exe.capture: + print('--- stdout ---') + print(stdout.decode()) + print('--- stderr ---') + print(stderr.decode()) + return p.returncode + + if exe.capture: skip_write = False try: with open(exe.capture, 'rb') as cur: @@ -73,11 +82,8 @@ def run_exe(exe: ExecutableSerialisation) -> int: if not skip_write: with open(exe.capture, 'wb') as output: output.write(stdout) - else: - sys.stdout.buffer.write(stdout) - if stderr: - sys.stderr.buffer.write(stderr) - return p.returncode + + return 0 def run(args: T.List[str]) -> int: global options diff --git a/test cases/common/54 install script/meson.build b/test cases/common/54 install script/meson.build index eab95ab..24d5dc8 100644 --- a/test cases/common/54 install script/meson.build +++ b/test cases/common/54 install script/meson.build @@ -1,6 +1,5 @@ project('custom install script', 'c') -executable('prog', 'prog.c', install : true) meson.add_install_script('myinstall.py', 'diiba/daaba', 'file.dat') meson.add_install_script('myinstall.py', 'this/should', 'also-work.dat') @@ -33,3 +32,14 @@ installer = configure_file( ) meson.add_install_script(installer, 'otherdir', afile, '--mode=copy') + +# This executable links on a library built in src/ directory. On Windows this +# means meson must add src/ into $PATH to find the DLL when running it as +# install script. +myexe = executable('prog', 'prog.c', + link_with: mylib, + install : true, +) +if meson.can_run_host_binaries() + meson.add_install_script(myexe) +endif diff --git a/test cases/common/54 install script/prog.c b/test cases/common/54 install script/prog.c index 3bbf08e..85f8df9 100644 --- a/test cases/common/54 install script/prog.c +++ b/test cases/common/54 install script/prog.c @@ -1,6 +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 0; + return foo(); } diff --git a/test cases/common/54 install script/src/foo.c b/test cases/common/54 install script/src/foo.c new file mode 100644 index 0000000..46cb845 --- /dev/null +++ b/test cases/common/54 install script/src/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/common/54 install script/src/meson.build b/test cases/common/54 install script/src/meson.build index 123fe18..72de346 100644 --- a/test cases/common/54 install script/src/meson.build +++ b/test cases/common/54 install script/src/meson.build @@ -1,3 +1,5 @@ meson.add_install_script('myinstall.py', 'this/does', 'something-different.dat') afile = files('a file.txt') + +mylib = shared_library('mylib', 'foo.c') |