aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-01-26 13:51:23 -0500
committerJussi Pakkanen <jpakkane@gmail.com>2021-01-30 09:51:06 +0000
commit0626465ea8aa65b10776d5c4064e881fe0d6fa25 (patch)
treebe11f7029bd7bd418494d45cf7f3de52cbaf435f
parentc321339b24f896d02b0839d1b1e5008eae405858 (diff)
downloadmeson-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.py69
-rw-r--r--mesonbuild/build.py8
-rw-r--r--mesonbuild/interpreter.py15
-rw-r--r--mesonbuild/mdist.py9
-rw-r--r--mesonbuild/minstall.py10
-rw-r--r--mesonbuild/modules/gnome.py4
-rw-r--r--mesonbuild/modules/hotdoc.py7
-rw-r--r--mesonbuild/modules/i18n.py2
-rw-r--r--mesonbuild/scripts/meson_exe.py26
-rw-r--r--test cases/common/54 install script/meson.build12
-rw-r--r--test cases/common/54 install script/prog.c10
-rw-r--r--test cases/common/54 install script/src/foo.c10
-rw-r--r--test cases/common/54 install script/src/meson.build2
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')