Bug 1269517 - Implement check_header in Python configure. r=glandium
authorChris Manchester <cmanchester@mozilla.com>
Tue, 26 Jul 2016 15:27:19 -0700
changeset 346835 2081a003f2d83f456ae6a8d1bfa77157508960b7
parent 346834 cd2ca7c770fd94e67af3f4f97ceab20a373eb29c
child 346836 b50c01f263a5c8f2d55b2bad0603a70b10ae4981
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [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 check_header in Python configure. r=glandium 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()