Bug 1269517 - Implement try_compile for Python configure. r=glandium
authorChris Manchester <cmanchester@mozilla.com>
Tue, 26 Jul 2016 15:27:19 -0700
changeset 306803 de64f009410311eda93f4ac9358f44f1bc327fbe
parent 306802 c12838ecfbb51c04eefdf6e92a4dce9f062172d9
child 306804 cd2ca7c770fd94e67af3f4f97ceab20a373eb29c
push id30499
push usercbook@mozilla.com
push dateWed, 27 Jul 2016 14:35:59 +0000
treeherdermozilla-central@fef429fba4c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs1269517
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1269517 - Implement try_compile for Python configure. r=glandium MozReview-Commit-ID: AE7uRVneGXJ
build/moz.configure/compilechecks.configure
build/moz.configure/toolchain.configure
python/moz.build
python/mozbuild/mozbuild/test/configure/test_header_checks.py
new file mode 100644
--- /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
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -671,16 +671,18 @@ def compiler(language, host_or_target, c
 
 c_compiler = compiler('C', target)
 cxx_compiler = compiler('C++', target, c_compiler=c_compiler)
 host_c_compiler = compiler('C', host, other_compiler=c_compiler)
 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.
     if compiler_info.type == 'msvc':
         return '-Zi'
     return '-g'
 
 option(env='MOZ_DEBUG_FLAGS',
--- a/python/moz.build
+++ b/python/moz.build
@@ -34,16 +34,17 @@ PYTHON_UNIT_TESTS += [
     'mozbuild/mozbuild/test/backend/test_android_eclipse.py',
     'mozbuild/mozbuild/test/backend/test_build.py',
     'mozbuild/mozbuild/test/backend/test_configenvironment.py',
     'mozbuild/mozbuild/test/backend/test_recursivemake.py',
     'mozbuild/mozbuild/test/backend/test_visualstudio.py',
     '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',
     'mozbuild/mozbuild/test/configure/test_toolchain_helpers.py',
     'mozbuild/mozbuild/test/configure/test_util.py',
     'mozbuild/mozbuild/test/controller/test_ccachestats.py',
     'mozbuild/mozbuild/test/controller/test_clobber.py',
     'mozbuild/mozbuild/test/frontend/test_context.py',
new file mode 100644
--- /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 <foo.h>
+          #include <bar.h>
+          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()