diff --git a/build/moz.configure/compilechecks.configure b/build/moz.configure/compilechecks.configure new file mode 100644 index 000000000000..2daa1f302bc2 --- /dev/null +++ b/build/moz.configure/compilechecks.configure @@ -0,0 +1,55 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# Generates a test program and attempts to compile it. In case of failure, the +# resulting check will return None. If the test program succeeds, it will return +# the output of the test program. +# - `includes` are the includes (as file names) that will appear at the top of +# the generated test program. +# - `body` is the code that will appear in the main function of the generated +# test program. `return 0;` is appended to the function body automatically. +# - `language` is the language selection, so that the appropriate compiler is +# used. +# - `flags` are the flags to be passed to the compiler, in addition to `-c`. +# - `check_msg` is the message to be printed to accompany compiling the test +# program. +@template +@imports('textwrap') +def try_compile(includes=None, body='', language='C++', flags=None, check_msg=None): + includes = includes or [] + source_lines = ['#include <%s>' % f for f in includes] + source = '\n'.join(source_lines) + '\n' + source += textwrap.dedent('''\ + int + main(void) + { + %s + ; + return 0; + } + ''' % body) + flags = flags or [] + flags.append('-c') + + if check_msg: + def checking_fn(fn): + return checking(check_msg, callback=lambda r: r is not None)(fn) + else: + def checking_fn(fn): + return fn + + @depends(cxx_compiler, c_compiler) + @checking_fn + def check(cxx_info, c_info): + info = { + 'C': c_info, + 'C++': cxx_info, + }[language] + return try_invoke_compiler(info.wrapper + [info.compiler] + info.flags, + language, source, flags, + onerror=lambda: None) + return check diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure index bbbec7fdb16a..113fd5df22d3 100644 --- a/build/moz.configure/toolchain.configure +++ b/build/moz.configure/toolchain.configure @@ -676,6 +676,8 @@ host_cxx_compiler = compiler('C++', host, c_compiler=host_c_compiler, other_compiler=cxx_compiler, other_c_compiler=c_compiler) +include('compilechecks.configure') + @depends(c_compiler) def default_debug_flags(compiler_info): # Debug info is ON by default. diff --git a/python/moz.build b/python/moz.build index 57592f65bd0d..66b9c4fb481c 100644 --- a/python/moz.build +++ b/python/moz.build @@ -39,6 +39,7 @@ PYTHON_UNIT_TESTS += [ 'mozbuild/mozbuild/test/compilation/test_warnings.py', 'mozbuild/mozbuild/test/configure/test_checks_configure.py', 'mozbuild/mozbuild/test/configure/test_configure.py', + 'mozbuild/mozbuild/test/configure/test_header_checks.py', 'mozbuild/mozbuild/test/configure/test_moz_configure.py', 'mozbuild/mozbuild/test/configure/test_options.py', 'mozbuild/mozbuild/test/configure/test_toolchain_configure.py', diff --git a/python/mozbuild/mozbuild/test/configure/test_header_checks.py b/python/mozbuild/mozbuild/test/configure/test_header_checks.py new file mode 100644 index 000000000000..502c4fba7360 --- /dev/null +++ b/python/mozbuild/mozbuild/test/configure/test_header_checks.py @@ -0,0 +1,154 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import, print_function, unicode_literals + +import os +import textwrap +import unittest + +from StringIO import StringIO + +from buildconfig import topsrcdir +from common import ConfigureTestSandbox +from mozbuild.util import exec_ +from mozunit import main +from test_toolchain_helpers import FakeCompiler + + +class TestHeaderChecks(unittest.TestCase): + + def get_mock_compiler(self, expected_test_content=None, expected_flags=None): + expected_flags = expected_flags or [] + def mock_compiler(stdin, args): + args, test_file = args[:-1], args[-1] + self.assertIn('-c', args) + for flag in expected_flags: + self.assertIn(flag, args) + + if expected_test_content: + with open(test_file) as fh: + test_content = fh.read() + self.assertEqual(test_content, expected_test_content) + + return FakeCompiler()(None, args) + return mock_compiler + + def do_compile_test(self, command, expected_test_content=None, + expected_flags=None): + + paths = { + os.path.abspath('/usr/bin/mockcc'): self.get_mock_compiler( + expected_test_content=expected_test_content, + expected_flags=expected_flags), + } + + mock_compiler_defs = textwrap.dedent('''\ + @depends('--help') + def c_compiler(_): + return namespace( + flags=[], + compiler=os.path.abspath('/usr/bin/mockcc'), + wrapper=[], + ) + + @depends('--help') + def cxx_compiler(_): + return namespace( + flags=[], + compiler=os.path.abspath('/usr/bin/mockcc'), + wrapper=[], + ) + ''') + + config = {} + out = StringIO() + sandbox = ConfigureTestSandbox(paths, config, {}, ['/bin/configure'], + out, out) + base_dir = os.path.join(topsrcdir, 'build', 'moz.configure') + sandbox.include_file(os.path.join(base_dir, 'util.configure')) + sandbox.include_file(os.path.join(base_dir, 'checks.configure')) + exec_(mock_compiler_defs, sandbox) + sandbox.include_file(os.path.join(base_dir, 'compilechecks.configure')) + + status = 0 + try: + exec_(command, sandbox) + sandbox.run() + except SystemExit as e: + status = e.code + + return config, out.getvalue(), status + + def test_try_compile_include(self): + expected_test_content = textwrap.dedent('''\ + #include + #include + int + main(void) + { + + ; + return 0; + } + ''') + + cmd = textwrap.dedent('''\ + try_compile(['foo.h', 'bar.h'], language='C') + ''') + + config, out, status = self.do_compile_test(cmd, expected_test_content) + self.assertEqual(status, 0) + self.assertEqual(config, {}) + + def test_try_compile_flags(self): + expected_flags = ['--extra', '--flags'] + + cmd = textwrap.dedent('''\ + try_compile(language='C++', flags=['--flags', '--extra']) + ''') + + config, out, status = self.do_compile_test(cmd, expected_flags=expected_flags) + self.assertEqual(status, 0) + self.assertEqual(config, {}) + + def test_try_compile_failure(self): + cmd = textwrap.dedent('''\ + @depends(try_compile(body='somefn();', flags=['-funknown-flag'])) + def have_fn(value): + if value is not None: + return True + set_config('HAVE_SOMEFN', have_fn) + + @depends(try_compile(body='anotherfn();', language='C')) + def have_another(value): + if value is not None: + return True + set_config('HAVE_ANOTHERFN', have_another) + ''') + + config, out, status = self.do_compile_test(cmd) + self.assertEqual(status, 0) + self.assertEqual(config, { + 'HAVE_ANOTHERFN': True, + }) + + def test_try_compile_msg(self): + cmd = textwrap.dedent('''\ + @depends(try_compile(language='C++', flags=['-fknown-flag'], + check_msg='whether -fknown-flag works')) + def known_flag(result): + if result is not None: + return True + set_config('HAVE_KNOWN_FLAG', known_flag) + ''') + config, out, status = self.do_compile_test(cmd) + self.assertEqual(status, 0) + self.assertEqual(config, {'HAVE_KNOWN_FLAG': True}) + self.assertEqual(out, textwrap.dedent('''\ + checking whether -fknown-flag works... yes + ''')) + +if __name__ == '__main__': + main()