diff options
author | Nirbheek Chauhan <nirbheek@centricular.com> | 2018-07-09 04:02:02 +0530 |
---|---|---|
committer | Nirbheek Chauhan <nirbheek@centricular.com> | 2018-07-09 05:39:40 +0530 |
commit | e8dae2b966498207867cb07d58f4404b76c087ce (patch) | |
tree | 5ee7e334660dd3409e9af474861ba43147d0b309 | |
parent | 416a00308f5b0f228af3c93eb597eca8529fdbb0 (diff) | |
download | meson-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.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; +} |