diff options
-rw-r--r-- | mesonbuild/backend/backends.py | 18 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 8 | ||||
-rw-r--r-- | mesonbuild/backend/vs2010backend.py | 3 | ||||
-rw-r--r-- | mesonbuild/compilers/c.py | 11 | ||||
-rw-r--r-- | mesonbuild/mtest.py | 15 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_exe.py | 6 | ||||
-rwxr-xr-x | run_tests.py | 34 | ||||
-rwxr-xr-x | run_unittests.py | 65 | ||||
-rw-r--r-- | test cases/unit/35 exe_wrapper behaviour/broken-cross.txt | 20 | ||||
-rw-r--r-- | test cases/unit/35 exe_wrapper behaviour/meson.build | 19 | ||||
-rw-r--r-- | test cases/unit/35 exe_wrapper behaviour/meson_options.txt | 2 | ||||
-rw-r--r-- | test cases/unit/35 exe_wrapper behaviour/prog.c | 17 |
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; +} |