aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNirbheek Chauhan <nirbheek@centricular.com>2018-07-09 04:02:02 +0530
committerNirbheek Chauhan <nirbheek@centricular.com>2018-07-09 05:39:40 +0530
commite8dae2b966498207867cb07d58f4404b76c087ce (patch)
tree5ee7e334660dd3409e9af474861ba43147d0b309
parent416a00308f5b0f228af3c93eb597eca8529fdbb0 (diff)
downloadmeson-e8dae2b966498207867cb07d58f4404b76c087ce.zip
meson-e8dae2b966498207867cb07d58f4404b76c087ce.tar.gz
meson-e8dae2b966498207867cb07d58f4404b76c087ce.tar.bz2
cross: Be more permissive about not-found exe_wrapper
We used to immediately try to use whatever exe_wrapper was defined in the cross file, but some people generate the cross file once and use it for several projects, most of which do not even need an exe wrapper to build. Now we're a bit more resilient. We quietly fall back to using non-exe-wrapper paths for compiler checks and skip the sanity check. However, if some code needs the exe wrapper, f.ex., if you run a built executable using custom_target() or run_target(), we will error out during setup. Tests will, of course, continue to error out when you run them if the exe wrapper was not found. We don't want people's tests to silently "pass" (aka skip) because of a bad CI setup. Closes https://github.com/mesonbuild/meson/issues/3562 This commit also adds a test for the behaviour of exe_wrapper in these cases, and refactors the unit tests a bit for it.
-rw-r--r--mesonbuild/backend/backends.py18
-rw-r--r--mesonbuild/backend/ninjabackend.py8
-rw-r--r--mesonbuild/backend/vs2010backend.py3
-rw-r--r--mesonbuild/compilers/c.py11
-rw-r--r--mesonbuild/mtest.py15
-rw-r--r--mesonbuild/scripts/meson_exe.py6
-rwxr-xr-xrun_tests.py34
-rwxr-xr-xrun_unittests.py65
-rw-r--r--test cases/unit/35 exe_wrapper behaviour/broken-cross.txt20
-rw-r--r--test cases/unit/35 exe_wrapper behaviour/meson.build19
-rw-r--r--test cases/unit/35 exe_wrapper behaviour/meson_options.txt2
-rw-r--r--test cases/unit/35 exe_wrapper behaviour/prog.c17
12 files changed, 186 insertions, 32 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index b13aa10..b55d3e0 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -276,8 +276,11 @@ class Backend:
raise MesonException('Unknown data type in object list.')
return obj_list
- def serialize_executable(self, exe, cmd_args, workdir, env={},
+ def serialize_executable(self, tname, exe, cmd_args, workdir, env={},
extra_paths=None, capture=None):
+ '''
+ Serialize an executable for running with a generator or a custom target
+ '''
import hashlib
if extra_paths is None:
# The callee didn't check if we needed extra paths, so check it here
@@ -302,19 +305,24 @@ class Backend:
with open(exe_data, 'wb') as f:
if isinstance(exe, dependencies.ExternalProgram):
exe_cmd = exe.get_command()
- exe_needs_wrapper = False
+ exe_is_native = True
elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
exe_cmd = [self.get_target_filename_abs(exe)]
- exe_needs_wrapper = exe.is_cross
+ exe_is_native = not exe.is_cross
else:
exe_cmd = [exe]
- exe_needs_wrapper = False
- is_cross_built = exe_needs_wrapper and \
+ exe_is_native = True
+ is_cross_built = (not exe_is_native) and \
self.environment.is_cross_build() and \
self.environment.cross_info.need_cross_compiler() and \
self.environment.cross_info.need_exe_wrapper()
if is_cross_built:
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))
else:
exe_wrapper = None
es = ExecutableSerialisation(basename, exe_cmd, cmd_args, env,
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index f62bc67..eed40bc 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -541,7 +541,7 @@ int dummy;
if extra_paths:
serialize = True
if serialize:
- exe_data = self.serialize_executable(target.command[0], cmd[1:],
+ exe_data = self.serialize_executable(target.name, target.command[0], cmd[1:],
# All targets are built from the build dir
self.environment.get_build_dir(),
extra_paths=extra_paths,
@@ -598,6 +598,11 @@ int dummy;
if self.environment.is_cross_build():
exe_wrap = self.environment.get_exe_wrapper()
if exe_wrap:
+ if not exe_wrap.found():
+ msg = 'The exe_wrapper {!r} defined in the cross file is ' \
+ 'needed by run target {!r}, but was not found. ' \
+ 'Please check the command and/or add it to PATH.'
+ raise MesonException(msg.format(exe_wrap.name, target.name))
cmd += exe_wrap.get_command()
cmd.append(abs_exe)
elif isinstance(texe, dependencies.ExternalProgram):
@@ -1932,6 +1937,7 @@ rule FORTRAN_DEP_HACK%s
cmdlist = exe_arr + self.replace_extra_args(args, genlist)
if generator.capture:
exe_data = self.serialize_executable(
+ 'generator ' + cmdlist[0],
cmdlist[0],
cmdlist[1:],
self.environment.get_build_dir(),
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index d42e91d..cb4f706 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -133,6 +133,7 @@ class Vs2010Backend(backends.Backend):
cmd = exe_arr + self.replace_extra_args(args, genlist)
if generator.capture:
exe_data = self.serialize_executable(
+ 'generator ' + cmd[0],
cmd[0],
cmd[1:],
self.environment.get_build_dir(),
@@ -489,7 +490,7 @@ class Vs2010Backend(backends.Backend):
tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
extra_bdeps = target.get_transitive_build_target_deps()
extra_paths = self.determine_windows_extra_paths(target.command[0], extra_bdeps)
- exe_data = self.serialize_executable(target.command[0], cmd[1:],
+ exe_data = self.serialize_executable(target.name, target.command[0], cmd[1:],
# All targets run from the target dir
tdir_abs,
extra_paths=extra_paths,
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py
index fa6fd89..1530ea0 100644
--- a/mesonbuild/compilers/c.py
+++ b/mesonbuild/compilers/c.py
@@ -57,9 +57,12 @@ class CCompiler(Compiler):
self.id = 'unknown'
self.is_cross = is_cross
self.can_compile_suffixes.add('h')
- self.exe_wrapper = exe_wrapper
- if self.exe_wrapper:
- self.exe_wrapper = self.exe_wrapper.get_command()
+ # If the exe wrapper was not found, pretend it wasn't set so that the
+ # sanity check is skipped and compiler checks use fallbacks.
+ if not exe_wrapper or not exe_wrapper.found():
+ self.exe_wrapper = None
+ else:
+ self.exe_wrapper = exe_wrapper.get_command()
# Set to None until we actually need to check this
self.has_fatal_warnings_link_arg = None
@@ -277,7 +280,7 @@ class CCompiler(Compiler):
if self.exe_wrapper is None:
# Can't check if the binaries run so we have to assume they do
return
- cmdlist = self.exe_wrapper.get_command() + [binary_name]
+ cmdlist = self.exe_wrapper + [binary_name]
else:
cmdlist = [binary_name]
mlog.debug('Running test binary command: ' + ' '.join(cmdlist))
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index 3c4073b..8ebef04 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -240,7 +240,10 @@ class SingleTestRunner:
# because there is no execute wrapper.
return None
else:
- return [self.test.exe_runner] + self.test.fname
+ if not self.test.exe_runner.found():
+ msg = 'The exe_wrapper defined in the cross file {!r} was not ' \
+ 'found. Please check the command and/or add it to PATH.'
+ raise TestException(msg.format(self.test.exe_runner.name))
return self.test.exe_runner.get_command() + self.test.fname
else:
return self.test.fname
@@ -738,12 +741,13 @@ def run(args):
if check_bin is not None:
exe = ExternalProgram(check_bin, silent=True)
if not exe.found():
- sys.exit('Could not find requested program: %s' % check_bin)
+ print('Could not find requested program: {!r}'.format(check_bin))
+ return 1
options.wd = os.path.abspath(options.wd)
if not options.list and not options.no_rebuild:
if not rebuild_all(options.wd):
- sys.exit(-1)
+ return 1
try:
th = TestHarness(options)
@@ -755,5 +759,8 @@ def run(args):
return th.run_special()
except TestException as e:
print('Meson test encountered an error:\n')
- print(e)
+ if os.environ.get('MESON_FORCE_BACKTRACE'):
+ raise e
+ else:
+ print(e)
return 1
diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py
index ee5906b..84abfc3 100644
--- a/mesonbuild/scripts/meson_exe.py
+++ b/mesonbuild/scripts/meson_exe.py
@@ -49,7 +49,11 @@ def run_exe(exe):
else:
if exe.is_cross:
if exe.exe_runner is None:
- raise AssertionError('BUG: Trying to run cross-compiled exes with no wrapper')
+ raise AssertionError('BUG: Can\'t run cross-compiled exe {!r}'
+ 'with no wrapper'.format(exe.name))
+ elif not exe.exe_runner.found():
+ raise AssertionError('BUG: Can\'t run cross-compiled exe {!r} with not-found'
+ 'wrapper {!r}'.format(exe.name, exe.exe_runner.get_path()))
else:
cmd = exe.exe_runner.get_command() + exe.fname
else:
diff --git a/run_tests.py b/run_tests.py
index af20ba2..6e441d3 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -29,6 +29,7 @@ from pathlib import Path
import mesonbuild
from mesonbuild import mesonlib
from mesonbuild import mesonmain
+from mesonbuild import mtest
from mesonbuild import mlog
from mesonbuild.environment import detect_ninja
@@ -156,8 +157,17 @@ def get_fake_options(prefix):
opts.cmd_line_options = {}
return opts
-def should_run_linux_cross_tests():
- return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
+def run_mtest_inprocess(commandlist):
+ old_stdout = sys.stdout
+ sys.stdout = mystdout = StringIO()
+ old_stderr = sys.stderr
+ sys.stderr = mystderr = StringIO()
+ try:
+ returncode = mtest.run(commandlist)
+ finally:
+ sys.stdout = old_stdout
+ sys.stderr = old_stderr
+ return returncode, mystdout.getvalue(), mystderr.getvalue()
def run_configure_inprocess(commandlist):
old_stdout = sys.stdout
@@ -203,7 +213,7 @@ if __name__ == '__main__':
# Iterate over list in reverse order to find the last --backend arg
backend = Backend.ninja
cross = False
- # FIXME: Convert to argparse
+ # FIXME: PLEASE convert to argparse
for arg in reversed(sys.argv[1:]):
if arg.startswith('--backend'):
if arg.startswith('--backend=vs'):
@@ -212,6 +222,10 @@ if __name__ == '__main__':
backend = Backend.xcode
if arg.startswith('--cross'):
cross = True
+ if arg == '--cross=mingw':
+ cross = 'mingw'
+ elif arg == '--cross=arm':
+ cross = 'arm'
# Running on a developer machine? Be nice!
if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'TRAVIS' not in os.environ:
os.nice(20)
@@ -249,10 +263,12 @@ if __name__ == '__main__':
returncode += subprocess.call(mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:], env=env)
else:
cross_test_args = mesonlib.python_command + ['run_cross_test.py']
- print(mlog.bold('Running armhf cross tests.').get_text(mlog.colorize_console))
- print()
- returncode += subprocess.call(cross_test_args + ['cross/ubuntu-armhf.txt'], env=env)
- print(mlog.bold('Running mingw-w64 64-bit cross tests.').get_text(mlog.colorize_console))
- print()
- returncode += subprocess.call(cross_test_args + ['cross/linux-mingw-w64-64bit.txt'], env=env)
+ if cross is True or cross == 'arm':
+ print(mlog.bold('Running armhf cross tests.').get_text(mlog.colorize_console))
+ print()
+ returncode += subprocess.call(cross_test_args + ['cross/ubuntu-armhf.txt'], env=env)
+ if cross is True or cross == 'mingw':
+ print(mlog.bold('Running mingw-w64 64-bit cross tests.').get_text(mlog.colorize_console))
+ print()
+ returncode += subprocess.call(cross_test_args + ['cross/linux-mingw-w64-64bit.txt'], env=env)
sys.exit(returncode)
diff --git a/run_unittests.py b/run_unittests.py
index b3bc271..02afe8e 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -22,6 +22,7 @@ import textwrap
import os
import shutil
import unittest
+import platform
from unittest import mock
from configparser import ConfigParser
from glob import glob
@@ -47,7 +48,7 @@ import mesonbuild.modules.pkgconfig
from run_tests import exe_suffix, get_fake_options, get_meson_script
from run_tests import get_builddir_target_args, get_backend_commands, Backend
from run_tests import ensure_backend_detects_changes, run_configure_inprocess
-from run_tests import should_run_linux_cross_tests
+from run_tests import run_mtest_inprocess
def get_dynamic_section_entry(fname, entry):
@@ -641,8 +642,11 @@ class BasePlatformTests(unittest.TestCase):
dir_args = get_builddir_target_args(self.backend, self.builddir, None)
self._run(self.clean_command + dir_args, workdir=self.builddir)
- def run_tests(self):
- self._run(self.test_command, workdir=self.builddir)
+ def run_tests(self, inprocess=False):
+ if not inprocess:
+ self._run(self.test_command, workdir=self.builddir)
+ else:
+ run_mtest_inprocess(['-C', self.builddir])
def install(self, *, use_destdir=True):
if self.backend is not Backend.ninja:
@@ -3500,9 +3504,9 @@ endian = 'little'
self.assertNotRegex(out, self.installdir + '.*dylib ')
-class LinuxArmCrossCompileTests(BasePlatformTests):
+class LinuxCrossArmTests(BasePlatformTests):
'''
- Tests that verify cross-compilation to Linux/ARM
+ Tests that cross-compilation to Linux/ARM works
'''
def setUp(self):
super().setUp()
@@ -3536,6 +3540,45 @@ class LinuxArmCrossCompileTests(BasePlatformTests):
self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS')
self.build()
+class LinuxCrossMingwTests(BasePlatformTests):
+ '''
+ Tests that cross-compilation to Windows/MinGW works
+ '''
+ def setUp(self):
+ super().setUp()
+ src_root = os.path.dirname(__file__)
+ self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt')
+
+ def test_exe_wrapper_behaviour(self):
+ '''
+ Test that an exe wrapper that isn't found doesn't cause compiler sanity
+ checks and compiler checks to fail, but causes configure to fail if it
+ requires running a cross-built executable (custom_target or run_target)
+ and causes the tests to be skipped if they are run.
+ '''
+ testdir = os.path.join(self.unit_test_dir, '35 exe_wrapper behaviour')
+ # Configures, builds, and tests fine by default
+ self.init(testdir)
+ self.build()
+ self.run_tests()
+ self.wipe()
+ os.mkdir(self.builddir)
+ # Change cross file to use a non-existing exe_wrapper and it should fail
+ self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt')
+ # Force tracebacks so we can detect them properly
+ os.environ['MESON_FORCE_BACKTRACE'] = '1'
+ with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'):
+ # Must run in-process or we'll get a generic CalledProcessError
+ self.init(testdir, extra_args='-Drun-target=false', inprocess=True)
+ with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'):
+ # Must run in-process or we'll get a generic CalledProcessError
+ self.init(testdir, extra_args='-Dcustom-target=false', inprocess=True)
+ self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'])
+ self.build()
+ with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'):
+ # Must run in-process or we'll get a generic CalledProcessError
+ self.run_tests(inprocess=True)
+
class PythonTests(BasePlatformTests):
'''
@@ -3669,13 +3712,21 @@ def unset_envs():
if v in os.environ:
del os.environ[v]
+def should_run_cross_arm_tests():
+ return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
+
+def should_run_cross_mingw_tests():
+ return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin())
+
if __name__ == '__main__':
unset_envs()
cases = ['InternalTests', 'AllPlatformTests', 'FailureTests', 'PythonTests']
if not is_windows():
cases += ['LinuxlikeTests']
- if should_run_linux_cross_tests():
- cases += ['LinuxArmCrossCompileTests']
+ if should_run_cross_arm_tests():
+ cases += ['LinuxCrossArmTests']
+ if should_run_cross_mingw_tests():
+ cases += ['LinuxCrossMingwTests']
if is_windows() or is_cygwin():
cases += ['WindowsTests']
diff --git a/test cases/unit/35 exe_wrapper behaviour/broken-cross.txt b/test cases/unit/35 exe_wrapper behaviour/broken-cross.txt
new file mode 100644
index 0000000..a5a3931
--- /dev/null
+++ b/test cases/unit/35 exe_wrapper behaviour/broken-cross.txt
@@ -0,0 +1,20 @@
+[binaries]
+c = '/usr/bin/x86_64-w64-mingw32-gcc'
+cpp = '/usr/bin/x86_64-w64-mingw32-g++'
+ar = '/usr/bin/x86_64-w64-mingw32-ar'
+strip = '/usr/bin/x86_64-w64-mingw32-strip'
+pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
+windres = '/usr/bin/x86_64-w64-mingw32-windres'
+exe_wrapper = 'broken'
+
+[properties]
+# Directory that contains 'bin', 'lib', etc
+root = '/usr/x86_64-w64-mingw32'
+# Directory that contains 'bin', 'lib', etc for the toolchain and system libraries
+sys_root = '/usr/x86_64-w64-mingw32/sys-root/mingw'
+
+[host_machine]
+system = 'windows'
+cpu_family = 'x86_64'
+cpu = 'x86_64'
+endian = 'little'
diff --git a/test cases/unit/35 exe_wrapper behaviour/meson.build b/test cases/unit/35 exe_wrapper behaviour/meson.build
new file mode 100644
index 0000000..16a44d5
--- /dev/null
+++ b/test cases/unit/35 exe_wrapper behaviour/meson.build
@@ -0,0 +1,19 @@
+project('exe wrapper behaviour', 'c')
+
+assert(meson.is_cross_build(), 'not setup as cross build')
+assert(meson.has_exe_wrapper(), 'exe wrapper not defined?')
+
+exe = executable('prog', 'prog.c')
+
+if get_option('custom-target')
+ custom_target('use-exe-wrapper',
+ build_by_default: true,
+ output: 'out.txt',
+ command: [exe, '@OUTPUT@'])
+endif
+
+test('test-prog', exe)
+
+if get_option('run-target')
+ run_target('run-prog', command : exe)
+endif
diff --git a/test cases/unit/35 exe_wrapper behaviour/meson_options.txt b/test cases/unit/35 exe_wrapper behaviour/meson_options.txt
new file mode 100644
index 0000000..e5645a0
--- /dev/null
+++ b/test cases/unit/35 exe_wrapper behaviour/meson_options.txt
@@ -0,0 +1,2 @@
+option('custom-target', type: 'boolean', value: true)
+option('run-target', type: 'boolean', value: true)
diff --git a/test cases/unit/35 exe_wrapper behaviour/prog.c b/test cases/unit/35 exe_wrapper behaviour/prog.c
new file mode 100644
index 0000000..3213780
--- /dev/null
+++ b/test cases/unit/35 exe_wrapper behaviour/prog.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+int main (int argc, char * argv[])
+{
+ const char *out = "SUCCESS!";
+
+ if (argc != 2) {
+ printf ("%s\n", out);
+ } else {
+ int ret;
+ FILE *f = fopen (argv[1], "w");
+ ret = fwrite (out, sizeof (out), 1, f);
+ if (ret != 1)
+ return -1;
+ }
+ return 0;
+}