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.
pull/3850/head
Nirbheek Chauhan 6 years ago
parent 416a00308f
commit e8dae2b966
  1. 18
      mesonbuild/backend/backends.py
  2. 8
      mesonbuild/backend/ninjabackend.py
  3. 3
      mesonbuild/backend/vs2010backend.py
  4. 11
      mesonbuild/compilers/c.py
  5. 15
      mesonbuild/mtest.py
  6. 6
      mesonbuild/scripts/meson_exe.py
  7. 34
      run_tests.py
  8. 65
      run_unittests.py
  9. 20
      test cases/unit/35 exe_wrapper behaviour/broken-cross.txt
  10. 19
      test cases/unit/35 exe_wrapper behaviour/meson.build
  11. 2
      test cases/unit/35 exe_wrapper behaviour/meson_options.txt
  12. 17
      test cases/unit/35 exe_wrapper behaviour/prog.c

@ -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,

@ -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(),

@ -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,

@ -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))

@ -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

@ -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:

@ -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)

@ -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']

@ -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'

@ -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

@ -0,0 +1,2 @@
option('custom-target', type: 'boolean', value: true)
option('run-target', type: 'boolean', value: true)

@ -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;
}
Loading…
Cancel
Save