Bug 1257823 - Move set_config() to the global scope. r=nalexander
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 22 Mar 2016 14:21:32 +0900
changeset 290156 9d2b6f4c3ee8366365c16cee6cbda90c4688c743
parent 290155 8debac7402e85ae922bf42c752bc835794b5e40f
child 290157 62ae3968b2d82f17ec2853714ab565c02a7cb9bc
push id30114
push usercbook@mozilla.com
push dateThu, 24 Mar 2016 15:15:54 +0000
treeherdermozilla-central@24c5fbde4488 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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 1257823 - Move set_config() to the global scope. r=nalexander The way set_config is set currently makes it difficult to introspect moz.configure files to know what configuration items are being set, because they're hidden in the control flow of functions. This makes some of the moz.configure more convoluted, but this is why there are templates, and we can improve the recurring cases afterwards.
--- a/b2g/common.configure
+++ b/b2g/common.configure
@@ -9,13 +9,15 @@
 option(env='MOZTTDIR', nargs=1, help='Path to truetype fonts for B2G')
 def mozttdir(value):
     if value:
         path = value[0]
         if not os.path.isdir(path):
             error('MOZTTDIR "%s" is not a valid directory' % path)
-        set_config('MOZTTDIR', path)
         set_define('PACKAGE_MOZTT', True)
+        return path
+set_config('MOZTTDIR', mozttdir)
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -65,16 +65,21 @@ def check_prog(var, progs, allow_missing
             result = find_program(prog)
             if result:
                 return result
         return not_found
     def postcheck(value):
-        set_config(var, ':' if value is not_found else value)
         if value is not_found and not allow_missing:
             from mozbuild.shellutil import quote
             error('Cannot find %s (tried: %s)'
                   % (var.lower(), ', '.join(quote(p) for p in progs)))
         return None if value is not_found else value
+    @depends(postcheck)
+    def normalized_for_config(value):
+        return ':' if value is None else value
+    set_config(var, normalized_for_config)
     return postcheck
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -21,20 +21,16 @@ def check_build_environment(help, dist):
         dist = os.path.join(topobjdir, 'dist')
     result = namespace(
-    set_config('TOPSRCDIR', topsrcdir)
-    set_config('TOPOBJDIR', topobjdir)
-    set_config('MOZ_BUILD_ROOT', topobjdir)
-    set_config('DIST', dist)
     if help:
         return result
     if topsrcdir == topobjdir:
             '  ***\n'
             '  * Building directly in the main source directory is not allowed.\n'
@@ -64,16 +60,22 @@ def check_build_environment(help, dist):
             '  *     1. cd %s\n'
             '  *     2. gmake distclean\n'
             '  ***'
             % ('\n  '.join(conflict_files), topsrcdir)
     return result
+set_config('TOPSRCDIR', delayed_getattr(check_build_environment, 'topsrcdir'))
+set_config('TOPOBJDIR', delayed_getattr(check_build_environment, 'topobjdir'))
+set_config('MOZ_BUILD_ROOT', delayed_getattr(check_build_environment,
+                                             'topobjdir'))
+set_config('DIST', delayed_getattr(check_build_environment, 'dist'))
 option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
 option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
 option(env='MOZCONFIG', nargs=1, help='Mozconfig location')
 # Read user mozconfig
 # ==============================================================
@@ -215,20 +217,20 @@ def virtualenv_python(env_python, build_
         # Windows.
         sys.exit(subprocess.call([python] + sys.argv))
     # We are now in the virtualenv
     import distutils.sysconfig
     if not distutils.sysconfig.get_python_lib():
         error('Could not determine python site packages directory')
-    set_config('PYTHON', python)
     add_old_configure_assignment('PYTHON', python)
     return python
+set_config('PYTHON', virtualenv_python)
 # Inject mozconfig options
 # ==============================================================
 def command_line_helper():
     # This escapes the sandbox. Don't copy this. This is only here because
     # it is a one off and because the required functionality doesn't need
@@ -499,45 +501,55 @@ def target_variables(target):
     elif target.kernel == 'Darwin' or (target.kernel == 'Linux' and
                                        target.os == 'GNU'):
         os_target = target.kernel
         os_arch = target.kernel
         os_target = target.os
         os_arch = target.kernel
     add_old_configure_assignment('OS_TARGET', os_target)
-    set_config('OS_TARGET', os_target)
     add_old_configure_assignment('OS_ARCH', os_arch)
-    set_config('OS_ARCH', os_arch)
     if target.os == 'Darwin' and target.cpu == 'x86':
         os_test = 'i386'
         os_test = target.raw_cpu
     add_old_configure_assignment('OS_TEST', os_test)
-    set_config('OS_TEST', os_test)
     add_old_configure_assignment('CPU_ARCH', target.cpu)
-    set_config('CPU_ARCH', target.cpu)
+    return namespace(
+        OS_TARGET=os_target,
+        OS_ARCH=os_arch,
+        OS_TEST=os_test,
+        INTEL_ARCHITECTURE=target.cpu in ('x86', 'x86_64') or None,
+    )
-    if target.cpu in ('x86', 'x86_64'):
-        set_config('INTEL_ARCHITECTURE', True)
+set_config('OS_TARGET', delayed_getattr(target_variables, 'OS_TARGET'))
+set_config('OS_ARCH', delayed_getattr(target_variables, 'OS_ARCH'))
+set_config('OS_TEST', delayed_getattr(target_variables, 'OS_TEST'))
+set_config('CPU_ARCH', delayed_getattr(target, 'cpu'))
+set_config('INTEL_ARCHITECTURE', delayed_getattr(target_variables,
+                                                 'INTEL_ARCHITECTURE'))
+set_config('TARGET_CPU', delayed_getattr(target, 'raw_cpu'))
+set_config('TARGET_OS', delayed_getattr(target, 'raw_os'))
-    set_config('TARGET_CPU', target.raw_cpu)
-    set_config('TARGET_OS', target.raw_os)
 def host_variables(host):
     if host.kernel == 'kFreeBSD':
         os_arch = 'GNU_kFreeBSD'
         os_arch = host.kernel
     add_old_configure_assignment('HOST_OS_ARCH', os_arch)
-    set_config('HOST_OS_ARCH', os_arch)
+    return namespace(
+        HOST_OS_ARCH=os_arch,
+    )
+set_config('HOST_OS_ARCH', delayed_getattr(host_variables, 'HOST_OS_ARCH'))
 def target_platform_defines(target):
     if target.kernel == 'WINNT':
         set_define('_WINDOWS', True)
         set_define('WIN32', True)
         set_define('XP_WIN', True)
         set_define('XP_WIN32', True)
@@ -580,69 +592,78 @@ option('--with-external-source-dir', env
 @depends('--enable-project', '--with-external-source-dir',
          check_build_environment, '--help')
 def include_project_configure(project, external_source_dir, build_env, help):
     if not project:
         error('--enable-project is required.')
     base_dir = build_env.topsrcdir
     if external_source_dir:
-        set_config('EXTERNAL_SOURCE_DIR', external_source_dir[0])
         base_dir = os.path.join(base_dir, external_source_dir[0])
     path = os.path.join(base_dir, project[0], 'moz.configure')
     if not os.path.exists(path):
         error('Cannot find project %s' % project[0])
     return path
+def external_source_dir(value):
+    if value:
+        return value[0]
+set_config('EXTERNAL_SOURCE_DIR', external_source_dir)
 @depends(include_project_configure, check_build_environment, '--help')
 def build_project(include_project_configure, build_env, help):
     ret = os.path.dirname(os.path.relpath(include_project_configure,
-    set_config('MOZ_BUILD_APP', ret)
     set_define('MOZ_BUILD_APP', ret)
     add_old_configure_assignment('MOZ_BUILD_APP', ret)
     return ret
+set_config('MOZ_BUILD_APP', build_project)
 # set RELEASE_BUILD and NIGHTLY_BUILD variables depending on the cycle we're in
 # The logic works like this:
 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
 # - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
 # - otherwise, we're building Release/Beta (define RELEASE_BUILD)
 def milestone(build_env):
     milestone_path = os.path.join(build_env.topsrcdir,
     with open(milestone_path, 'r') as fh:
         milestone = fh.read().splitlines()[-1]
-    set_config('GRE_MILESTONE', milestone)
-    is_nightly = is_release = False
+    is_nightly = is_release = None
     if 'a1' in milestone:
-        set_config('NIGHTLY_BUILD', True)
         set_define('NIGHTLY_BUILD', True)
         add_old_configure_assignment('NIGHTLY_BUILD', True)
         is_nightly = True
     elif 'a' not in milestone:
-        set_config('RELEASE_BUILD', True)
         set_define('RELEASE_BUILD', True)
         add_old_configure_assignment('RELEASE_BUILD', True)
         is_release = True
     return namespace(version=milestone,
+set_config('GRE_MILESTONE', delayed_getattr(milestone, 'version'))
+set_config('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
+set_config('RELEASE_BUILD', delayed_getattr(milestone, 'is_release'))
 # This is temporary until js/src/configure and configure are merged.
 # Use instead of option() in js/moz.configure
 def js_option(*args, **kwargs):
     opt = option(*args, **kwargs)
     @depends(opt.option, build_project)
     def js_option(value, build_project):
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -51,19 +51,20 @@ def autoconf(mozconfig, autoconf):
     if not autoconf:
         error('Could not find autoconf 2.13')
     if not os.path.exists(autoconf):
         error('Could not find autoconf 2.13 at %s' % (autoconf,))
-    set_config('AUTOCONF', autoconf)
     return autoconf
+set_config('AUTOCONF', autoconf)
 # See comment in mozconfig_options() from build/moz.configure/init.configure
 def check_mozconfig_variables():
     # This escapes the sandbox. Don't copy this. This is only here because it
     # is a one off until old-configure is gone.
     all_options = depends.__self__._options.itervalues()
@@ -366,17 +367,16 @@ def old_configure_options(*options):
 def old_configure(prepare_configure, extra_old_configure_args, all_options,
     import os
     import subprocess
     import sys
-    import types
     from mozbuild.shellutil import quote
     cmd = prepare_configure
     # old-configure only supports the options listed in @old_configure_options
     # so we don't need to pass it every single option we've been passed. Only
     # the ones that are not supported by python configure need to.
     cmd += [
@@ -412,17 +412,33 @@ def old_configure(prepare_configure, ext
     for flag in raw_config['flags']:
         if flag not in all_options:
             error('Missing option in `@old_configure_options` in %s: %s'
                   % (__file__, flag))
     # If the code execution above fails, we want to keep the file around for
     # debugging.
+    return raw_config
-    config = {}
+# set_config is only available in the global namespace, not directly in
+# @depends functions, but we do need to enumerate the result of
+# old_configure, so we cheat.
+def set_old_configure_config(name, value):
+    set_config(name, value)
+def post_old_configure(raw_config):
+    import types
     for k, v in raw_config['substs']:
-        set_config(k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
+        set_old_configure_config(
+            k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
     for k, v in dict(raw_config['defines']).iteritems():
         set_define(k[1:-1], v[1:-1])
-    set_config('non_global_defines', raw_config['non_global_defines'])
+    set_old_configure_config('non_global_defines',
+                             raw_config['non_global_defines'])
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -101,8 +101,26 @@ def deprecated_option(*args, **kwargs):
 # from mozbuild.util import ReadOnlyNamespace as namespace
 def namespace(**kwargs):
     from mozbuild.util import ReadOnlyNamespace
     return ReadOnlyNamespace(**kwargs)
+# Some @depends function return namespaces, and one could want to use one
+# specific attribute from such a namespace as a "value" given to functions
+# such as `set_config`. But those functions do not take immediate values.
+# The `delayed_getattr` function allows access to attributes from the result
+# of a @depends function in a non-immediate manner.
+#   @depends('--option')
+#   def option(value)
+#       return namespace(foo=value)
+#   set_config('FOO', delayed_getattr(option, 'foo')
+def delayed_getattr(func, key):
+    @depends(func)
+    @advanced
+    def result(value):
+        return getattr(value, key)
+    return result
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -16,62 +16,71 @@ def building_js(build_project, help):
 # top-level configure, it can go away, although the JS_STANDALONE config
 # will still need to be set depending on building_js above.
 option(env='JS_STANDALONE', default=building_js,
        help='Reserved for internal use')
 def js_standalone(value):
     if value:
-        set_config('JS_STANDALONE', True)
         add_old_configure_assignment('JS_STANDALONE', True)
+        return True
+set_config('JS_STANDALONE', js_standalone)
 js_option('--disable-js-shell', default=building_js,
        help='Do not build the JS shell')
-def js_shell(value):
+def js_disable_shell(value):
     if not value:
-        set_config('JS_DISABLE_SHELL', True)
+        return True
+set_config('JS_DISABLE_SHELL', js_disable_shell)
 # Use SpiderMonkey Promise implementation if it's enabled
 # =======================================================
 js_option('--enable-sm-promise', help='Enable SpiderMonkey promises')
 def sm_promise(value):
     if value:
-        set_config('SPIDERMONKEY_PROMISE', True)
         set_define('SPIDERMONKEY_PROMISE', True)
+        return True
+set_config('SPIDERMONKEY_PROMISE', sm_promise)
 # SpiderMonkey as a shared library, and how its symbols are exported
 # ==================================================================
 js_option('--disable-shared-js', default=building_js,
           help='Do not create a shared library')
 js_option('--disable-export-js', default=building_js,
           help='Do not mark JS symbols as DLL exported/visible')
 @depends('--disable-shared-js', '--disable-export-js')
 def static_js(shared_js, export_js):
     if shared_js:
         if not export_js:
             error('Must export JS symbols when building a shared library.')
-        set_config('JS_SHARED_LIBRARY', True)
         add_old_configure_assignment('JS_SHARED_LIBRARY', True)
         if export_js:
             set_define('STATIC_EXPORTABLE_JS_API', True)
             set_define('STATIC_JS_API', True)
         set_define('MOZ_STATIC_JS', True)
+def shared_js(value):
+    if value:
+        return True
+set_config('JS_SHARED_LIBRARY', shared_js)
 @deprecated_option(env='DISABLE_SHARED_JS', nargs='?')
 def disable_shared_js(value):
     # DISABLE_SHARED_JS=1 gets us an empty PositiveOptionValue
     if value and not len(value):
         suggestion = '--disable-shared-js'
         suggestion = '--enable-shared-js'
@@ -97,21 +106,22 @@ js_option('--enable-instruments', env='M
           help='Enable instruments remote profiling')
 @depends('--enable-instruments', target)
 def instruments(value, target):
     if value and target.os != 'OSX':
         error('--enable-instruments cannot be used when targeting %s'
               % target.os)
     if value:
-        set_config('MOZ_INSTRUMENTS', True)
         set_define('MOZ_INSTRUMENTS', True)
         add_old_configure_assignment('MOZ_INSTRUMENTS', True)
         imply_option('--enable-profiling', reason='--enable-instruments')
+        return True
+set_config('MOZ_INSTRUMENTS', instruments)
 js_option('--enable-callgrind', env='MOZ_CALLGRIND',
           help='Enable callgrind profiling')
 def callgrind(value):
     if value:
         set_define('MOZ_CALLGRIND', True)
@@ -120,24 +130,28 @@ def callgrind(value):
 js_option('--enable-profiling', env='MOZ_PROFILING',
           help='Set compile flags necessary for using sampling profilers '
                '(e.g. shark, perf)')
 @depends('--enable-profiling', target)
 def profiling(value, target):
     if value:
-        set_config('MOZ_PROFILING', True)
         set_define('MOZ_PROFILING', True)
         add_old_configure_assignment('MOZ_PROFILING', True)
         if target.kernel == 'WINNT' or (target.kernel == 'Linux' and
                                         target.os == 'GNU'):
             imply_option('--enable-vtune', reason='--enable-profiling')
+        return True
+set_config('MOZ_PROFILING', profiling)
 js_option('--enable-vtune', env='MOZ_VTUNE', help='Enable vtune profiling')
 def vtune(value):
     if value:
-        set_config('MOZ_VTUNE', True)
         set_define('MOZ_VTUNE', True)
+        return True
+set_config('MOZ_VTUNE', vtune)
--- a/mobile/android/gradle.configure
+++ b/mobile/android/gradle.configure
@@ -7,43 +7,52 @@
 # If --with-gradle is specified, build mobile/android with Gradle.  If no
 # Gradle binary is specified, or if --without-gradle is specified, use the in
 # tree Gradle wrapper.  The wrapper downloads and installs Gradle, which is
 # good for local developers but not good in automation.
 option('--with-gradle', nargs='?',
        help='Enable building mobile/android with Gradle '
             '(argument: location of binary or wrapper (gradle/gradlew))')
+def with_gradle(value):
+    if value:
+        return True
+set_config('MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE', with_gradle)
 @depends('--with-gradle', check_build_environment)
 def gradle(value, build_env):
-    if value:
-        set_config('MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE', True)
     gradle = value[0] if len(value) else \
         os.path.join(build_env.topsrcdir, 'gradlew')
     # TODO: verify that $GRADLE is executable.
     if not os.path.isfile(gradle):
         error('GRADLE must be executable: %s' % gradle)
-    set_config('GRADLE', gradle)
+    return gradle
-    return gradle
+set_config('GRADLE', gradle)
 # Automation uses this to change log levels, not use the daemon, and use
 # offline mode.
 option(env='GRADLE_FLAGS', default='', help='Flags to pass to Gradle.')
 def gradle_flags(value):
-    set_config('GRADLE_FLAGS', value[0] if value else '')
+    return value[0] if value else ''
+set_config('GRADLE_FLAGS', gradle_flags)
 # Automation will set this to file:///path/to/local via the mozconfig.
 # Local developer default is jcenter.
 option(env='GRADLE_MAVEN_REPOSITORY', default='https://jcenter.bintray.com/',
        help='Path to Maven repository containing Gradle dependencies.')
 def gradle_maven_repository(value):
     if value:
-        set_config('GRADLE_MAVEN_REPOSITORY', value[0])
+        return value[0]
+set_config('GRADLE_MAVEN_REPOSITORY', gradle_maven_repository)
--- a/moz.configure
+++ b/moz.configure
@@ -15,40 +15,44 @@ include('build/moz.configure/checks.conf
 # - Spidermonkey-specific options and rules should go in js/moz.configure.
 # - etc.
 # Multiprocess Firefox Testing UI - Nightly and Aurora
 # To be removed in Bug 1003313
 def e10s_testing_only(milestone):
     if not milestone.is_release:
-        set_config('E10S_TESTING_ONLY', True)
         set_define('E10S_TESTING_ONLY', True)
+        return True
+set_config('E10S_TESTING_ONLY', e10s_testing_only)
 option('--enable-artifact-builds', env='MOZ_ARTIFACT_BUILDS',
        help='Download and use prebuilt binary artifacts.')
 def artifact_builds(value):
     if value:
-        set_config('MOZ_ARTIFACT_BUILDS', True)
-    return bool(value)
+        return True
+set_config('MOZ_ARTIFACT_BUILDS', artifact_builds)
        help='Disable compiler/library checks')
 def compile_environment(value):
     if value:
-        set_config('COMPILE_ENVIRONMENT', True)
         add_old_configure_assignment('COMPILE_ENVIRONMENT', True)
-    return bool(value)
+        return True
+set_config('COMPILE_ENVIRONMENT', compile_environment)
 def build_backends_choices(help):
     from mozbuild.backend import backends
     return tuple(backends)
@@ -58,17 +62,19 @@ option('--enable-build-backend', nargs='
 @depends('--enable-build-backend', '--enable-artifact-builds')
 def build_backend(backends, artifact_builds):
     if artifact_builds:
         all_backends = ['FasterMake+RecursiveMake']
         all_backends = ['RecursiveMake', 'FasterMake']
-    set_config('BUILD_BACKENDS', unique_list(all_backends))
+    return unique_list(all_backends)
+set_config('BUILD_BACKENDS', build_backend)
 # Awk detection
 # ==============================================================
 awk = check_prog('AWK', ('gawk', 'mawk', 'nawk', 'awk'))
 # Until the AWK variable is not necessary in old-configure
@@ -155,22 +161,30 @@ def yasm_asflags(yasm, target):
             # We're assuming every x86 platform we support that's
             # not Windows or Mac is ELF.
             if target.cpu == 'x86':
                 asflags = '-f elf32'
             elif target.cpu == 'x86_64':
                 asflags = '-f elf64'
         if asflags:
             asflags += ' -rnasm -pnasm'
-            set_config('YASM_ASFLAGS', asflags)
-            set_config('HAVE_YASM', True)
             # Until the YASM variable is not necessary in old-configure.
             add_old_configure_assignment('YASM', True)
         return asflags
+set_config('YASM_ASFLAGS', yasm_asflags)
+def have_yasm(value):
+    if value:
+        return True
+set_config('HAVE_YASM', have_yasm)
 # Miscellaneous programs
 # ==============================================================
 check_prog('DOXYGEN', ('doxygen',), allow_missing=True)
 check_prog('TAR', ('gnutar', 'gtar', 'tar'))
 check_prog('UNZIP', ('unzip',))
 check_prog('XARGS', ('xargs',))
 check_prog('ZIP', ('zip',))
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -35,18 +35,18 @@ class DummyFunction(object):
         raise RuntimeError('The `%s` function may not be called'
                            % self.__name__)
 class SandboxedGlobal(dict):
     '''Identifiable dict type for use as function global'''
-class DependsOutput(dict):
-    '''Dict holding the results yielded by a @depends function.'''
+class DependsOutput(object):
+    '''Class for objects holding the options implied by a @depends function.'''
     __slots__ = ('implied_options',)
     def __init__(self):
         super(DependsOutput, self).__init__()
         self.implied_options = []
     def imply_option(self, option, reason=None):
         if not isinstance(option, types.StringTypes):
@@ -217,23 +217,23 @@ class ConfigureSandbox(dict):
         if (not isinstance(value, DummyFunction) and
                 value not in self._templates):
             raise KeyError('Cannot assign `%s` because it is neither a '
                            '@depends nor a @template' % key)
         return super(ConfigureSandbox, self).__setitem__(key, value)
-    def _resolve(self, arg):
+    def _resolve(self, arg, need_help_dependency=True):
         if isinstance(arg, DummyFunction):
             assert arg in self._depends
             func = self._depends[arg]
             assert not inspect.isgeneratorfunction(func)
             assert func in self._results
-            if not func.with_help:
+            if need_help_dependency and not func.with_help:
                 raise ConfigureError("Missing @depends for `%s`: '--help'" %
             result = self._results[func]
             return result
         return arg
     def option_impl(self, *args, **kwargs):
         '''Implementation of option()
@@ -285,22 +285,21 @@ class ConfigureSandbox(dict):
         references. The decorated function is called as soon as the decorator
         is called, and the arguments it receives are the OptionValue or
         function results corresponding to each of the arguments to @depends.
         As an exception, when a HelpFormatter is attached, only functions that
         have '--help' in their @depends argument list are called.
         The decorated function is altered to use a different global namespace
         for its execution. This different global namespace exposes a limited
-        set of functions from os.path, and three additional functions:
-        `imply_option`, `set_config` and `set_define`. The first allows to
-        inject additional options as if they had been passed on the command
-        line. The second declares new configuration items for consumption by
-        moz.build. The last declares new defines, stored in a DEFINES
-        configuration item.
+        set of functions from os.path, and two additional functions:
+        `imply_option` and `set_define`. The former allows to inject
+        additional options as if they had been passed on the command line.
+        The latter declares new defines, stored in a DEFINES configuration
+        item.
         if not args:
             raise ConfigureError('@depends needs at least one argument')
         with_help = False
         resolved_args = []
         for arg in args:
             if isinstance(arg, types.StringTypes):
@@ -330,17 +329,16 @@ class ConfigureSandbox(dict):
         def decorator(func):
             if inspect.isgeneratorfunction(func):
                 raise ConfigureError(
                     'Cannot decorate generator functions with @depends')
             func, glob = self._prepare_function(func)
             result = DependsOutput()
-                set_config=result.__setitem__,
             dummy = wraps(func)(DummyFunction())
             self._depends[dummy] = func
             func.with_help = with_help
             if with_help:
                 for arg in args:
                     if (isinstance(arg, DummyFunction) and
@@ -371,24 +369,16 @@ class ConfigureSandbox(dict):
                             or name))
                     if len(deps) != 1:
                         raise ConfigureError(
                             "Cannot infer what implied '%s'" % option)
                     reason = deps[0]
                 self._implied_options[option] = func, reason
-            if not self._help:
-                for k, v in result.iteritems():
-                    if k in self._config:
-                        raise ConfigureError(
-                            "Cannot add '%s' to configuration: Key already "
-                            "exists" % k)
-                    self._config[k] = v
             return dummy
         return decorator
     def include_impl(self, what):
         '''Implementation of include().
         Allows to include external files for execution in the sandbox.
         It is possible to use a @depends function as argument, in which case
@@ -410,29 +400,53 @@ class ConfigureSandbox(dict):
         Templates allow to simplify repetitive constructs, or to implement
         helper decorators and somesuch.
         template, glob = self._prepare_function(func)
+            set_config=self.set_config_impl,
         return template
     def advanced_impl(self, func):
         '''Implementation of @advanced.
         This function gives the decorated function access to the complete set
         of builtins, allowing the import keyword as an expected side effect.
         func, glob = self._prepare_function(func)
         return func
+    def set_config_impl(self, name, value):
+        '''Implementation of set_config().
+        Set the configuration items with the given name to the given value.
+        Both `name` and `value` can be references to @depends functions,
+        in which case the result from these functions is used. If the result
+        of either function is None, the configuration item is not set.
+        '''
+        # Don't set anything when --help was on the command line
+        if self._help:
+            return
+        name = self._resolve(name, need_help_dependency=False)
+        if name is None:
+            return
+        if not isinstance(name, types.StringTypes):
+            raise TypeError("Unexpected type: '%s'" % type(name))
+        if name in self._config:
+            raise ConfigureError(
+                "Cannot add '%s' to configuration: Key already "
+                "exists" % name)
+        value = self._resolve(value, need_help_dependency=False)
+        if value is not None:
+            self._config[name] = value
     def _set_define(self, name, value):
         defines = self._config.setdefault('DEFINES', {})
         if name in defines:
             raise ConfigureError("'%s' is already defined" % name)
         defines[name] = value
     def _prepare_function(self, func):
         '''Alter the given function global namespace with the common ground
--- a/python/mozbuild/mozbuild/test/configure/data/extra.configure
+++ b/python/mozbuild/mozbuild/test/configure/data/extra.configure
@@ -3,9 +3,11 @@
 # 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/.
 option('--extra', help='Extra')
 def extra(extra):
-    set_config('EXTRA', extra)
+    return extra
+set_config('EXTRA', extra)
--- a/python/mozbuild/mozbuild/test/configure/data/included.configure
+++ b/python/mozbuild/mozbuild/test/configure/data/included.configure
@@ -5,43 +5,54 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 # For more complex and repetitive things, we can create templates
 def check_compiler_flag(flag):
     def check(value):
         if value:
-            set_config('CFLAGS', [flag])
+            return [flag]
+    set_config('CFLAGS', check)
+    return check
-    return check
 # A template that doesn't return functions can be used in @depends functions.
 def fortytwo():
     return 42
 def twentyone():
     yield 21
 def check(value):
     if value:
-        set_config('TEMPLATE_VALUE', fortytwo())
+        return fortytwo()
+set_config('TEMPLATE_VALUE', check)
+def check(value):
+    if value:
         for val in twentyone():
-            set_config('TEMPLATE_VALUE_2', val)
+            return val
+set_config('TEMPLATE_VALUE_2', check)
 # Templates can use @advanced too to import modules and get the full set of
 # builtins.
 def platform():
     import sys
     return sys.platform
 option('--enable-advanced-template', help='Advanced template')
 def check(value):
     if value:
-        set_config('PLATFORM', platform())
+        return platform()
+set_config('PLATFORM', check)
--- a/python/mozbuild/mozbuild/test/configure/data/moz.configure
+++ b/python/mozbuild/mozbuild/test/configure/data/moz.configure
@@ -35,70 +35,80 @@ option('--option', env='MOZ_OPTION', hel
 # It is also possible to pass options through the environment only.
 option(env='CC', nargs=1, help='C Compiler')
 # Call the function when the --enable-simple option is processed, with its
 # OptionValue as argument.
 def simple(simple):
     if simple:
-        set_config('ENABLED_SIMPLE', simple)
+        return simple
+set_config('ENABLED_SIMPLE', simple)
 # There can be multiple functions depending on the same option.
 def simple(simple):
-    set_config('SIMPLE', simple)
+    return simple
+set_config('SIMPLE', simple)
 def with_env(with_env):
-    set_config('WITH_ENV', with_env)
+    return with_env
+set_config('WITH_ENV', with_env)
 # It doesn't matter if the dependency is on --enable or --disable
 def with_env2(values):
-    set_config('VALUES', values)
     return values
+set_config('VALUES', with_env2)
 # It is possible to @depends on environment-only options.
 def is_gcc(cc):
     return cc and 'gcc' in cc[0]
-def is_gcc_check(is_gcc):
-    set_config('IS_GCC', is_gcc)
+set_config('IS_GCC', is_gcc)
 # It is possible to depend on the result from another function.
 def with_env3(values):
-    set_config('VALUES2', values)
     return values
+set_config('VALUES2', with_env3)
 # @depends functions can also return results for use as input to another
 # @depends.
 def with_env4(values):
     return values
 def with_env5(values):
-    set_config('VALUES3', values)
+    return values
+set_config('VALUES3', with_env5)
 # The result from @depends functions can also be used as input to options.
 # The result must be returned, not implied. The function must also depend
 # on --help.
 @depends('--enable-simple', '--help')
 def simple(simple, help):
     return 'simple' if simple else 'not-simple'
 option('--with-returned-default', default=simple, help='Returned default')
 def default(value):
-    set_config('DEFAULTED', value)
+    return value
+set_config('DEFAULTED', default)
 # @depends functions can also declare that some extra options are implied.
 # Those options need to be defined _after_ the function, and they mustn't
 # appear on the command line or the environment with conflicting values.
 def values(values):
     if values:
@@ -108,45 +118,55 @@ def values(values):
 option('--enable-implied', help='Implied')
 option('--with-implied-values', nargs='*', help='Implied values')
 option(env='WITH_IMPLIED_ENV', nargs='*', help='Implied env')
 def implied(value):
-    set_config('IMPLIED', value)
+    return value
+set_config('IMPLIED', implied)
 def implied(values):
-    set_config('IMPLIED_VALUES', values)
+    return values
+set_config('IMPLIED_VALUES', implied)
 def implied(values):
-    set_config('IMPLIED_ENV', values)
+    return values
+set_config('IMPLIED_ENV', implied)
 @depends('--enable-values', '--help')
 def choices(values, help):
     if len(values):
         return {
             'alpha': ('a', 'b', 'c'),
             'numeric': ('0', '1', '2'),
 option('--returned-choices', choices=choices, help='Choices')
 def returned_choices(values):
-    set_config('CHOICES', values)
+    return values
+set_config('CHOICES', returned_choices)
 # All options must be referenced by some @depends function.
 # It is possible to depend on multiple options/functions
 @depends('--without-thing', '--with-stuff', with_env4, '--option')
 def remainder(*args):
-    set_config('REMAINDER', args)
+    return args
+set_config('REMAINDER', remainder)
 # It is possible to include other files to extend the configuration script.
 # It is also possible for the include file path to come from the result of a
 # @depends function. That function needs to depend on '--help' like for option
 # defaults and choices.
 option('--enable-include', nargs=1, help='Include')
@@ -160,37 +180,47 @@ include(include_path)
 # the standard builtins instead of restricted ones. The order of the decorators
 # matter: @advanced needs to appear last.
 option('--with-advanced', nargs='?', help='Advanced')
 def with_advanced(value):
     if value:
         from mozbuild.configure.options import OptionValue
-        set_config('ADVANCED', isinstance(value, OptionValue))
+        return isinstance(value, OptionValue)
+set_config('ADVANCED', with_advanced)
 # Trying to import without @advanced will fail at runtime.
 def with_advanced(value):
     if len(value) and value[0] == 'break':
         from mozbuild.configure.options import OptionValue
-        set_config('ADVANCED', isinstance(value, OptionValue))
+        return isinstance(value, OptionValue)
+set_config('ADVANCED2', with_advanced)
 # A limited set of functions from os.path are exposed to non @advanced
 # functions.
 def with_advanced(value):
     if len(value):
-        set_config('IS_FILE', os.path.isfile(value[0]))
+        return os.path.isfile(value[0])
+set_config('IS_FILE', with_advanced)
 # An @advanced function can still import the full set.
 def with_advanced(value):
     if len(value):
         import os.path
-        set_config('HAS_GETATIME', hasattr(os.path, 'getatime'))
+        return hasattr(os.path, 'getatime')
+set_config('HAS_GETATIME', with_advanced)
 def with_advanced(value):
     if len(value):
-        set_config('HAS_GETATIME2', hasattr(os.path, 'getatime'))
+        return hasattr(os.path, 'getatime')
+set_config('HAS_GETATIME2', with_advanced)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
@@ -0,0 +1,43 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+option('--set-foo', help='set foo')
+def foo(value):
+    if value:
+        return True
+set_config('FOO', foo)
+option('--set-bar', help='set bar')
+def bar(value):
+    return bool(value)
+set_config('BAR', bar)
+option('--set-value', nargs=1, help='set value')
+def set_value(value):
+    if value:
+        return value[0]
+set_config('VALUE', set_value)
+option('--set-name', nargs=1, help='set name')
+def set_name(value):
+    if value:
+        return value[0]
+set_config(set_name, True)
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -10,36 +10,40 @@ import unittest
 from mozunit import main
 from mozbuild.configure.options import (
-from mozbuild.configure import ConfigureSandbox
+from mozbuild.configure import (
+    ConfigureError,
+    ConfigureSandbox,
 import mozpack.path as mozpath
 test_data_path = mozpath.abspath(mozpath.dirname(__file__))
 test_data_path = mozpath.join(test_data_path, 'data')
 class TestConfigure(unittest.TestCase):
-    def get_result(self, args=[], environ={}, prog='/bin/configure'):
+    def get_result(self, args=[], environ={}, configure='moz.configure',
+                   prog='/bin/configure'):
         config = {}
         out = StringIO()
         sandbox = ConfigureSandbox(config, environ, [prog] + args, out, out)
-        sandbox.run(mozpath.join(test_data_path, 'moz.configure'))
+        sandbox.run(mozpath.join(test_data_path, configure))
         return config, out.getvalue()
-    def get_config(self, options=[], env={}):
-        config, out = self.get_result(options, environ=env)
+    def get_config(self, options=[], env={}, **kwargs):
+        config, out = self.get_result(options, environ=env, **kwargs)
         self.assertEquals('', out)
         return config
     def test_defaults(self):
         config = self.get_config()
         self.maxDiff = None
             'CHOICES': NegativeOptionValue(),
@@ -297,11 +301,44 @@ class TestConfigure(unittest.TestCase):
         self.assertIn('TEMPLATE_VALUE_2', config)
         self.assertEquals(config['TEMPLATE_VALUE_2'], 21)
     def test_template_advanced(self):
         config = self.get_config(['--enable-advanced-template'])
         self.assertIn('PLATFORM', config)
         self.assertEquals(config['PLATFORM'], sys.platform)
+    def test_set_config(self):
+        def get_config(*args):
+            return self.get_config(*args, configure='set_config.configure')
+        config, out = self.get_result(['--help'],
+                                      configure='set_config.configure')
+        self.assertEquals(config, {})
+        config = get_config(['--set-foo'])
+        self.assertIn('FOO', config)
+        self.assertEquals(config['FOO'], True)
+        config = get_config(['--set-bar'])
+        self.assertNotIn('FOO', config)
+        self.assertIn('BAR', config)
+        self.assertEquals(config['BAR'], True)
+        config = get_config(['--set-value=qux'])
+        self.assertIn('VALUE', config)
+        self.assertEquals(config['VALUE'], 'qux')
+        config = get_config(['--set-name=hoge'])
+        self.assertIn('hoge', config)
+        self.assertEquals(config['hoge'], True)
+        config = get_config([])
+        self.assertEquals(config, {'BAR': False})
+        with self.assertRaises(ConfigureError):
+            # Both --set-foo and --set-name=FOO are going to try to
+            # set_config('FOO'...)
+            get_config(['--set-foo', '--set-name=FOO'])
 if __name__ == '__main__':
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -22,48 +22,52 @@ def systrace(value, target):
 option('--enable-jprof', env='MOZ_JPROF',
        help='Enable jprof profiling tool (needs mozilla/tools/jprof)')
 def jprof(value):
     if value:
-        set_config('MOZ_JPROF', True)
         set_define('MOZ_JPROF', True)
+        return True
+set_config('MOZ_JPROF', jprof)
 def sps_profiler(target):
     if target.os == 'Android':
         return target.cpu in ('arm', 'x86')
     elif target.kernel == 'Linux':
         return target.cpu in ('x86', 'x86_64')
     return target.os in ('OSX', 'WINNT')
 def sps_profiler_define(value):
     if value:
-        set_config('MOZ_ENABLE_PROFILER_SPS', True)
         set_define('MOZ_ENABLE_PROFILER_SPS', True)
+        return True
+set_config('MOZ_ENABLE_PROFILER_SPS', sps_profiler_define)
 option('--enable-dmd', env='MOZ_DMD',
        help='Enable Dark Matter Detector (heap profiler). '
             'Also enables jemalloc, replace-malloc and profiling')
 def dmd(value):
     if value:
-        set_config('MOZ_DMD', True)
         set_define('MOZ_DMD', True)
         add_old_configure_assignment('MOZ_DMD', True)
+        return True
+set_config('MOZ_DMD', dmd)
 # Javascript engine
 # ==============================================================
 # L10N
 # ==============================================================
@@ -71,18 +75,19 @@ option('--with-l10n-base', nargs=1, env=
        help='Path to l10n repositories')
 def l10n_base(value):
     if value:
         path = value[0]
         if not os.path.isdir(path):
             error("Invalid value --with-l10n-base, %s doesn't exist" % path)
+        return os.path.realpath(os.path.abspath(path))
-        set_config('L10NBASEDIR', os.path.realpath(os.path.abspath(path)))
+set_config('L10NBASEDIR', l10n_base)
 # Default toolkit
 # ==============================================================
 # Normally, we'd want to use the `default` field on the option, but that
 # requires --target to be resolved at --help time, which requires to run
 # config.guess, which we want to avoid. Even better, we could actually set
 # `choices` depending on the target, but that doesn't pan out for the same
@@ -126,91 +131,107 @@ def toolkit(value, target, gonkdir):
 def toolkit(toolkit):
     if toolkit == 'cairo-gtk2-x11':
         widget_toolkit = 'gtk2'
         widget_toolkit = toolkit.replace('cairo-', '')
-    set_config('MOZ_WIDGET_TOOLKIT', widget_toolkit)
     add_old_configure_assignment('MOZ_WIDGET_TOOLKIT', widget_toolkit)
     if widget_toolkit == 'gtk2':
         set_define('MOZ_WIDGET_GTK', '2')
     elif widget_toolkit == 'gtk3':
         set_define('MOZ_WIDGET_GTK', '3')
     elif widget_toolkit != 'windows':
         set_define('MOZ_WIDGET_%s' % widget_toolkit.upper(), True)
     return widget_toolkit
+set_config('MOZ_WIDGET_TOOLKIT', toolkit)
 option('--without-x', env='WITHOUT_X', help='Disable X11 support')
 @depends('--without-x', toolkit)
 def x11(value, toolkit):
     if not value and toolkit != 'qt':
         error('--without-x is only valid with --enable-default-toolkit=qt')
     x11_toolkits = ('gtk2', 'gtk3', 'qt')
     if value and value.origin != 'default' and toolkit not in x11_toolkits:
         error('--with-x is only valid with --enable-default-toolkit={%s}'
               % ','.join(x11_toolkits))
     if value and toolkit in x11_toolkits:
-        set_config('MOZ_ENABLE_XREMOTE', True)
         set_define('MOZ_ENABLE_XREMOTE', True)
-        set_config('MOZ_X11', True)
         set_define('MOZ_X11', True)
         add_old_configure_assignment('MOZ_X11', True)
-    return value and toolkit in x11_toolkits
+    return True if value and toolkit in x11_toolkits else None
+set_config('MOZ_ENABLE_XREMOTE', x11)
+set_config('MOZ_X11', x11)
 # GL Provider
 # ==============================================================
 option('--with-gl-provider', nargs=1, help='Set GL provider backend type')
-@depends('--with-gl-provider', x11)
-def gl_provider(value, x11):
+def gl_provider(value):
     if value:
         provider = value[0]
-        set_config('MOZ_GL_PROVIDER', provider)
         set_define('MOZ_GL_PROVIDER', 'GLContextProvider%s' % provider)
-        set_config('MOZ_GL_DEFAULT_PROVIDER', provider)
         set_define('GL_PROVIDER_%s' % provider, True)
+        return provider
+@depends(gl_provider, x11)
+def gl_default_provider(value, x11):
+    if value:
+        return value
     elif x11:
-        set_config('MOZ_GL_DEFAULT_PROVIDER', 'GLX')
         set_define('GL_PROVIDER_GLX', True)
+        return 'GLX'
+set_config('MOZ_GL_PROVIDER', gl_provider)
+set_config('MOZ_GL_DEFAULT_PROVIDER', gl_default_provider)
 # PDF printing
 # ==============================================================
 def pdf_printing(toolkit):
     if toolkit in ('windows', 'gtk2', 'gtk3', 'qt', 'android', 'gonk'):
-        set_config('MOZ_PDF_PRINTING', True)
-        set_config('PDF_SURFACE_FEATURE', '#define CAIRO_HAS_PDF_SURFACE 1')
+        return True
+def pdf_surface_feature(pdf_printing):
+    if pdf_printing:
+        return '#define CAIRO_HAS_PDF_SURFACE 1'
         # CONFIGURE_SUBST_FILES need explicit empty values.
-        set_config('PDF_SURFACE_FEATURE', '')
+        return ''
+set_config('MOZ_PDF_PRINTING', pdf_printing)
+set_config('PDF_SURFACE_FEATURE', pdf_surface_feature)
 # Event loop instrumentation
 # ==============================================================
        help='Force-enable event loop instrumentation')
 @depends('MOZ_INSTRUMENT_EVENT_LOOP', toolkit)
 def instrument_event_loop(value, toolkit):
     if value or (toolkit in ('windows', 'gtk2', 'gtk3', 'cocoa', 'android',
                              'gonk') and value.origin == 'default'):
-        set_config('MOZ_INSTRUMENT_EVENT_LOOP', True)
         set_define('MOZ_INSTRUMENT_EVENT_LOOP', True)
+        return True
+set_config('MOZ_INSTRUMENT_EVENT_LOOP', instrument_event_loop)
 # Fontconfig Freetype
 # ==============================================================
        help='Force-enable the use of fontconfig freetype')
 @depends('USE_FC_FREETYPE', toolkit)
@@ -220,22 +241,21 @@ def fc_freetype(value, toolkit):
         add_old_configure_assignment('USE_FC_FREETYPE', True)
 # Apple platform decoder support
 # ==============================================================
 def applemedia(toolkit):
     if toolkit in ('cocoa', 'uikit'):
-        set_config('MOZ_APPLEMEDIA', True)
         set_define('MOZ_APPLEMEDIA', True)
         add_old_configure_assignment('MOZ_APPLEMEDIA', True)
         return True
-    return False
+set_config('MOZ_APPLEMEDIA', applemedia)
 # Windows Media Foundation support
 # ==============================================================
        help='Disable support for Windows Media Foundation')
 @depends('--disable-wmf', target)
 def wmf(value, target):
@@ -244,70 +264,75 @@ def wmf(value, target):
         # Enable Windows Media Foundation support by default.
         # Note our minimum SDK version is Windows 7 SDK, so we are (currently)
         # guaranteed to have a recent-enough SDK to build WMF.
         enabled = target.os == 'WINNT'
     if enabled and target.os != 'WINNT':
         error('Cannot enable Windows Media Foundation support on %s'
               % target.os)
     if enabled:
-        set_config('MOZ_WMF', True)
         set_define('MOZ_WMF', True)
-    return enabled
+        return True
+set_config('MOZ_WMF', wmf)
 # FFmpeg H264/AAC Decoding Support
 # ==============================================================
        help='Disable FFmpeg for fragmented H264/AAC decoding')
 @depends('--disable-ffmpeg', target)
 def ffmpeg(value, target):
     enabled = bool(value)
     if value.origin == 'default':
         enabled = target.os not in ('Android', 'WINNT')
     if enabled:
         set_define('MOZ_FFMPEG', True)
-        set_config('MOZ_FFMPEG', True)
         imply_option('--enable-fmp4', '--enable-ffmpeg')
-    return enabled
+        return True
+set_config('MOZ_FFMPEG', ffmpeg)
 # Built-in fragmented MP4 support.
 # ==============================================================
 option('--disable-fmp4', env='MOZ_FMP4',
        help='Disable support for in built Fragmented MP4 parsing')
 @depends('--disable-fmp4', target, wmf, applemedia)
 def fmp4(value, target, wmf, applemedia):
     enabled = bool(value)
     if value.origin == 'default':
         # target.os == 'Android' includes all B2G versions
         enabled = wmf or applemedia or target.os == 'Android'
     if enabled:
-        set_config('MOZ_FMP4', True)
         set_define('MOZ_FMP4', True)
         add_old_configure_assignment('MOZ_FMP4', True)
-    return enabled
+        return True
+set_config('MOZ_FMP4', fmp4)
 # EME Support
 # ==============================================================
 option('--enable-eme', nargs='*', choices=('adobe',),
        help='Enable support for Encrypted Media Extensions')
 @depends('--enable-eme', fmp4)
 def eme(value, fmp4):
     enabled = bool(value)
     if value.origin == 'default':
         enabled = enabled or fmp4
     if enabled and not fmp4:
         error('Encrypted Media Extension support requires '
               'Fragmented MP4 support')
     if enabled:
-        set_config('MOZ_EME', True)
         set_define('MOZ_EME', True)
+        return True
+def eme_modules(value):
     # Theoretically, we could pass `value` directly when it is a
     # PositiveOptionValue, but somehow, the JSON serialization in configure.py
     # outputs inconsistent data in some cases when we do (a closing bracket
     # without an opening one).
-    set_config('MOZ_EME_MODULES', list(value) if value else [])
-    return enabled
+    return list(value) if value else []
+set_config('MOZ_EME', eme)
+set_config('MOZ_EME_MODULES', eme_modules)