Bug 1269517 - Implement check_header in Python configure. draft
authorChris Manchester <cmanchester@mozilla.com>
Fri, 22 Jul 2016 10:39:36 -0700
changeset 391398 61465f937384a9dde4573a3580ec69dd1b48521b
parent 391397 dc211d0b750db3e9cd5a692b875c9a86a49d06e7
child 391399 1abc04591638cc283a3aac52e27e14dd94a626de
push id23897
push usercmanchester@mozilla.com
push dateFri, 22 Jul 2016 17:39:54 +0000
bugs1269517
milestone50.0a1
Bug 1269517 - Implement check_header in Python configure. MozReview-Commit-ID: 1AypZg3f79a
build/moz.configure/android-ndk.configure
build/moz.configure/compilechecks.configure
build/moz.configure/toolchain.configure
python/mozbuild/mozbuild/test/configure/test_header_checks.py
--- a/build/moz.configure/android-ndk.configure
+++ b/build/moz.configure/android-ndk.configure
@@ -80,16 +80,23 @@ def android_platform(target, android_ver
     if not os.path.isdir(platform_dir):
         die("Android platform directory not found. With the current "
             "configuration, it should be in %s" % platform_dir)
 
     return platform_dir
 
 add_old_configure_assignment('android_platform', android_platform)
 
+@depends(android_platform)
+def extra_toolchain_flags(platform_dir):
+    if not platform_dir:
+        return []
+    return ['-idirafter',
+            os.path.join(platform_dir, 'usr', 'include')]
+
 @depends(target, host, ndk, '--with-android-toolchain',
          '--with-android-gnu-compiler-version')
 @checking('for the Android toolchain directory', lambda x: x or 'not found')
 @imports(_from='mozbuild.shellutil', _import='quote')
 def android_toolchain(target, host, ndk, toolchain, gnu_compiler_version):
     if not ndk:
         return
     if toolchain:
--- a/build/moz.configure/compilechecks.configure
+++ b/build/moz.configure/compilechecks.configure
@@ -27,29 +27,84 @@ def try_compile(includes=None, body='', 
         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)
+    def get_flags():
+        if flags:
+            return flags[:]
+
+    @depends(cxx_compiler, c_compiler, extra_toolchain_flags)
     @checking_fn
-    def check(cxx_info, c_info):
+    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.
+# - `header` is the header, as a file name, to check for.
+# - `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`.
+# - `includes` are additional includes, as file names, to appear before the
+#   header checked for.
+# - `when` is a depends function that if present will make performing the check
+#   conditional on the value of that function.
+@template
+def check_header(header, language='C++', flags=None, includes=None, when=None):
+    when = when or depends('--help')(lambda _: True)
+
+    if includes:
+        includes = includes[:]
+    else:
+        includes = []
+    includes.append(header)
+
+    @depends_when(try_compile(includes=includes, language=language, flags=flags,
+                              check_msg='for %s' % header), when=when)
+    def have_header(value):
+        if value is not None:
+            return True
+    header_var = 'HAVE_%s' % (header.upper()
+                                    .replace('-', '_')
+                                    .replace('/', '_')
+                                    .replace('.', '_'))
+    set_define(header_var, have_header)
+    return have_header
+
+# A convenience wrapper for check_header for checking multiple headers.
+# returns an array of the resulting checks in order corresponding to the
+# provided headers.
+# - `headers` are the headers to be checked.
+# - `kwargs` are keyword arguments passed verbatim to check_header.
+@template
+def check_headers(*headers, **kwargs):
+    checks = []
+    for header in headers:
+        checks.append(check_header(header, **kwargs))
+    return checks
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -50,16 +50,21 @@ set_config('YASM_ASFLAGS', yasm_asflags)
 def have_yasm(value):
     if value:
         return True
 
 set_config('HAVE_YASM', have_yasm)
 # Until the YASM variable is not necessary in old-configure.
 add_old_configure_assignment('YASM', have_yasm)
 
+@depends('--help')
+def extra_toolchain_flags(_):
+    # This value will be overriden for android builds, where
+    # extra flags are required to do basic checks.
+    return []
 
 # Android NDK
 # ==============================================================
 
 @depends('--disable-compile-environment', build_project, gonkdir, '--help')
 def android_ndk_include(compile_env, build_project, gonkdir, _):
     if compile_env and (gonkdir or build_project in ('mobile/android', 'js')):
         return 'android-ndk.configure'
--- a/python/mozbuild/mozbuild/test/configure/test_header_checks.py
+++ b/python/mozbuild/mozbuild/test/configure/test_header_checks.py
@@ -55,16 +55,19 @@ class TestHeaderChecks(unittest.TestCase
 
             @depends('--help')
             def cxx_compiler(_):
                 return namespace(
                     flags=[],
                     compiler=os.path.abspath('/usr/bin/mockcc'),
                     wrapper=[],
                 )
+            @depends('--help')
+            def extra_toolchain_flags(_):
+                return []
         ''')
 
         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'))
@@ -145,10 +148,107 @@ class TestHeaderChecks(unittest.TestCase
         ''')
         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
         '''))
 
+    def test_check_header(self):
+        expected_test_content = textwrap.dedent('''\
+          #include <foo.h>
+          int
+          main(void)
+          {
+
+            ;
+            return 0;
+          }
+        ''')
+
+        cmd = textwrap.dedent('''\
+            check_header('foo.h')
+        ''')
+
+        config, out, status = self.do_compile_test(cmd,
+                                                   expected_test_content=expected_test_content)
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'DEFINES': {'HAVE_FOO_H': True}})
+        self.assertEqual(out, textwrap.dedent('''\
+            checking for foo.h... yes
+        '''))
+
+    def test_check_header_include(self):
+        expected_test_content = textwrap.dedent('''\
+          #include <std.h>
+          #include <bar.h>
+          #include <foo.h>
+          int
+          main(void)
+          {
+
+            ;
+            return 0;
+          }
+        ''')
+
+        cmd = textwrap.dedent('''\
+           have_foo = check_header('foo.h', includes=['std.h', 'bar.h'])
+           set_config('HAVE_FOO_H', have_foo)
+        ''')
+
+        config, out, status = self.do_compile_test(cmd,
+                                                   expected_test_content=expected_test_content)
+
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {
+            'HAVE_FOO_H': True,
+            'DEFINES': {
+                'HAVE_FOO_H': True,
+            }
+        })
+        self.assertEqual(out, textwrap.dedent('''\
+            checking for foo.h... yes
+        '''))
+
+    def test_check_headers_multiple(self):
+        cmd = textwrap.dedent('''\
+            baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h')
+            set_config('HAVE_BAZ_BAR', baz_bar)
+            set_config('HAVE_QUUX_BAR', quux_bar)
+        ''')
+
+        config, out, status = self.do_compile_test(cmd)
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {
+            'HAVE_BAZ_BAR': True,
+            'HAVE_QUUX_BAR': True,
+            'DEFINES': {
+                'HAVE_BAZ_FOO_BAR_H': True,
+                'HAVE_BAZ_QUUX_FOO_BAR_H': True,
+            }
+        })
+        self.assertEqual(out, textwrap.dedent('''\
+            checking for baz/foo-bar.h... yes
+            checking for baz-quux/foo-bar.h... yes
+        '''))
+
+    def test_check_headers_not_found(self):
+
+        cmd = textwrap.dedent('''\
+            baz_bar, quux_bar = check_headers('baz/foo-bar.h', 'baz-quux/foo-bar.h',
+                                              flags=['-funknown-flag'])
+            set_config('HAVE_BAZ_BAR', baz_bar)
+            set_config('HAVE_QUUX_BAR', quux_bar)
+        ''')
+
+        config, out, status = self.do_compile_test(cmd)
+        self.assertEqual(status, 0)
+        self.assertEqual(config, {'DEFINES': {}})
+        self.assertEqual(out, textwrap.dedent('''\
+            checking for baz/foo-bar.h... no
+            checking for baz-quux/foo-bar.h... no
+        '''))
+
+
 if __name__ == '__main__':
     main()