Bug 902825 - Allow generating dist/include/mozconfig/*.h; r?glandium draft
authorMike Shal <mshal@mozilla.com>
Wed, 14 Dec 2016 16:03:49 -0500
changeset 675778 7439e735a7c20f1caf34966c574bb67db950dfb4
parent 675777 c39236c283cf123c84563d9a3ad4126be048eda8
child 675779 8024747d3d8c52563d1f1c1f89fe587b4fdc40ad
push id83240
push userbmo:mshal@mozilla.com
push dateThu, 05 Oct 2017 21:34:38 +0000
reviewersglandium
bugs902825
milestone58.0a1
Bug 902825 - Allow generating dist/include/mozconfig/*.h; r?glandium Using the defines from the config environment, we can generate a set of header files in dist/include/mozconfig/ so that source files can include them to get access to specific configure values. Instead of rebuilding the whole world when a single define changes (due to its presence in mozilla-config.h), we can recompile only the set of files that actually depend on its value. This begins the process by generating the individual headers from all of the non-global defines in moz.configure. A non-global config item must have the following properties: 1) For defines: They are used in C/C++ code by doing #include "mozconfig/DEFINE_NAME.h" or have a suitable CONFIGURE_SUBST_FILE with those defines (eg: xpcom/xpcom-config.h) 2) For substs: They are not used in Makefile.in's or included .mk files, because the values are not exported to autoconf.mk If a variable is used in both set_define() and set_config(), then both properties must apply for it to be marked non-global. MozReview-Commit-ID: 22jiYaamSo2
python/mozbuild/mozbuild/backend/common.py
python/mozbuild/mozbuild/test/backend/common.py
python/mozbuild/mozbuild/test/backend/data/mozconfig-header/moz.build
python/mozbuild/mozbuild/test/backend/test_mozconfig_header.py
python/mozbuild/mozbuild/test/python.ini
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -43,16 +43,24 @@ from mozbuild.jar import (
     DeprecatedJarManifest,
     JarManifestParser,
 )
 from mozbuild.preprocessor import Preprocessor
 from mozpack.chrome.manifest import parse_manifest_line
 
 from mozbuild.util import group_unified_files
 
+MOZCONFIG_HEADER = '''/* THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT */
+
+#ifndef {header_guard}
+#define {header_guard}
+{define_var}
+#endif
+'''
+
 class XPIDLManager(object):
     """Helps manage XPCOM IDLs in the context of the build system."""
     def __init__(self, config):
         self.config = config
         self.topsrcdir = config.topsrcdir
         self.topobjdir = config.topobjdir
 
         self.idls = {}
@@ -327,16 +335,20 @@ class CommonBackend(BuildBackend):
                                                           files_per_unified_file=16))
 
         self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False)
         self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping)
 
         for config in self._configs:
             self.backend_input_files.add(config.source)
 
+        # Generate the mozconfig/*.h headers for individual dependencies on
+        # configure defines.
+        self._generate_mozconfig_headers()
+
         # Write out a machine-readable file describing binaries.
         topobjdir = self.environment.topobjdir
         with self._write_file(mozpath.join(topobjdir, 'binaries.json')) as fh:
             d = {
                 'shared_libraries': [s.to_dict() for s in self._binaries.shared_libraries],
                 'programs': [p.to_dict() for p in self._binaries.programs],
             }
             json.dump(d, fh, sort_keys=True, indent=4)
@@ -519,8 +531,32 @@ class CommonBackend(BuildBackend):
                     FinalTargetPreprocessedFiles(jar_context, files_pp))
 
             for m in jarinfo.chrome_manifests:
                 entry = parse_manifest_line(
                     mozpath.dirname(jarinfo.name),
                     m.replace('%', mozpath.basename(jarinfo.name) + '/'))
                 self.consume_object(ChromeManifestEntry(
                     jar_context, '%s.manifest' % jarinfo.name, entry))
+
+    def _write_mozconfig_header(self, include_dir, define, value=None):
+        path = mozpath.join(include_dir, define + '.h')
+        if value is not None:
+            define_var = '#define {define} {value}'.format(
+                define = define,
+                value = value,
+            )
+        else:
+            define_var = ''
+
+        with self._write_file(path) as fh:
+            fh.write(MOZCONFIG_HEADER.format(
+                header_guard = 'MOZCONFIG_%s_H' % define,
+                define_var = define_var,
+            ))
+
+    def _generate_mozconfig_headers(self):
+        include_dir = mozpath.join(self.environment.topobjdir, 'dist/include/mozconfig')
+
+        for var in self.environment.non_global_config:
+            if var in self.environment.alldefines:
+                v = self.environment.defines.get(var)
+                self._write_mozconfig_header(include_dir, var, v)
--- a/python/mozbuild/mozbuild/test/backend/common.py
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -164,17 +164,43 @@ CONFIGS = defaultdict(lambda: {
     'prog-lib-c-only': {
         'defines': {},
         'non_global_config': [],
         'substs': {
             'COMPILE_ENVIRONMENT': '1',
             'LIB_SUFFIX': '.a',
             'BIN_SUFFIX': '',
         },
-    }
+    },
+    'mozconfig-header': {
+        'defines': {
+            'MOZ_FOO': '1',
+            'MOZ_BAR': '2',
+            'MOZ_BAZ': '3',
+            'MOZ_CONST': '4',
+            'MOZ_NON_GLOBAL_1': 'ng1',
+        },
+        'alldefines': {
+            'MOZ_FOO',
+            'MOZ_BAR',
+            'MOZ_BAZ',
+            'MOZ_CONST',
+            'MOZ_NON_GLOBAL_1',
+        },
+        'non_global_config': [
+            'MOZ_FOO',
+            'MOZ_BAR',
+            'MOZ_CONST',
+            'MOZ_NONGLOBAL_1',
+        ],
+        'substs': {
+            'MOZ_FOO': '1',
+            'MOZ_SUBST': 'subst',
+        },
+    },
 })
 
 
 class BackendTester(unittest.TestCase):
     def setUp(self):
         self._old_env = dict(os.environ)
         os.environ.pop('MOZ_OBJDIR', None)
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/data/mozconfig-header/moz.build
@@ -0,0 +1,1 @@
+# Stub file for testing mozconfig defines
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_mozconfig_header.py
@@ -0,0 +1,63 @@
+# 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 unicode_literals
+
+import os
+import stat
+
+import mozpack.path as mozpath
+from mozunit import main
+
+from mozbuild.backend.recursivemake import RecursiveMakeBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestMozconfigHeader(BackendTester):
+    def _check_header(self, env, name, exists, value=None):
+        header = mozpath.join(env.topobjdir, 'dist', 'include', 'mozconfig','%s.h' % name)
+        self.assertEqual(os.path.exists(header), exists)
+        if value:
+            with open(header) as fh:
+                lines = fh.readlines()
+            self.assertIn('#define %s %s\n' % (name, value), lines)
+
+            return os.stat(header)[stat.ST_MTIME]
+
+    def test_headers_written(self):
+        """Ensure all mozconfig_headers are written out."""
+        env = self._consume('mozconfig-header', RecursiveMakeBackend)
+
+        # MOZ_FOO, MOZ_BAR, and MOZ_CONST are the mozconfig headers from the
+        # config. MOZ_BAZ is a define but not a mozconfig_header
+        self._check_header(env, 'MOZ_FOO', True, 1)
+        self._check_header(env, 'MOZ_BAR', True, 2)
+        old_ts = self._check_header(env, 'MOZ_CONST', True, 4)
+        self._check_header(env, 'MOZ_BAZ', False)
+        self._check_header(env, 'MOZ_NEW', False)
+
+        # Now MOZ_BAR is removed from non_global_config, MOZ_FOO is changed,
+        # MOZ_CONST remains the same, and MOZ_NEW is added.
+        env.defines = {d: env.defines[d] for d in env.defines.keys() if d != 'MOZ_BAR'}
+        env.defines['MOZ_FOO'] = 'newfoo'
+        env.defines['MOZ_NEW'] = 'new'
+        env.non_global_config = [
+            'MOZ_FOO',
+            'MOZ_CONST',
+            'MOZ_NEW',
+            'MOZ_NON_GLOBAL_1',
+        ]
+        env.alldefines.add('MOZ_NEW')
+        self._consume('mozconfig-header', RecursiveMakeBackend, env=env)
+        self._check_header(env, 'MOZ_FOO', True, 'newfoo')
+        self._check_header(env, 'MOZ_BAR', False)
+        new_ts = self._check_header(env, 'MOZ_CONST', True, 4)
+        self._check_header(env, 'MOZ_BAZ', False)
+        self._check_header(env, 'MOZ_NEW', True, 'new')
+
+        # Assert that the file representing an unchanged define is not touched.
+        self.assertEqual(old_ts, new_ts)
+
+if __name__ == '__main__':
+    main()
--- a/python/mozbuild/mozbuild/test/python.ini
+++ b/python/mozbuild/mozbuild/test/python.ini
@@ -1,13 +1,14 @@
 [action/test_buildlist.py]
 [action/test_generate_browsersearch.py]
 [action/test_package_fennec_apk.py]
 [backend/test_build.py]
 [backend/test_configenvironment.py]
+[backend/test_mozconfig_header.py]
 [backend/test_partialconfigenvironment.py]
 [backend/test_recursivemake.py]
 [backend/test_test_manifest.py]
 [backend/test_visualstudio.py]
 [codecoverage/test_lcov_rewrite.py]
 [compilation/test_warnings.py]
 [configure/lint.py]
 [configure/test_checks_configure.py]