Bug 1386876 - Add classes to handle compile flags computed by moz.build with templates, convert 'DISABLE_STL_WRAPPING' to use them. draft
authorChris Manchester <cmanchester@mozilla.com>
Fri, 28 Apr 2017 16:35:19 -0700
changeset 662476 094c79d23313f5c511f1a9584113c3976444538e
parent 660845 3c96d611ebd67fc219d22bcb476a72412c76f6c7
child 662477 82f9d5fd1020e5df5053f6f68efaf2c3ce1e0f88
push id79094
push userbmo:cmanchester@mozilla.com
push dateMon, 11 Sep 2017 18:33:35 +0000
bugs1386876
milestone57.0a1
Bug 1386876 - Add classes to handle compile flags computed by moz.build with templates, convert 'DISABLE_STL_WRAPPING' to use them. MozReview-Commit-ID: 3PYOtX4E8OC
browser/app/moz.build
build/templates.mozbuild
config/config.mk
old-configure.in
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/compilation/database.py
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/data.py
python/mozbuild/mozbuild/frontend/emitter.py
python/mozbuild/mozbuild/frontend/gyp_reader.py
python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
python/mozbuild/mozbuild/test/common.py
python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build
python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/test1.c
python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build
python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/test1.c
python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build
python/mozbuild/mozbuild/test/frontend/data/compile-flags/test1.c
python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build
python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/test1.c
python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
python/mozbuild/mozbuild/test/frontend/test_emitter.py
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -96,17 +96,17 @@ if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_
 #
 # The default heap size is 1MB on Win32.
 # The heap will grow if need be.
 #
 # Set it to 256k.  See bug 127069.
 if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
     LDFLAGS += ['/HEAP:0x40000']
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
 
 if CONFIG['MOZ_GPSD']:
--- a/build/templates.mozbuild
+++ b/build/templates.mozbuild
@@ -141,11 +141,15 @@ def HostRustLibrary(name, features=None)
     '''Template for host Rust libraries.'''
     HostLibrary(name)
 
     IS_RUST_LIBRARY = True
 
     if features:
         HOST_RUST_LIBRARY_FEATURES = features
 
+@template
+def DisableStlWrapping():
+    COMPILE_FLAGS['STL'] = []
+
 
 include('gecko_templates.mozbuild')
 include('test_templates.mozbuild')
--- a/config/config.mk
+++ b/config/config.mk
@@ -324,17 +324,17 @@ endif # CLANG_CL
 # Use warnings-as-errors if ALLOW_COMPILER_WARNINGS is not set to 1 (which
 # includes the case where it's undefined).
 ifneq (1,$(ALLOW_COMPILER_WARNINGS))
 CXXFLAGS += $(WARNINGS_AS_ERRORS)
 CFLAGS   += $(WARNINGS_AS_ERRORS)
 endif # ALLOW_COMPILER_WARNINGS
 
 COMPILE_CFLAGS	= $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CFLAGS) $(_DEPEND_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(MK_COMPILE_DEFINES)
-COMPILE_CXXFLAGS = $(if $(DISABLE_STL_WRAPPING),,$(STL_FLAGS)) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
+COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
 COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
 ASFLAGS += $(MOZBUILD_ASFLAGS)
 
 ifndef CROSS_COMPILE
 HOST_CFLAGS += $(RTL_FLAGS)
 endif
 
--- a/old-configure.in
+++ b/old-configure.in
@@ -363,17 +363,17 @@ fi # COMPILE_ENVIRONMENT
 
 AC_SUBST(MIDL_FLAGS)
 AC_SUBST(_MSC_VER)
 
 AC_SUBST(GNU_AS)
 AC_SUBST(GNU_CC)
 AC_SUBST(GNU_CXX)
 
-AC_SUBST(STL_FLAGS)
+AC_SUBST_LIST(STL_FLAGS)
 AC_SUBST(WRAP_STL_INCLUDES)
 AC_SUBST(MOZ_MSVC_STL_WRAP_RAISE)
 
 dnl ========================================================
 dnl Checks for programs.
 dnl ========================================================
 if test "$COMPILE_ENVIRONMENT"; then
 
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -31,16 +31,17 @@ from .common import CommonBackend
 from ..frontend.data import (
     AndroidAssetsDirs,
     AndroidResDirs,
     AndroidExtraResDirs,
     AndroidExtraPackages,
     BaseLibrary,
     BaseProgram,
     ChromeManifestEntry,
+    ComputedFlags,
     ConfigFileSubstitution,
     ContextDerived,
     ContextWrapped,
     Defines,
     DirectoryTraversal,
     ExternalLibrary,
     FinalTargetFiles,
     FinalTargetPreprocessedFiles,
@@ -589,16 +590,19 @@ class RecursiveMakeBackend(CommonBackend
             self._process_linked_libraries(obj, backend_file)
 
         elif isinstance(obj, LocalInclude):
             self._process_local_include(obj.path, backend_file)
 
         elif isinstance(obj, PerSourceFlag):
             self._process_per_source_flag(obj, backend_file)
 
+        elif isinstance(obj, ComputedFlags):
+            self._process_computed_flags(obj, backend_file)
+
         elif isinstance(obj, InstallationTarget):
             self._process_installation_target(obj, backend_file)
 
         elif isinstance(obj, ContextWrapped):
             # Process a rich build system object from the front-end
             # as-is.  Please follow precedent and handle CamelCaseData
             # in a function named _process_camel_case_data.  At some
             # point in the future, this unwrapping process may be
@@ -1209,16 +1213,20 @@ class RecursiveMakeBackend(CommonBackend
         else:
             path = d + path
         backend_file.write('LOCAL_INCLUDES += -I%s\n' % path)
 
     def _process_per_source_flag(self, per_source_flag, backend_file):
         for flag in per_source_flag.flags:
             backend_file.write('%s_FLAGS += %s\n' % (mozpath.basename(per_source_flag.file_name), flag))
 
+    def _process_computed_flags(self, computed_flags, backend_file):
+        for var, flags in computed_flags.get_flags():
+            backend_file.write('COMPUTED_%s += %s\n' % (var, make_quote(' '.join(flags))))
+
     def _process_java_jar_data(self, jar, backend_file):
         target = jar.name
         backend_file.write('JAVA_JAR_TARGETS += %s\n' % target)
         backend_file.write('%s_DEST := %s.jar\n' % (target, jar.name))
         if jar.sources:
             backend_file.write('%s_JAVAFILES := %s\n' %
                 (target, ' '.join(jar.sources)))
         if jar.generated_sources:
--- a/python/mozbuild/mozbuild/compilation/database.py
+++ b/python/mozbuild/mozbuild/compilation/database.py
@@ -5,16 +5,17 @@
 # This modules provides functionality for dealing with code completion.
 
 import os
 import types
 
 from mozbuild.compilation import util
 from mozbuild.backend.common import CommonBackend
 from mozbuild.frontend.data import (
+    ComputedFlags,
     Sources,
     GeneratedSources,
     DirectoryTraversal,
     Defines,
     Linkable,
     LocalInclude,
     PerSourceFlag,
     VariablePassthru,
@@ -65,17 +66,17 @@ class CompileDBBackend(CommonBackend):
 
         consumed = CommonBackend.consume_object(self, obj)
 
         if consumed:
             return True
 
         if isinstance(obj, DirectoryTraversal):
             self._envs[obj.objdir] = obj.config
-            for var in ('STL_FLAGS', 'VISIBILITY_FLAGS', 'WARNINGS_AS_ERRORS'):
+            for var in ('VISIBILITY_FLAGS', 'WARNINGS_AS_ERRORS'):
                 value = obj.config.substs.get(var)
                 if value:
                     self._local_flags[obj.objdir][var] = value
 
         elif isinstance(obj, (Sources, GeneratedSources)):
             # For other sources, include each source file.
             for f in obj.files:
                 self._build_db_line(obj.objdir, obj.relativedir, obj.config, f,
@@ -100,26 +101,27 @@ class CompileDBBackend(CommonBackend):
         elif isinstance(obj, VariablePassthru):
             if obj.variables.get('IS_GYP_DIR'):
                 self._gyp_dirs.add(obj.objdir)
             for var in ('MOZBUILD_CFLAGS', 'MOZBUILD_CXXFLAGS',
                         'MOZBUILD_CMFLAGS', 'MOZBUILD_CMMFLAGS',
                         'RTL_FLAGS', 'VISIBILITY_FLAGS'):
                 if var in obj.variables:
                     self._local_flags[obj.objdir][var] = obj.variables[var]
-            if (obj.variables.get('DISABLE_STL_WRAPPING') and
-                    'STL_FLAGS' in self._local_flags[obj.objdir]):
-                del self._local_flags[obj.objdir]['STL_FLAGS']
             if (obj.variables.get('ALLOW_COMPILER_WARNINGS') and
                     'WARNINGS_AS_ERRORS' in self._local_flags[obj.objdir]):
                 del self._local_flags[obj.objdir]['WARNINGS_AS_ERRORS']
 
         elif isinstance(obj, PerSourceFlag):
             self._per_source_flags[obj.file_name].extend(obj.flags)
 
+        elif isinstance(obj, ComputedFlags):
+            for var, flags in obj.get_flags():
+                self._local_flags[obj.objdir]['COMPUTED_%s' % var] = flags
+
         return True
 
     def consume_finished(self):
         CommonBackend.consume_finished(self)
 
         db = []
 
         for (directory, filename, unified), cmd in self._db.iteritems():
@@ -230,17 +232,17 @@ class CompileDBBackend(CommonBackend):
             value = cenv.substs.get(name)
             if not value:
                 return
             if isinstance(value, types.StringTypes):
                 value = value.split()
             db.extend(value)
 
         if canonical_suffix in ('.mm', '.cpp'):
-            db.append('$(STL_FLAGS)')
+            db.append('$(COMPUTED_CXXFLAGS)')
 
         db.extend((
             '$(VISIBILITY_FLAGS)',
             '$(DEFINES)',
             '-I%s' % mozpath.join(cenv.topsrcdir, reldir),
             '-I%s' % objdir,
             '$(LOCAL_INCLUDES)',
             '-I%s/dist/include' % cenv.topobjdir,
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -292,16 +292,36 @@ class InitializedDefines(ContextDerivedV
     def __init__(self, context, value=None):
         OrderedDict.__init__(self)
         for define in context.config.substs.get('MOZ_DEBUG_DEFINES', ()):
             self[define] = 1
         if value:
             self.update(value)
 
 
+class CompileFlags(ContextDerivedValue, dict):
+    def __init__(self, context):
+        self.flag_variables = (
+            ('STL', context.config.substs.get('STL_FLAGS'), ('CXXFLAGS',)),
+        )
+        self._known_keys = set(k for k, v, _ in self.flag_variables)
+
+        dict.__init__(self,
+                      ((k, TypedList(unicode)(v)) for k, v, _ in self.flag_variables))
+
+    def __setitem__(self, key, value):
+        if key not in self._known_keys:
+            raise ValueError('Invalid value. `%s` is not a compile flags '
+                             'category.' % key)
+        if not (isinstance(value, list) and all(isinstance(v, unicode) for v in value)):
+            raise ValueError('A list of strings must be provided as a value for a '
+                             'compile flags category.')
+        dict.__setitem__(self, key, value)
+
+
 class FinalTargetValue(ContextDerivedValue, unicode):
     def __new__(cls, context, value=""):
         if not value:
             value = 'dist/'
             if context['XPI_NAME']:
                 value += 'xpi-stage/' + context['XPI_NAME']
             else:
                 value += 'bin'
@@ -1683,16 +1703,21 @@ VARIABLES = {
         tree to install files into. Values are directories (relative to this
         file) whose content to copy into the Sphinx documentation tree.
         """),
 
     'SPHINX_PYTHON_PACKAGE_DIRS': (StrictOrderingOnAppendList, list,
         """Directories containing Python packages that Sphinx documents.
         """),
 
+    'COMPILE_FLAGS': (CompileFlags, dict,
+        """Recipe for compile flags for this context. Not to be manipulated
+        directly.
+        """),
+
     'CFLAGS': (List, list,
         """Flags passed to the C compiler for all of the C source files
            declared in this directory.
 
            Note that the ordering of flags matters here, these flags will be
            added to the compiler's command line in the same order as they
            appear in the moz.build file.
         """),
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -18,16 +18,18 @@ structures.
 from __future__ import absolute_import, unicode_literals
 
 from mozbuild.util import StrictOrderingOnAppendList
 from mozpack.chrome.manifest import ManifestEntry
 
 import mozpack.path as mozpath
 from .context import FinalTargetValue
 
+from collections import defaultdict, OrderedDict
+
 from ..util import (
     group_unified_files,
 )
 
 from ..testing import (
     all_test_flavors,
 )
 
@@ -152,16 +154,35 @@ class VariablePassthru(ContextDerived):
     in our build backends since we will continue to be tied to our rules.mk.
     """
     __slots__ = ('variables')
 
     def __init__(self, context):
         ContextDerived.__init__(self, context)
         self.variables = {}
 
+
+class ComputedFlags(ContextDerived):
+    """Aggregate flags for consumption by various backends.
+    """
+    __slots__ = ('flags',)
+
+    def __init__(self, context, reader_flags):
+        ContextDerived.__init__(self, context)
+        self.flags = reader_flags
+
+    def get_flags(self):
+        flags = defaultdict(list)
+        for key, _, dest_vars in self.flags.flag_variables:
+            value = self.flags.get(key)
+            if value:
+                for dest_var in dest_vars:
+                    flags[dest_var].extend(value)
+        return flags.items()
+
 class XPIDLFile(ContextDerived):
     """Describes an XPIDL file to be compiled."""
 
     __slots__ = (
         'source_path',
         'basename',
         'module',
         'add_to_manifest',
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -25,16 +25,17 @@ import pytoml
 from .data import (
     AndroidAssetsDirs,
     AndroidExtraPackages,
     AndroidExtraResDirs,
     AndroidResDirs,
     BaseSources,
     BrandingFiles,
     ChromeManifestEntry,
+    ComputedFlags,
     ConfigFileSubstitution,
     ContextWrapped,
     Defines,
     DirectoryTraversal,
     Exports,
     FinalTargetFiles,
     FinalTargetPreprocessedFiles,
     GeneratedEventWebIDLFile,
@@ -123,16 +124,17 @@ class TreeMetadataEmitter(LoggingMixin):
         self.info = {}
         for k, v in mozinfo.info.items():
             if isinstance(k, unicode):
                 k = k.encode('ascii')
             self.info[k] = v
 
         self._libs = OrderedDefaultDict(list)
         self._binaries = OrderedDict()
+        self._compile_dirs = set()
         self._linkage = []
         self._static_linking_shared = set()
         self._crate_verified_local = set()
         self._crate_directories = dict()
 
         # Keep track of external paths (third party build systems), starting
         # from what we run a subconfigure in. We'll eliminate some directories
         # as we traverse them with moz.build (e.g. js/src).
@@ -752,16 +754,18 @@ class TreeMetadataEmitter(LoggingMixin):
                 lib.lib_defines.update(lib_defines)
 
         # Only emit sources if we have linkables defined in the same context.
         # Note the linkables are not emitted in this function, but much later,
         # after aggregation (because of e.g. USE_LIBS processing).
         if not (linkables or host_linkables):
             return
 
+        self._compile_dirs.add(context.objdir)
+
         sources = defaultdict(list)
         gen_sources = defaultdict(list)
         all_flags = {}
         for symbol in ('SOURCES', 'HOST_SOURCES', 'UNIFIED_SOURCES'):
             srcs = sources[symbol]
             gen_srcs = gen_sources[symbol]
             context_srcs = context.get(symbol, [])
             for f in context_srcs:
@@ -900,17 +904,16 @@ class TreeMetadataEmitter(LoggingMixin):
         # them. We should aim to keep this set small because it violates the
         # desired abstraction of the build definition away from makefiles.
         passthru = VariablePassthru(context)
         varlist = [
             'ALLOW_COMPILER_WARNINGS',
             'ANDROID_APK_NAME',
             'ANDROID_APK_PACKAGE',
             'ANDROID_GENERATED_RESFILES',
-            'DISABLE_STL_WRAPPING',
             'EXTRA_DSO_LDOPTS',
             'RCFILE',
             'RESFILE',
             'RCINCLUDE',
             'DEFFILE',
             'WIN32_EXE_LDFLAGS',
             'LD_VERSION_SCRIPT',
             'USE_EXTENSION_MANIFEST',
@@ -1128,16 +1131,19 @@ class TreeMetadataEmitter(LoggingMixin):
 
         android_extra_packages = context.get('ANDROID_EXTRA_PACKAGES')
         if android_extra_packages:
             yield AndroidExtraPackages(context, android_extra_packages)
 
         if passthru.variables:
             yield passthru
 
+        if context.objdir in self._compile_dirs:
+            yield ComputedFlags(context, context['COMPILE_FLAGS'])
+
     def _create_substitution(self, cls, context, path):
         sub = cls(context)
         sub.input_path = '%s.in' % path.full_path
         sub.output_path = path.translated
         sub.relpath = path
 
         return sub
 
--- a/python/mozbuild/mozbuild/frontend/gyp_reader.py
+++ b/python/mozbuild/mozbuild/frontend/gyp_reader.py
@@ -321,17 +321,17 @@ def process_gyp_result(gyp_result, gyp_d
               '!/ipc/ipdl/_ipdlheaders',
               '/ipc/chromium/src',
               '/ipc/glue',
           ]
           # These get set via VC project file settings for normal GYP builds.
           if config.substs['OS_TARGET'] == 'WINNT':
               context['DEFINES']['UNICODE'] = True
               context['DEFINES']['_UNICODE'] = True
-        context['DISABLE_STL_WRAPPING'] = True
+        context['COMPILE_FLAGS']['STL'] = []
 
         for key, value in gyp_dir_attrs.sandbox_vars.items():
             if context.get(key) and isinstance(context[key], list):
                 # If we have a key from sanbox_vars that's also been
                 # populated here we use the value from sandbox_vars as our
                 # basis rather than overriding outright.
                 context[key] = value + context[key]
             else:
--- a/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
+++ b/python/mozbuild/mozbuild/test/backend/data/variable_passthru/moz.build
@@ -13,11 +13,9 @@ DEFFILE = 'baz.def'
 
 CFLAGS += ['-fno-exceptions', '-w']
 CXXFLAGS += ['-fcxx-exceptions', '-option with spaces']
 LDFLAGS += ['-ld flag with spaces', '-x']
 HOST_CFLAGS += ['-funroll-loops', '-wall']
 HOST_CXXFLAGS += ['-funroll-loops-harder', '-wall-day-everyday']
 WIN32_EXE_LDFLAGS += ['-subsystem:console']
 
-DISABLE_STL_WRAPPING = True
-
 ALLOW_COMPILER_WARNINGS = True
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -314,19 +314,16 @@ class TestRecursiveMakeBackend(BackendTe
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = {
             'ALLOW_COMPILER_WARNINGS': [
                 'ALLOW_COMPILER_WARNINGS := 1',
             ],
-            'DISABLE_STL_WRAPPING': [
-                'DISABLE_STL_WRAPPING := 1',
-            ],
             'VISIBILITY_FLAGS': [
                 'VISIBILITY_FLAGS :=',
             ],
             'RCFILE': [
                 'RCFILE := foo.rc',
             ],
             'RESFILE': [
                 'RESFILE := bar.res',
--- a/python/mozbuild/mozbuild/test/common.py
+++ b/python/mozbuild/mozbuild/test/common.py
@@ -30,18 +30,23 @@ class MockConfig(object):
             'MOZ_FOO': 'foo',
             'MOZ_BAR': 'bar',
             'MOZ_TRUE': '1',
             'MOZ_FALSE': '',
             'DLL_PREFIX': 'lib',
             'DLL_SUFFIX': '.so'
         }, **extra_substs)
 
-        self.substs_unicode = ReadOnlyDict({k.decode('utf-8'): v.decode('utf-8',
-            'replace') for k, v in self.substs.items()})
+        def decode_value(value):
+            if isinstance(value, list):
+                return [v.decode('utf-8', 'replace') for v in value]
+            return value.decode('utf-8', 'replace')
+
+        self.substs_unicode = ReadOnlyDict({k.decode('utf-8'): decode_value(v)
+                                            for k, v in self.substs.items()})
 
         self.defines = self.substs
 
         self.external_source_dir = None
         self.lib_prefix = 'lib'
         self.rust_lib_prefix = 'lib'
         self.lib_suffix = '.a'
         self.rust_lib_suffix = '.a'
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-field-validation/moz.build
@@ -0,0 +1,13 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    '''Template for libraries.'''
+    LIBRARY_NAME = name
+
+Library('dummy')
+
+COMPILE_FLAGS['STL_FLAGS'] = []
+
+UNIFIED_SOURCES += ['test1.c']
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags-type-validation/moz.build
@@ -0,0 +1,13 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    '''Template for libraries.'''
+    LIBRARY_NAME = name
+
+Library('dummy')
+
+COMPILE_FLAGS['STL'] = [None, 123]
+
+UNIFIED_SOURCES += ['test1.c']
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/compile-flags/moz.build
@@ -0,0 +1,15 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    '''Template for libraries.'''
+    LIBRARY_NAME = name
+
+Library('dummy')
+
+@template
+def DisableStlWrapping():
+    COMPILE_FLAGS['STL'] = []
+
+UNIFIED_SOURCES += ['test1.c']
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/disable-stl-wrapping/moz.build
@@ -0,0 +1,17 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+@template
+def Library(name):
+    '''Template for libraries.'''
+    LIBRARY_NAME = name
+
+Library('dummy')
+
+@template
+def DisableStlWrapping():
+    COMPILE_FLAGS['STL'] = []
+
+UNIFIED_SOURCES += ['test1.c']
+
+DisableStlWrapping()
new file mode 100644
--- a/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/variable-passthru/moz.build
@@ -15,11 +15,9 @@ DEFFILE = 'baz.def'
 
 CFLAGS += ['-fno-exceptions', '-w']
 CXXFLAGS += ['-fcxx-exceptions', '-include foo.h']
 LDFLAGS += ['-framework Foo', '-x']
 HOST_CFLAGS += ['-funroll-loops', '-wall']
 HOST_CXXFLAGS += ['-funroll-loops-harder', '-wall-day-everyday']
 WIN32_EXE_LDFLAGS += ['-subsystem:console']
 
-DISABLE_STL_WRAPPING = True
-
 ALLOW_COMPILER_WARNINGS = True
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -12,16 +12,17 @@ from mozunit import main
 from mozbuild.frontend.context import (
     ObjDirPath,
     Path,
 )
 from mozbuild.frontend.data import (
     AndroidResDirs,
     BrandingFiles,
     ChromeManifestEntry,
+    ComputedFlags,
     ConfigFileSubstitution,
     Defines,
     DirectoryTraversal,
     Exports,
     FinalTargetPreprocessedFiles,
     GeneratedFile,
     GeneratedSources,
     HostDefines,
@@ -175,17 +176,16 @@ class TestEmitterBasic(unittest.TestCase
         reader = self.reader('variable-passthru')
         objs = self.read_topsrcdir(reader)
 
         self.assertEqual(len(objs), 1)
         self.assertIsInstance(objs[0], VariablePassthru)
 
         wanted = {
             'ALLOW_COMPILER_WARNINGS': True,
-            'DISABLE_STL_WRAPPING': True,
             'NO_DIST_INSTALL': True,
             'VISIBILITY_FLAGS': '',
             'RCFILE': 'foo.rc',
             'RESFILE': 'bar.res',
             'RCINCLUDE': 'bar.rc',
             'DEFFILE': 'baz.def',
             'MOZBUILD_CFLAGS': ['-fno-exceptions', '-w'],
             'MOZBUILD_CXXFLAGS': ['-fcxx-exceptions', '-include foo.h'],
@@ -198,16 +198,42 @@ class TestEmitterBasic(unittest.TestCase
         }
 
         variables = objs[0].variables
         maxDiff = self.maxDiff
         self.maxDiff = None
         self.assertEqual(wanted, variables)
         self.maxDiff = maxDiff
 
+    def test_compile_flags(self):
+        reader = self.reader('compile-flags',
+                             extra_substs={'STL_FLAGS': ['-I/path/to/objdir/dist/stl_wrappers']})
+        sources, flags, lib = self.read_topsrcdir(reader)
+        self.assertIsInstance(flags, ComputedFlags)
+        self.assertEqual(flags.flags['STL'], reader.config.substs['STL_FLAGS'])
+
+    def test_compile_flags_validation(self):
+        reader = self.reader('compile-flags-field-validation')
+
+        with self.assertRaisesRegexp(BuildReaderError, 'Invalid value.'):
+            self.read_topsrcdir(reader)
+
+        reader = self.reader('compile-flags-type-validation')
+        with self.assertRaisesRegexp(BuildReaderError,
+                                     'A list of strings must be provided'):
+            self.read_topsrcdir(reader)
+
+
+    def test_disable_stl_wrapping(self):
+        reader = self.reader('disable-stl-wrapping',
+                             extra_substs={'STL_FLAGS': ['-I/path/to/objdir/dist/stl_wrappers']})
+        sources, flags, lib = self.read_topsrcdir(reader)
+        self.assertIsInstance(flags, ComputedFlags)
+        self.assertEqual(flags.flags['STL'], [])
+
     def test_use_yasm(self):
         # When yasm is not available, this should raise.
         reader = self.reader('use-yasm')
         with self.assertRaisesRegexp(SandboxValidationError,
             'yasm is not available'):
             self.read_topsrcdir(reader)
 
         # When yasm is available, this should work.
@@ -381,24 +407,25 @@ class TestEmitterBasic(unittest.TestCase
         icons = files._children['icons']
 
         self.assertEqual(icons._strings, ['quux.icns'])
 
     def test_program(self):
         reader = self.reader('program')
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 3)
-        self.assertIsInstance(objs[0], Program)
-        self.assertIsInstance(objs[1], SimpleProgram)
+        self.assertEqual(len(objs), 4)
+        self.assertIsInstance(objs[0], ComputedFlags)
+        self.assertIsInstance(objs[1], Program)
         self.assertIsInstance(objs[2], SimpleProgram)
+        self.assertIsInstance(objs[3], SimpleProgram)
 
-        self.assertEqual(objs[0].program, 'test_program.prog')
-        self.assertEqual(objs[1].program, 'test_program1.prog')
-        self.assertEqual(objs[2].program, 'test_program2.prog')
+        self.assertEqual(objs[1].program, 'test_program.prog')
+        self.assertEqual(objs[2].program, 'test_program1.prog')
+        self.assertEqual(objs[3].program, 'test_program2.prog')
 
     def test_test_manifest_missing_manifest(self):
         """A missing manifest file should result in an error."""
         reader = self.reader('test-manifest-missing-manifest')
 
         with self.assertRaisesRegexp(BuildReaderError, 'IOError: Missing files'):
             self.read_topsrcdir(reader)
 
@@ -811,16 +838,18 @@ class TestEmitterBasic(unittest.TestCase
     def test_sources(self):
         """Test that SOURCES works properly."""
         reader = self.reader('sources')
         objs = self.read_topsrcdir(reader)
 
         # The last object is a Linkable.
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
+        computed_flags = objs.pop()
+        self.assertIsInstance(computed_flags, ComputedFlags)
         self.assertEqual(len(objs), 6)
         for o in objs:
             self.assertIsInstance(o, Sources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 6)
 
         expected = {
@@ -863,17 +892,17 @@ class TestEmitterBasic(unittest.TestCase
     def test_generated_sources(self):
         """Test that GENERATED_SOURCES works properly."""
         reader = self.reader('generated-sources')
         objs = self.read_topsrcdir(reader)
 
         # The last object is a Linkable.
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
-        self.assertEqual(len(objs), 6)
+        self.assertEqual(len(objs), 7)
 
         generated_sources = [o for o in objs if isinstance(o, GeneratedSources)]
         self.assertEqual(len(generated_sources), 6)
 
         suffix_map = {obj.canonical_suffix: obj for obj in generated_sources}
         self.assertEqual(len(suffix_map), 6)
 
         expected = {
@@ -893,16 +922,18 @@ class TestEmitterBasic(unittest.TestCase
     def test_host_sources(self):
         """Test that HOST_SOURCES works properly."""
         reader = self.reader('host-sources')
         objs = self.read_topsrcdir(reader)
 
         # The last object is a Linkable
         linkable = objs.pop()
         self.assertTrue(linkable.cxx_link)
+        computed_flags = objs.pop()
+        self.assertIsInstance(computed_flags, ComputedFlags)
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, HostSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {
@@ -916,18 +947,19 @@ class TestEmitterBasic(unittest.TestCase
                 sources.files,
                 [mozpath.join(reader.config.topsrcdir, f) for f in files])
 
     def test_unified_sources(self):
         """Test that UNIFIED_SOURCES works properly."""
         reader = self.reader('unified-sources')
         objs = self.read_topsrcdir(reader)
 
-        # The last object is a Linkable, ignore it
-        objs = objs[:-1]
+        # The last object is a Linkable, the second to last ComputedFlags,
+        # ignore them.
+        objs = objs[:-2]
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, UnifiedSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {
@@ -942,18 +974,19 @@ class TestEmitterBasic(unittest.TestCase
                 [mozpath.join(reader.config.topsrcdir, f) for f in files])
             self.assertTrue(sources.have_unified_mapping)
 
     def test_unified_sources_non_unified(self):
         """Test that UNIFIED_SOURCES with FILES_PER_UNIFIED_FILE=1 works properly."""
         reader = self.reader('unified-sources-non-unified')
         objs = self.read_topsrcdir(reader)
 
-        # The last object is a Linkable, ignore it
-        objs = objs[:-1]
+        # The last object is a Linkable, the second to last ComputedFlags,
+        # ignore them.
+        objs = objs[:-2]
         self.assertEqual(len(objs), 3)
         for o in objs:
             self.assertIsInstance(o, UnifiedSources)
 
         suffix_map = {obj.canonical_suffix: obj for obj in objs}
         self.assertEqual(len(suffix_map), 3)
 
         expected = {
@@ -1043,18 +1076,19 @@ class TestEmitterBasic(unittest.TestCase
             self.read_topsrcdir(reader)
 
     def test_rust_library_dash_folding(self):
         '''Test that on-disk names of RustLibrary objects convert dashes to underscores.'''
         reader = self.reader('rust-library-dash-folding',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 1)
-        lib = objs[0]
+        self.assertEqual(len(objs), 2)
+        flags, lib = objs
+        self.assertIsInstance(flags, ComputedFlags)
         self.assertIsInstance(lib, RustLibrary)
         self.assertRegexpMatches(lib.lib_name, "random_crate")
         self.assertRegexpMatches(lib.import_name, "random_crate")
         self.assertRegexpMatches(lib.basename, "random-crate")
 
     def test_multiple_rust_libraries(self):
         '''Test that linking multiple Rust libraries throws an error'''
         reader = self.reader('multiple-rust-libraries',
@@ -1063,18 +1097,19 @@ class TestEmitterBasic(unittest.TestCase
              'Cannot link multiple Rust libraries'):
             self.read_topsrcdir(reader)
 
     def test_rust_library_features(self):
         '''Test that RustLibrary features are correctly emitted.'''
         reader = self.reader('rust-library-features',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
-        self.assertEqual(len(objs), 1)
-        lib = objs[0]
+        self.assertEqual(len(objs), 2)
+        flags, lib = objs
+        self.assertIsInstance(flags, ComputedFlags)
         self.assertIsInstance(lib, RustLibrary)
         self.assertEqual(lib.features, ['musthave', 'cantlivewithout'])
 
     def test_rust_library_duplicate_features(self):
         '''Test that duplicate RustLibrary features are rejected.'''
         reader = self.reader('rust-library-duplicate-features')
         with self.assertRaisesRegexp(SandboxValidationError,
              'features for .* should not contain duplicates'):
@@ -1133,29 +1168,29 @@ class TestEmitterBasic(unittest.TestCase
         self.assertEqual(objs[0].name, 'some')
 
     def test_host_rust_libraries(self):
         '''Test HOST_RUST_LIBRARIES emission.'''
         reader = self.reader('host-rust-libraries',
                              extra_substs=dict(RUST_HOST_TARGET='i686-pc-windows-msvc',
                                                HOST_BIN_SUFFIX='.exe'))
         objs = self.read_topsrcdir(reader)
-        self.assertEqual(len(objs), 1)
-        self.assertIsInstance(objs[0], HostRustLibrary)
-        self.assertRegexpMatches(objs[0].lib_name, 'host_lib')
-        self.assertRegexpMatches(objs[0].import_name, 'host_lib')
+        self.assertEqual(len(objs), 2)
+        self.assertIsInstance(objs[1], HostRustLibrary)
+        self.assertRegexpMatches(objs[1].lib_name, 'host_lib')
+        self.assertRegexpMatches(objs[1].import_name, 'host_lib')
 
     def test_crate_dependency_path_resolution(self):
         '''Test recursive dependencies resolve with the correct paths.'''
         reader = self.reader('crate-dependency-path-resolution',
                              extra_substs=dict(RUST_TARGET='i686-pc-windows-msvc'))
         objs = self.read_topsrcdir(reader)
 
-        self.assertEqual(len(objs), 1)
-        self.assertIsInstance(objs[0], RustLibrary)
+        self.assertEqual(len(objs), 2)
+        self.assertIsInstance(objs[1], RustLibrary)
 
     def test_android_res_dirs(self):
         """Test that ANDROID_RES_DIRS works properly."""
         reader = self.reader('android-res-dirs')
         objs = self.read_topsrcdir(reader)
 
         self.assertEqual(len(objs), 1)
         self.assertIsInstance(objs[0], AndroidResDirs)
@@ -1169,39 +1204,42 @@ class TestEmitterBasic(unittest.TestCase
         self.assertEquals([p.full_path for p in objs[0].paths], expected)
 
     def test_install_shared_lib(self):
         """Test that we can install a shared library with TEST_HARNESS_FILES"""
         reader = self.reader('test-install-shared-lib')
         objs = self.read_topsrcdir(reader)
         self.assertIsInstance(objs[0], TestHarnessFiles)
         self.assertIsInstance(objs[1], VariablePassthru)
-        self.assertIsInstance(objs[2], SharedLibrary)
+        self.assertIsInstance(objs[2], ComputedFlags)
+        self.assertIsInstance(objs[3], SharedLibrary)
         for path, files in objs[0].files.walk():
             for f in files:
                 self.assertEqual(str(f), '!libfoo.so')
                 self.assertEqual(path, 'foo/bar')
 
     def test_symbols_file(self):
         """Test that SYMBOLS_FILE works"""
         reader = self.reader('test-symbols-file')
-        genfile, shlib = self.read_topsrcdir(reader)
+        genfile, flags, shlib = self.read_topsrcdir(reader)
         self.assertIsInstance(genfile, GeneratedFile)
+        self.assertIsInstance(flags, ComputedFlags)
         self.assertIsInstance(shlib, SharedLibrary)
         # This looks weird but MockConfig sets DLL_{PREFIX,SUFFIX} and
         # the reader method in this class sets OS_TARGET=WINNT.
         self.assertEqual(shlib.symbols_file, 'libfoo.so.def')
 
     def test_symbols_file_objdir(self):
         """Test that a SYMBOLS_FILE in the objdir works"""
         reader = self.reader('test-symbols-file-objdir')
-        genfile, shlib = self.read_topsrcdir(reader)
+        genfile, flags, shlib = self.read_topsrcdir(reader)
         self.assertIsInstance(genfile, GeneratedFile)
         self.assertEqual(genfile.script,
                          mozpath.join(reader.config.topsrcdir, 'foo.py'))
+        self.assertIsInstance(flags, ComputedFlags)
         self.assertIsInstance(shlib, SharedLibrary)
         self.assertEqual(shlib.symbols_file, 'foo.symbols')
 
     def test_symbols_file_objdir_missing_generated(self):
         """Test that a SYMBOLS_FILE in the objdir that's missing
         from GENERATED_FILES is an error.
         """
         reader = self.reader('test-symbols-file-objdir-missing-generated')