Bug 1292046 - Add a check that the compiler works with -c out of the box. r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 04 Aug 2016 15:51:47 +0900
changeset 397070 ea9e47974d13414cab70a473f0398c08b7c2f3c7
parent 397069 f0150c7439f1d22dc3ca8e29d9486f9daca3f1bc
child 397071 6060e204b876e5b4b67ae33b6c4ada279db06050
push id25194
push userbmo:mh+mozilla@glandium.org
push dateFri, 05 Aug 2016 05:27:35 +0000
reviewerschmanchester
bugs1292046
milestone51.0a1
Bug 1292046 - Add a check that the compiler works with -c out of the box. r?chmanchester The base compiler check in python configure does some preprocessing, which ensures the compiler works to some extent. Autoconf used to have a more complete test, doing a compile/link. We do have plenty of tests afterwards that do that anyways, but it's better if we fail early if the toolchain fails somehow. This refactors try_compile such that the *_compiler variable themselves can be used to trigger compiler tests. Eventually, we'll want something similar for preprocessing and possibly other invocations. This also removes similar tests from build/autoconf/toolchain.m4.
build/autoconf/toolchain.m4
build/moz.configure/checks.configure
build/moz.configure/compilechecks.configure
build/moz.configure/compilers-util.configure
build/moz.configure/toolchain.configure
python/mozbuild/mozbuild/test/configure/test_header_checks.py
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -44,44 +44,21 @@ fi
 AC_SUBST(CLANG_CXX)
 AC_SUBST(CLANG_CL)
 ])
 
 AC_DEFUN([MOZ_CROSS_COMPILER],
 [
 echo "cross compiling from $host to $target"
 
-_SAVE_CC="$CC"
-_SAVE_CFLAGS="$CFLAGS"
-_SAVE_LDFLAGS="$LDFLAGS"
-
 if test -z "$HOST_AR_FLAGS"; then
     HOST_AR_FLAGS="$AR_FLAGS"
 fi
 AC_CHECK_PROGS(HOST_RANLIB, $HOST_RANLIB ranlib, ranlib, :)
 AC_CHECK_PROGS(HOST_AR, $HOST_AR ar, ar, :)
-CC="$HOST_CC"
-CFLAGS="$HOST_CFLAGS"
-LDFLAGS="$HOST_LDFLAGS"
-
-AC_MSG_CHECKING([whether the host c compiler ($HOST_CC $HOST_CFLAGS $HOST_LDFLAGS) works])
-AC_TRY_COMPILE([], [return(0);],
-    [ac_cv_prog_hostcc_works=1 AC_MSG_RESULT([yes])],
-    AC_MSG_ERROR([installation or configuration problem: host compiler $HOST_CC cannot create executables.]) )
-
-CC="$HOST_CXX"
-CFLAGS="$HOST_CXXFLAGS"
-AC_MSG_CHECKING([whether the host c++ compiler ($HOST_CXX $HOST_CXXFLAGS $HOST_LDFLAGS) works])
-AC_TRY_COMPILE([], [return(0);],
-    [ac_cv_prog_hostcxx_works=1 AC_MSG_RESULT([yes])],
-    AC_MSG_ERROR([installation or configuration problem: host compiler $HOST_CXX cannot create executables.]) )
-
-CC=$_SAVE_CC
-CFLAGS=$_SAVE_CFLAGS
-LDFLAGS=$_SAVE_LDFLAGS
 
 dnl AC_CHECK_PROGS manually goes through $PATH, and as such fails to handle
 dnl absolute or relative paths. Relative paths wouldn't work anyways, but
 dnl absolute paths would. Trick AC_CHECK_PROGS into working in that case by
 dnl adding / to PATH. This is temporary until this moves to moz.configure
 dnl (soon).
 _SAVE_PATH=$PATH
 case "${TOOLCHAIN_PREFIX}" in
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -55,17 +55,17 @@ def checking(what, callback=None):
                     error = e.message
                 display_ret = callback(ret) if callback else ret
                 if display_ret is True:
                     log.info('yes')
                 elif display_ret is False or display_ret is None:
                     log.info('no')
                 else:
                     log.info(display_ret)
-                if error:
+                if error is not None:
                     die(error)
             return ret
         return wrapped
     return decorator
 
 
 # Template to check for programs in $PATH.
 # - `var` is the name of the variable that will be set with `set_config` when
--- a/build/moz.configure/compilechecks.configure
+++ b/build/moz.configure/compilechecks.configure
@@ -13,57 +13,24 @@
 # - `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)
+    compiler = {
+        'C': c_compiler,
+        'C++': cxx_compiler,
+    }[language]
 
-    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
-
-    def get_flags():
-        if flags:
-            return flags[:]
+    return compiler.try_compile(includes, body, flags, check_msg)
 
-    @depends(cxx_compiler, c_compiler, extra_toolchain_flags)
-    @checking_fn
-    def check(cxx_info, c_info, extra_flags):
-        flags = get_flags() or []
-        flags += extra_flags
-        flags.append('-c')
-
-        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
 
 # Checks for the presence of the given header on the target system by compiling
 # a test program including that header. The return value of the template is a
 # check function returning True if the header is present, and None if it is not.
 # The value of this check function is also used to set a variable (with set_define)
 # corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in
 # defines if check_header if called with 'malloc.h' as input and malloc.h is
 # present on the target.
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/compilers-util.configure
@@ -0,0 +1,65 @@
+# -*- Mode: python; 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/.
+
+@template
+@imports('textwrap')
+@imports(_from='mozbuild.configure', _import='DependsFunction')
+def compiler_class(compiler):
+    class Compiler(DependsFunction):
+        # 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.
+        # - `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.
+        def try_compile(self, includes=None, body='', flags=None,
+                        check_msg=None, onerror=lambda: 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)
+
+            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
+
+            def get_flags():
+                if flags:
+                    return flags[:]
+
+            @depends(self, extra_toolchain_flags)
+            @checking_fn
+            def func(compiler, extra_flags):
+                flags = get_flags() or []
+                flags += extra_flags
+                flags.append('-c')
+
+                return try_invoke_compiler(
+                    compiler.wrapper + [compiler.compiler] + compiler.flags,
+                    compiler.language, source, flags, onerror=onerror)
+
+            return func
+
+    compiler.__class__ = Compiler
+    return compiler
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -166,16 +166,18 @@ def toolchain_prefix(value, target, host
         return '%s-' % target.toolchain
 
 set_config('TOOLCHAIN_PREFIX', toolchain_prefix)
 add_old_configure_assignment('TOOLCHAIN_PREFIX', toolchain_prefix)
 
 
 # Compilers
 # ==============================================================
+include('compilers-util.configure')
+
 def try_preprocess(compiler, language, source):
     return try_invoke_compiler(compiler, language, source, ['-E'])
 
 @imports(_from='mozbuild.configure.constants', _import='CompilerType')
 @imports(_from='mozbuild.configure.constants',
          _import='CPU_preprocessor_checks')
 @imports(_from='mozbuild.configure.constants',
          _import='kernel_preprocessor_checks')
@@ -528,17 +530,17 @@ def compiler(language, host_or_target, c
         ('C++', host): 'HOST_CXX',
     }[language, host_or_target]
 
     default_compilers = {
         'C': lambda: default_c_compilers(host_or_target),
         'C++': lambda: default_cxx_compilers(c_compiler),
     }[language]()
 
-    what='the %s %s compiler' % (host_or_target_str, language),
+    what='the %s %s compiler' % (host_or_target_str, language)
 
     option(env=var, nargs=1, help='Path to %s' % what)
 
     # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
     # HOST_CXX variables.
     @depends_if(var)
     @imports(_from='itertools', _import='takewhile')
     @imports(_from='mozbuild.shellutil', _import='split', _as='shell_split')
@@ -731,16 +733,25 @@ def compiler(language, host_or_target, c
     # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
     # old-configure to do some of its still existing checks.
     if language == 'C':
         add_old_configure_assignment(
             '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
         add_old_configure_assignment(
             '%s_VERSION' % var, delayed_getattr(valid_compiler, 'version'))
 
+    valid_compiler = compiler_class(valid_compiler)
+
+    def compiler_error():
+        raise FatalCheckError('Failed compiling a simple %s source with %s'
+                              % (language, what))
+
+    valid_compiler.try_compile(check_msg='%s works' % what,
+                               onerror=compiler_error)
+
     return valid_compiler
 
 
 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,
--- a/python/mozbuild/mozbuild/test/configure/test_header_checks.py
+++ b/python/mozbuild/mozbuild/test/configure/test_header_checks.py
@@ -2,16 +2,17 @@
 # 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
+import mozpack.path as mozpath
 
 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
@@ -39,42 +40,50 @@ class TestHeaderChecks(unittest.TestCase
                         expected_flags=None):
 
         paths = {
             os.path.abspath('/usr/bin/mockcc'): self.get_mock_compiler(
                 expected_test_content=expected_test_content,
                 expected_flags=expected_flags),
         }
 
+        base_dir = os.path.join(topsrcdir, 'build', 'moz.configure')
+
         mock_compiler_defs = textwrap.dedent('''\
             @depends('--help')
+            def extra_toolchain_flags(_):
+                return []
+
+            include('%s/compilers-util.configure')
+
+            @compiler_class
+            @depends('--help')
             def c_compiler(_):
                 return namespace(
                     flags=[],
                     compiler=os.path.abspath('/usr/bin/mockcc'),
                     wrapper=[],
+                    language='C',
                 )
 
+            @compiler_class
             @depends('--help')
             def cxx_compiler(_):
                 return namespace(
                     flags=[],
                     compiler=os.path.abspath('/usr/bin/mockcc'),
                     wrapper=[],
+                    language='C++',
                 )
-            @depends('--help')
-            def extra_toolchain_flags(_):
-                return []
-        ''')
+        ''' % mozpath.normsep(base_dir))
 
         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)