Bug 1207882 - Add an initial partial implementation of a new, faster, build backend. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 24 Sep 2015 10:40:54 +0900
changeset 264882 66e58f784dbc4e2cb09e00544c2dc25f3432293d
parent 264881 d742983b5010bf26d1d1e31fd4112dd773fc9688
child 264883 af6ea1464daa5bed510b6ce8f319a389fb590a6e
push id29450
push usercbook@mozilla.com
push dateTue, 29 Sep 2015 10:00:39 +0000
treeherdermozilla-central@acdb22976ff8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1207882
milestone44.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 1207882 - Add an initial partial implementation of a new, faster, build backend. r=gps
config/faster/rules.mk
python/mozbuild/mozbuild/backend/fastermake.py
python/mozbuild/mozbuild/config_status.py
python/mozbuild/mozbuild/mach_commands.py
toolkit/content/buildconfig.html
new file mode 100644
--- /dev/null
+++ b/config/faster/rules.mk
@@ -0,0 +1,209 @@
+# 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/.
+
+# /!\ Please make sure to update the following comment when you touch this
+# file. Thank you /!\
+
+# The traditional Mozilla build system relied on going through the entire
+# build tree a number of times with different targets, and many of the
+# things happening at each step required other things happening in previous
+# steps without any documentation of those dependencies.
+#
+# This new build system tries to start afresh by establishing what files or
+# operations are needed for the build, and applying the necessary rules to
+# have those in place, relying on make dependencies to get them going.
+#
+# As of writing, only building non-compiled parts of Firefox is supported
+# here (a few other things are also left out). This is a starting point, with
+# the intent to grow this build system to make it more complete.
+#
+# This file contains rules and dependencies to get things working. The intent
+# is for a Makefile to define some dependencies and variables, and include
+# this file. What needs to be defined there, and ends up being generated by
+# python/mozbuild/mozbuild/backend/fastermake.py is the following:
+# - TOPSRCDIR/TOPOBJDIR, respectively the top source directory and the top
+#   object directory
+# - PYTHON, the path to the python executable
+# - ACDEFINES, which contains a set of -Dvar=name to be used during
+#   preprocessing
+# - MOZ_CHROME_FILE_FORMAT, which defines whether to use file copies or
+#   symbolic links
+# - JAR_MN_TARGETS, which defines the targets to use for jar manifest
+#   processing, see further below
+# - PP_TARGETS, which defines the file paths of preprocessed files, see
+#   further below
+# - INSTALL_MANIFESTS, which defines the list of base directories handled
+#   by install manifests, see further below
+# - MANIFEST_TARGETS, which defines the file paths of chrome manifests, see
+#   further below
+#
+# A convention used between this file and the Makefile including it is that
+# global Make variables names are uppercase, while "local" Make variables
+# applied to specific targets are lowercase.
+
+# Targets to be triggered for a default build
+default: $(addprefix install-,$(INSTALL_MANIFESTS))
+default: $(addprefix jar-,$(JAR_MN_TARGETS))
+
+# Explicit files to be built for a default build
+default: $(addprefix $(TOPOBJDIR)/,$(PP_TARGETS))
+default: $(addprefix $(TOPOBJDIR)/,$(MANIFEST_TARGETS))
+default: $(TOPOBJDIR)/dist/bin/greprefs.js
+default: $(TOPOBJDIR)/dist/bin/platform.ini
+default: $(TOPOBJDIR)/dist/bin/webapprt/webapprt.ini
+
+.PHONY: FORCE
+
+# Extra define to trigger some workarounds. We should strive to limit the
+# use of those. As of writing the only one is in
+# toolkit/content/buildconfig.html.
+ACDEFINES += -DBUILD_FASTER
+
+# Generic rule to fall back to the recursive make backend
+$(TOPOBJDIR)/%: FORCE
+	$(MAKE) -C $(dir $@) $(notdir $@)
+
+# Files under the faster/ sub-directory, however, are not meant to use the
+# fallback
+$(TOPOBJDIR)/faster/%: ;
+
+# And files under dist/ are meant to be copied from their first dependency
+# if there is no other rule.
+$(TOPOBJDIR)/dist/%:
+	rm -f $@
+	cp $< $@
+
+# Install files using install manifests
+#
+# The list of base directories is given in INSTALL_MANIFESTS. The
+# corresponding install manifests are named correspondingly, with forward
+# slashes replaced with underscores, and prefixed with `install_`. That is,
+# the install manifest for `dist/bin` would be `install_dist_bin`.
+$(addprefix install-,$(INSTALL_MANIFESTS)): install-%:
+	$(PYTHON) -m mozbuild.action.process_install_manifest \
+		--no-remove \
+		--no-remove-empty-directories \
+		$(TOPOBJDIR)/$* \
+		install_$(subst /,_,$*)
+
+# Preprocessed files. Ideally they would be using install manifests but
+# right now, it's not possible because of things like APP_BUILDID or
+# nsURLFormatter.js.
+# Things missing:
+# - XULPPFLAGS
+#
+# The list of preprocessed files is defined in PP_TARGETS. The list is
+# relative to TOPOBJDIR.
+# The source file for each of those preprocessed files is defined as a Make
+# dependency for the $(TOPOBJDIR)/path target. For example:
+#   PP_TARGETS = foo/bar
+#   $(TOPOBJDIR)/foo/bar: /path/to/source/for/foo/bar.in
+# The file name for the source doesn't need to be different.
+# Additionally, extra defines can be specified for a given preprocessing
+# by setting the `defines` variable specifically for the given target.
+# For example:
+#   $(TOPOBJDIR)/foo/bar: defines = -Dqux=foobar
+$(addprefix $(TOPOBJDIR)/,$(PP_TARGETS)): Makefile
+$(addprefix $(TOPOBJDIR)/,$(PP_TARGETS)): $(TOPOBJDIR)/%:
+	$(PYTHON) -m mozbuild.action.preprocessor \
+		--depend $(TOPOBJDIR)/faster/.deps/$(subst /,_,$*) \
+		-DAB_CD=en-US \
+		$(defines) \
+		$(ACDEFINES) \
+		$< \
+		-o $@
+
+# Include the dependency files from the above preprocessed files rule.
+$(foreach pp_target,$(PP_TARGETS), \
+	$(eval -include $(TOPOBJDIR)/faster/.deps/$(subst /,_,$(pp_target))))
+
+# Install files from jar manifests. Ideally, they would be using install
+# manifests, but the code to read jar manifests and emit appropriate
+# install manifests is not there yet.
+# Things missing:
+# - XULPPFLAGS
+# - DEFINES from config/config.mk
+# - L10N
+# - -e when USE_EXTENSION_MANIFEST is set in moz.build
+#
+# The list given in JAR_MN_TARGETS corresponds to the list of `jar-%` targets
+# to be processed, with the `jar-` prefix stripped.
+# The Makefile is expected to specify the source jar manifest as a dependency
+# to each target. There is no expectation that the `jar-%` target name matches
+# the source file name in any way. For example:
+#   JAR_MN_TARGETS = foo
+#   jar-foo: /path/to/some/jar.mn
+# Additionally, extra defines can be specified for the processing of the jar
+# manifest by settig the `defines` variable specifically for the given target.
+# For example:
+#   jar-foo: defines = -Dqux=foo
+# The default base path where files are going to be installed is `dist/bin`.
+# It is possible to use a different path by setting the `install_target`
+# variable. For example:
+#   jar-foo: install_target = dist/bin/foo
+# When processing jar manifests, relative paths given inside a jar manifest
+# can be resolved from an object directory. The default path for that object
+# directory is the translation of the jar manifest directory path from the
+# source directory to the object directory. That is, for
+# $(TOPSRCDIR)/path/to/jar.mn, the default would be $(TOPOBJDIR)/path/to.
+# In case a different path must be used for the object directory, the `objdir`
+# variable can be set. For example:
+#   jar-foo: objdir=/some/other/path
+jar-%: objdir ?= $(dir $(patsubst $(TOPSRCDIR)%,$(TOPOBJDIR)%,$<))
+jar-%: install_target ?= dist/bin
+jar-%:
+	cd $(objdir) && \
+	$(PYTHON) -m mozbuild.action.jar_maker \
+		-j $(TOPOBJDIR)/$(install_target)/chrome \
+		-t $(TOPSRCDIR) \
+		-f $(MOZ_CHROME_FILE_FORMAT) \
+		-c $(dir $<)/en-US \
+		-DAB_CD=en-US \
+		$(defines) \
+		$(ACDEFINES) \
+		$<
+
+# Create some chrome manifests
+# This rule is forced to run every time because it may be updating files that
+# already exit.
+#
+# The list of chrome manifests is given in MANIFEST_TARGETS, relative to the
+# top object directory. The content for those manifests is given in the
+# `content` variable associated with the target. For example:
+#   MANIFEST_TARGETS = foo
+#   $(TOPOBJDIR)/foo: content = "manifest foo.manifest" "manifest bar.manifest"
+$(addprefix $(TOPOBJDIR)/,$(MANIFEST_TARGETS)): FORCE
+	$(PYTHON) -m mozbuild.action.buildlist \
+		$@ \
+		$(content)
+
+# ============================================================================
+# Below is a set of additional dependencies and variables used to build things
+# that are not supported by data in moz.build.
+
+# GENERATED_FILES are not supported yet, and even if they were, the
+# dependencies are missing information.
+$(foreach p,linux osx windows,jar-browser-themes-$(p)-jar.mn): \
+jar-browser-themes-%-jar.mn: \
+	$(TOPOBJDIR)/browser/themes/%/tab-selected-end.svg \
+	$(TOPOBJDIR)/browser/themes/%/tab-selected-start.svg
+
+# These files are manually generated from
+# toolkit/components/urlformatter/Makefile.in and are force-included so that
+# the corresponding defines don't end up in the command lines.
+KEYS = mozilla_api_key google_api_key google_oauth_api_key bing_api_key
+$(TOPOBJDIR)/dist/bin/components/nsURLFormatter.js: \
+	$(addprefix $(TOPOBJDIR)/toolkit/components/urlformatter/, $(KEYS))
+$(TOPOBJDIR)/dist/bin/components/nsURLFormatter.js: defines += \
+	$(addprefix -I $(TOPOBJDIR)/toolkit/components/urlformatter/,$(KEYS))
+
+# Extra dependencies and/or definitions for preprocessed files.
+$(TOPOBJDIR)/dist/bin/application.ini: $(TOPOBJDIR)/config/buildid
+$(TOPOBJDIR)/dist/bin/application.ini: defines += \
+	-DAPP_BUILDID=$(shell cat $(TOPOBJDIR)/config/buildid)
+
+# Files to build with the recursive backend and simply copy
+$(TOPOBJDIR)/dist/bin/greprefs.js: $(TOPOBJDIR)/modules/libpref/greprefs.js
+$(TOPOBJDIR)/dist/bin/platform.ini: $(TOPOBJDIR)/toolkit/xre/platform.ini
+$(TOPOBJDIR)/dist/bin/webapprt/webapprt.ini: $(TOPOBJDIR)/webapprt/webapprt.ini
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/backend/fastermake.py
@@ -0,0 +1,258 @@
+# 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 absolute_import, unicode_literals
+
+from mozbuild.backend.common import CommonBackend
+from mozbuild.frontend.data import (
+    ContextDerived,
+    Defines,
+    DistFiles,
+    FinalTargetFiles,
+    JARManifest,
+    JavaScriptModules,
+    JsPreferenceFile,
+    Resources,
+    VariablePassthru,
+)
+from mozbuild.makeutil import Makefile
+from mozbuild.util import OrderedDefaultDict
+from mozpack.manifests import InstallManifest
+import mozpack.path as mozpath
+from collections import OrderedDict
+from itertools import chain
+
+
+class FasterMakeBackend(CommonBackend):
+    def _init(self):
+        super(FasterMakeBackend, self)._init()
+
+        self._seen_directories = set()
+        self._defines = dict()
+        self._jar_manifests = OrderedDict()
+
+        self._preprocess_files = OrderedDict()
+
+        self._manifest_entries = OrderedDefaultDict(list)
+
+        self._install_manifests = OrderedDefaultDict(InstallManifest)
+
+    def consume_object(self, obj):
+        # We currently ignore a lot of object types, so just acknowledge
+        # everything.
+        obj.ack()
+
+        if not isinstance(obj, Defines) and isinstance(obj, ContextDerived):
+            defines = self._defines.get(obj.objdir, [])
+            if defines:
+                defines = list(defines.get_defines())
+
+        if isinstance(obj, Defines):
+            self._defines[obj.objdir] = obj
+
+            # We're assuming below that Defines come first for a given objdir,
+            # which is kind of set in stone from the order things are treated
+            # in emitter.py.
+            assert obj.objdir not in self._seen_directories
+
+        elif isinstance(obj, JARManifest) and \
+                obj.install_target.startswith('dist/bin'):
+            self._jar_manifests[obj.path] = (obj.objdir,
+                                             obj.install_target,
+                                             defines)
+
+        elif isinstance(obj, VariablePassthru) and \
+                obj.install_target.startswith('dist/bin'):
+            for f in obj.variables.get('EXTRA_COMPONENTS', {}):
+                path = mozpath.join(obj.install_target, 'components',
+                                    mozpath.basename(f))
+                self._install_manifests[obj.install_target].add_symlink(
+                    mozpath.join(obj.srcdir, f),
+                    mozpath.join('components', mozpath.basename(f))
+                )
+                if f.endswith('.manifest'):
+                    manifest = mozpath.join(obj.install_target,
+                                            'chrome.manifest')
+                    self._manifest_entries[manifest].append(
+                        'manifest components/%s' % mozpath.basename(f))
+
+            for f in obj.variables.get('EXTRA_PP_COMPONENTS', {}):
+                path = mozpath.join(obj.install_target, 'components',
+                                    mozpath.basename(f))
+                self._preprocess_files[path] = (obj.srcdir, f, defines)
+                if f.endswith('.manifest'):
+                    manifest = mozpath.join(obj.install_target,
+                                            'chrome.manifest')
+                    self._manifest_entries[manifest].append(
+                        'manifest components/%s' % mozpath.basename(f))
+
+        elif isinstance(obj, JavaScriptModules) and \
+                obj.install_target.startswith('dist/bin'):
+            for path, strings in obj.modules.walk():
+                base = mozpath.join(obj.install_target, 'modules', path)
+                for f in strings:
+                    if obj.flavor == 'extra':
+                        self._install_manifests[obj.install_target].add_symlink(
+                            mozpath.join(obj.srcdir, f),
+                            mozpath.join('modules', path, mozpath.basename(f))
+                        )
+                    elif obj.flavor == 'extra_pp':
+                        dest = mozpath.join(base, mozpath.basename(f))
+                        self._preprocess_files[dest] = (obj.srcdir, f, defines)
+
+        elif isinstance(obj, JsPreferenceFile) and \
+                obj.install_target.startswith('dist/bin'):
+            # The condition for the directory value in config/rules.mk is:
+            # ifneq (,$(DIST_SUBDIR)$(XPI_NAME)$(LIBXUL_SDK))
+            # - LIBXUL_SDK is not supported (it likely doesn't work in the
+            # recursive backend anyways
+            # - when XPI_NAME is set, obj.install_target will start with
+            # dist/xpi-stage
+            # - when DIST_SUBDIR is set, obj.install_target will start with
+            # dist/bin/$(DIST_SUBDIR)
+            # So an equivalent condition that is not cumbersome for us and that
+            # is enough at least for now is checking if obj.install_target is
+            # different from dist/bin.
+            if obj.install_target == 'dist/bin':
+                pref_dir = 'defaults/pref'
+            else:
+                pref_dir = 'defaults/preferences'
+
+            dest = mozpath.join(obj.install_target, pref_dir,
+                                mozpath.basename(obj.path))
+            # on win32, pref files need CRLF line endings... see bug 206029
+            if self.environment.substs['OS_ARCH'] == 'WINNT':
+                defines.append('--line-endings=crlf')
+            # We preprocess these, but they don't necessarily have preprocessor
+            # directives, so tell the preprocessor to not complain about that.
+            defines.append('--silence-missing-directive-warnings')
+            self._preprocess_files[dest] = (obj.srcdir, obj.path, defines)
+
+        elif isinstance(obj, Resources) and \
+                obj.install_target.startswith('dist/bin'):
+            for path, strings in obj.resources.walk():
+                base = mozpath.join(obj.install_target, 'res', path)
+                for f in strings:
+                    flags = strings.flags_for(f)
+                    if flags and flags.preprocess:
+                        dest = mozpath.join(base, mozpath.basename(f))
+                        defines = Defines(obj._context, obj.defines)
+                        defines = list(defines.get_defines())
+                        defines.extend(['--marker', '%'])
+                        self._preprocess_files[dest] = (obj.srcdir, f, defines)
+                    else:
+                        self._install_manifests[obj.install_target].add_symlink(
+                            mozpath.join(obj.srcdir, f),
+                            mozpath.join('res', path, mozpath.basename(f))
+                        )
+
+        elif isinstance(obj, FinalTargetFiles) and \
+                obj.install_target.startswith('dist/bin'):
+            for path, strings in obj.files.walk():
+                base = mozpath.join(obj.install_target, path)
+                for f in strings:
+                    self._install_manifests[obj.install_target].add_symlink(
+                        mozpath.join(obj.srcdir, f),
+                        mozpath.join(path, mozpath.basename(f))
+                    )
+
+        elif isinstance(obj, DistFiles) and \
+                obj.install_target.startswith('dist/bin'):
+            # We preprocess these, but they don't necessarily have preprocessor
+            # directives, so tell the preprocessor to not complain about that.
+            defines.append('--silence-missing-directive-warnings')
+            for f in obj.files:
+                dest = mozpath.join(obj.install_target, mozpath.basename(f))
+                self._preprocess_files[dest] = (obj.srcdir, f, defines)
+
+        else:
+            return
+
+        self._seen_directories.add(obj.objdir)
+
+    def consume_finished(self):
+        mk = Makefile()
+        # Add the default rule at the very beginning.
+        mk.create_rule(['default'])
+        mk.add_statement('TOPSRCDIR = %s' % self.environment.topsrcdir)
+        mk.add_statement('TOPOBJDIR = %s' % self.environment.topobjdir)
+
+        # Add a few necessary variables inherited from configure
+        for var in (
+            'PYTHON',
+            'ACDEFINES',
+            'MOZ_CHROME_FILE_FORMAT',
+        ):
+            mk.add_statement('%s = %s' % (var, self.environment.substs[var]))
+
+        # Add all necessary information for jar manifest processing
+        jar_mn_targets = []
+
+        for path, (objdir, install_target, defines) in \
+                self._jar_manifests.iteritems():
+            rel_manifest = mozpath.relpath(path, self.environment.topsrcdir)
+            target = rel_manifest.replace('/', '-')
+            assert target not in jar_mn_targets
+            jar_mn_targets.append(target)
+            target = 'jar-%s' % target
+            mk.create_rule([target]).add_dependencies([path])
+            if objdir != mozpath.join(self.environment.topobjdir,
+                                      mozpath.dirname(rel_manifest)):
+                mk.create_rule([target]).add_dependencies(
+                    ['objdir = %s' % objdir])
+            if install_target != 'dist/bin':
+                mk.create_rule([target]).add_dependencies(
+                    ['install_target = %s' % install_target])
+            if defines:
+                mk.create_rule([target]).add_dependencies(
+                    ['defines = %s' % ' '.join(defines)])
+
+        mk.add_statement('JAR_MN_TARGETS = %s' % ' '.join(jar_mn_targets))
+
+        # Add information for chrome manifest generation
+        manifest_targets = []
+
+        for target, entries in self._manifest_entries.iteritems():
+            manifest_targets.append(target)
+            target = '$(TOPOBJDIR)/%s' % target
+            mk.create_rule([target]).add_dependencies(
+                ['content = %s' % ' '.join('"%s"' % e for e in entries)])
+
+        mk.add_statement('MANIFEST_TARGETS = %s' % ' '.join(manifest_targets))
+
+        # Add information for preprocessed files.
+        preprocess_targets = []
+
+        for target, (srcdir, f, defines) in self._preprocess_files.iteritems():
+            # This matches what PP_TARGETS do in config/rules.
+            if target.endswith('.in'):
+                target = target[:-3]
+                # PP_TARGETS assumes this is true, but doesn't enforce it.
+                assert target not in self._preprocess_files
+            preprocess_targets.append(target)
+            target = '$(TOPOBJDIR)/%s' % target
+            mk.create_rule([target]).add_dependencies(
+                [mozpath.join(srcdir, f)])
+            if defines:
+                mk.create_rule([target]).add_dependencies(
+                        ['defines = %s' % ' '.join(defines)])
+
+        mk.add_statement('PP_TARGETS = %s' % ' '.join(preprocess_targets))
+
+        # Add information for install manifests.
+        mk.add_statement('INSTALL_MANIFESTS = %s'
+                         % ' '.join(self._install_manifests.keys()))
+
+        mk.add_statement('include $(TOPSRCDIR)/config/faster/rules.mk')
+
+        for base, install_manifest in self._install_manifests.iteritems():
+            with self._write_file(
+                    mozpath.join(self.environment.topobjdir, 'faster',
+                                 'install_%s' % base.replace('/', '_'))) as fh:
+                install_manifest.write(fileobj=fh)
+
+        with self._write_file(
+                mozpath.join(self.environment.topobjdir, 'faster',
+                             'Makefile')) as fh:
+            mk.dump(fh, removal_guard=False)
--- a/python/mozbuild/mozbuild/config_status.py
+++ b/python/mozbuild/mozbuild/config_status.py
@@ -91,17 +91,18 @@ def config_status(topobjdir='.', topsrcd
                       help='update config.status by reconfiguring in the same conditions')
     parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                       help='display verbose output')
     parser.add_option('-n', dest='not_topobjdir', action='store_true',
                       help='do not consider current directory as top object directory')
     parser.add_option('-d', '--diff', action='store_true',
                       help='print diffs of changed files.')
     parser.add_option('-b', '--backend',
-                      choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse', 'VisualStudio'],
+                      choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse',
+                               'VisualStudio', 'FasterMake'],
                       default='RecursiveMake',
                       help='what backend to build (default: RecursiveMake).')
     options, args = parser.parse_args()
 
     # Without -n, the current directory is meant to be the top object directory
     if not options.not_topobjdir:
         topobjdir = os.path.abspath('.')
 
@@ -123,16 +124,19 @@ def config_status(topobjdir='.', topsrcd
     elif options.backend == 'CppEclipse':
         from mozbuild.backend.cpp_eclipse import CppEclipseBackend
         backend_cls = CppEclipseBackend
         if os.name == 'nt':
           raise Exception('Eclipse is not supported on Windows. Consider using Visual Studio instead.')
     elif options.backend == 'VisualStudio':
         from mozbuild.backend.visualstudio import VisualStudioBackend
         backend_cls = VisualStudioBackend
+    elif options.backend == 'FasterMake':
+        from mozbuild.backend.fastermake import FasterMakeBackend
+        backend_cls = FasterMakeBackend
 
     the_backend = backend_cls(env)
 
     reader = BuildReader(env)
     emitter = TreeMetadataEmitter(env)
     # This won't actually do anything because of the magic of generators.
     definitions = emitter.emit(reader.read_topsrcdir())
 
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -554,17 +554,18 @@ class Build(MachCommandBase):
 
     @Command('build-backend', category='build',
         description='Generate a backend used to build the tree.')
     @CommandArgument('-d', '--diff', action='store_true',
         help='Show a diff of changes.')
     # It would be nice to filter the choices below based on
     # conditions, but that is for another day.
     @CommandArgument('-b', '--backend',
-        choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse', 'VisualStudio'],
+        choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse',
+                 'VisualStudio', 'FasterMake'],
         default='RecursiveMake',
         help='Which backend to build (default: RecursiveMake).')
     def build_backend(self, backend='RecursiveMake', diff=False):
         python = self.virtualenv_manager.python_path
         config_status = os.path.join(self.topobjdir, 'config.status')
 
         if not os.path.exists(config_status):
             print('config.status not found.  Please run |mach configure| '
--- a/toolkit/content/buildconfig.html
+++ b/toolkit/content/buildconfig.html
@@ -49,17 +49,19 @@
     <tr>
       <td>@CC@</td>
       <td>@CC_VERSION@</td>
       <td>@CFLAGS@</td>
     </tr>
     <tr>
       <td>@CXX@</td>
       <td>@CXX_VERSION@</td>
+#ifndef BUILD_FASTER
       <td>@CXXFLAGS@ @CPPFLAGS@</td>
+#endif
     </tr>
   </tbody>
 </table>
 <h2>Configure arguments</h2>
 <p>@ac_configure_args@</p>
 #ifdef ANDROID
 <h2>Package name</h2>
 <p>@ANDROID_PACKAGE_NAME@</p>