Bug 1253203 - Move parts of configure.py into sandboxed moz.configure draft
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 04 Mar 2016 05:39:59 +0900
changeset 336855 e563b28c8fac6fc72b8e3915a2d36daba383d2a1
parent 336854 b5ca43488b99bca70c200a658496d02f23931501
child 515510 92b14bc212421d1b01e916318f00bf51ad42149d
push id12192
push userbmo:mh+mozilla@glandium.org
push dateFri, 04 Mar 2016 08:13:32 +0000
bugs1253203
milestone47.0a1
Bug 1253203 - Move parts of configure.py into sandboxed moz.configure This moves all the reading mozconfig, finding autoconf, refreshing the old configure, and running the old configure into sandboxed moz.configure. This effectively bootstraps the sandboxed python configure.
build/autoconf/altoptions.m4
build/autoconf/config.status.m4
build/moz.configure/init.configure
build/moz.configure/old.configure
build/moz.configure/util.configure
configure.py
js/src/old-configure.in
moz.configure
old-configure.in
python/mozbuild/mozbuild/configure/options.py
python/mozbuild/mozbuild/mach_commands.py
--- a/build/autoconf/altoptions.m4
+++ b/build/autoconf/altoptions.m4
@@ -15,109 +15,81 @@ dnl MOZ_ARG_DISABLE_BOOL(          NAME,
 dnl MOZ_ARG_ENABLE_STRING(         NAME, HELP, IF-SET [, ELSE])
 dnl MOZ_ARG_ENABLE_BOOL_OR_STRING( NAME, HELP, IF-YES, IF-NO, IF-SET[, ELSE]]])
 dnl MOZ_ARG_WITH_BOOL(             NAME, HELP, IF-YES [, IF-NO [, ELSE])
 dnl MOZ_ARG_WITHOUT_BOOL(          NAME, HELP, IF-NO [, IF-YES [, ELSE])
 dnl MOZ_ARG_WITH_STRING(           NAME, HELP, IF-SET [, ELSE])
 dnl MOZ_ARG_HEADER(Comment)
 dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file
 
+define([MOZ_DIVERSION_ARGS], 12)
+
+AC_DEFUN([MOZ_ARG],[dnl
+AC_DIVERT_PUSH(MOZ_DIVERSION_ARGS)dnl
+    '$1',
+AC_DIVERT_POP()dnl
+])
+AC_DEFUN([MOZ_AC_ARG_ENABLE],[MOZ_ARG([--enable-]translit([$1],[_],[-]))AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
+AC_DEFUN([MOZ_AC_ARG_WITH],[MOZ_ARG([--with-]translit([$1],[_],[-]))AC_ARG_WITH([$1], [$2], [$3], [$4])])
 
 dnl MOZ_TWO_STRING_TEST(NAME, VAL, STR1, IF-STR1, STR2, IF-STR2 [, ELSE])
 AC_DEFUN([MOZ_TWO_STRING_TEST],
 [if test "[$2]" = "[$3]"; then
     ifelse([$4], , :, [$4])
   elif test "[$2]" = "[$5]"; then
     ifelse([$6], , :, [$6])
   else
     ifelse([$7], ,
       [AC_MSG_ERROR([Option, [$1], does not take an argument ([$2]).])],
       [$7])
   fi])
 
 dnl MOZ_ARG_ENABLE_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE]])
 AC_DEFUN([MOZ_ARG_ENABLE_BOOL],
-[AC_ARG_ENABLE([$1], [$2], 
+[MOZ_AC_ARG_ENABLE([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$enableval], yes, [$3], no, [$4])],
  [$5])])
 
 dnl MOZ_ARG_DISABLE_BOOL(NAME, HELP, IF-NO [, IF-YES [, ELSE]])
 AC_DEFUN([MOZ_ARG_DISABLE_BOOL],
-[AC_ARG_ENABLE([$1], [$2],
+[MOZ_AC_ARG_ENABLE([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$enableval], no, [$3], yes, [$4])],
  [$5])])
 
 dnl MOZ_ARG_ENABLE_STRING(NAME, HELP, IF-SET [, ELSE])
 AC_DEFUN([MOZ_ARG_ENABLE_STRING],
-[AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
+[MOZ_AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
 
 dnl MOZ_ARG_ENABLE_BOOL_OR_STRING(NAME, HELP, IF-YES, IF-NO, IF-SET[, ELSE]]])
 AC_DEFUN([MOZ_ARG_ENABLE_BOOL_OR_STRING],
 [ifelse([$5], , 
  [errprint([Option, $1, needs an "IF-SET" argument.
 ])
   m4exit(1)],
- [AC_ARG_ENABLE([$1], [$2],
+ [MOZ_AC_ARG_ENABLE([$1], [$2],
   [MOZ_TWO_STRING_TEST([$1], [$enableval], yes, [$3], no, [$4], [$5])],
   [$6])])])
 
 dnl MOZ_ARG_WITH_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE])
 AC_DEFUN([MOZ_ARG_WITH_BOOL],
-[AC_ARG_WITH([$1], [$2],
+[MOZ_AC_ARG_WITH([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$withval], yes, [$3], no, [$4])],
  [$5])])
 
 dnl MOZ_ARG_WITHOUT_BOOL(NAME, HELP, IF-NO [, IF-YES [, ELSE])
 AC_DEFUN([MOZ_ARG_WITHOUT_BOOL],
-[AC_ARG_WITH([$1], [$2],
+[MOZ_AC_ARG_WITH([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$withval], no, [$3], yes, [$4])],
  [$5])])
 
 dnl MOZ_ARG_WITH_STRING(NAME, HELP, IF-SET [, ELSE])
 AC_DEFUN([MOZ_ARG_WITH_STRING],
-[AC_ARG_WITH([$1], [$2], [$3], [$4])])
+[MOZ_AC_ARG_WITH([$1], [$2], [$3], [$4])])
 
 dnl MOZ_ARG_HEADER(Comment)
 dnl This is used by webconfig to group options
 define(MOZ_ARG_HEADER, [# $1])
 
 dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file
 AC_DEFUN([MOZ_READ_MOZCONFIG],
 [AC_REQUIRE([AC_INIT_BINSH])dnl
-inserted=
-dnl Shell is hard, so here is what the following does:
-dnl - Reset $@ (command line arguments)
-dnl - Add the configure options from mozconfig to $@ one by one
-dnl - Add the original command line arguments after that, one by one
-dnl
-dnl There are several tricks involved:
-dnl - It is not possible to preserve the whitespaces in $@ by assigning to
-dnl   another variable, so the two first steps above need to happen in the first
-dnl   iteration of the third step.
-dnl - We always want the configure options to be added, so the loop must be
-dnl   iterated at least once, so we add a dummy argument first, and discard it.
-dnl - something | while read line ... makes the while run in a subshell, meaning
-dnl   that anything it does is not propagated to the main shell, so we can't do
-dnl   set -- foo there. As a consequence, what the while loop reading mach
-dnl   environment output does is output a set of shell commands for the main shell
-dnl   to eval.
-dnl - Extra care is due when lines from mach environment output contain special
-dnl   shell characters, so we use ' for quoting and ensure no ' end up in between
-dnl   the quoting mark unescaped.
-dnl Some of the above is directly done in mach environment --format=configure.
-failed_eval() {
-  echo "Failed eval'ing the following:"
-  $(dirname [$]0)/[$1]/mach environment --format=configure
-  exit 1
-}
-
-set -- dummy "[$]@"
-for ac_option
-do
-  if test -z "$inserted"; then
-    set --
-    eval "$($(dirname [$]0)/[$1]/mach environment --format=configure)" || failed_eval
-    inserted=1
-  else
-    set -- "[$]@" "$ac_option"
-  fi
-done
+. ./old-configure.vars
 ])
--- a/build/autoconf/config.status.m4
+++ b/build/autoconf/config.status.m4
@@ -154,16 +154,20 @@ EOF
 if test -n "$_NON_GLOBAL_ACDEFINES"; then
   for var in $_NON_GLOBAL_ACDEFINES; do
     echo "    '$var'," >> $CONFIG_STATUS
   done
 fi
 
 cat >> $CONFIG_STATUS <<EOF
 ]
+
+flags = [
+undivert(MOZ_DIVERSION_ARGS)dnl
+]
 EOF
 
 changequote([, ])
 ])
 
 define([m4_fatal],[
 errprint([$1
 ])
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/init.configure
@@ -0,0 +1,120 @@
+# -*- 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/.
+
+include('util.configure')
+
+option(env='DIST', nargs=1, help='DIST directory')
+
+# Do not allow objdir == srcdir builds.
+# ==============================================================
+@depends('--help', 'DIST')
+def check_build_environment(help, dist):
+    topobjdir = os.path.realpath(os.path.abspath('.'))
+    topsrcdir = os.path.realpath(os.path.abspath(
+        os.path.join(os.path.dirname(__file__), '..', '..')))
+
+    set_config('TOPSRCDIR', topsrcdir)
+    set_config('TOPOBJDIR', topobjdir)
+    set_config('MOZ_BUILD_ROOT', topobjdir)
+    if dist:
+        set_config('DIST', normsep(dist[0]))
+    else:
+        set_config('DIST', os.path.join(topobjdir, 'dist'))
+
+    if help:
+        return
+
+    if topsrcdir == topobjdir:
+        error(
+            '  ***\n'
+            '  * Building directly in the main source directory is not allowed.\n'
+            '  *\n'
+            '  * To build, you must run configure from a separate directory\n'
+            '  * (referred to as an object directory).\n'
+            '  *\n'
+            '  * If you are building with a mozconfig, you will need to change your\n'
+            '  * mozconfig to point to a different object directory.\n'
+            '  ***'
+        )
+
+    # Check for a couple representative files in the source tree
+    conflict_files = [
+        '*         %s' % f for f in ('Makefile', 'config/autoconf.mk')
+        if os.path.exists(os.path.join(topsrcdir, f))
+    ]
+    if conflict_files:
+        error(
+            '  ***\n'
+            '  *   Your source tree contains these files:\n'
+            '  %s\n'
+            '  *   This indicates that you previously built in the source tree.\n'
+            '  *   A source tree build can confuse the separate objdir build.\n'
+            '  *\n'
+            '  *   To clean up the source tree:\n'
+            '  *     1. cd %s\n'
+            '  *     2. gmake distclean\n'
+            '  ***'
+            % ('\n  '.join(conflict_files), topsrcdir)
+        )
+
+
+option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
+option(env='MOZCONFIG', nargs=1, help='Mozconfig location')
+
+# Read user mozconfig
+# ==============================================================
+@depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', check_build_environment, '--help')
+@advanced
+def mozconfig(current_project, mozconfig,  build_env, help):
+    import os
+    from mozbuild.mozconfig import MozconfigLoader
+
+    # Don't read the mozconfig for the js configure (yay backwards
+    # compatibility)
+    if build_env['TOPOBJDIR'].endswith('/js/src'):
+        return {'path': None}
+
+    loader = MozconfigLoader(build_env['TOPSRCDIR'])
+    current_project = current_project[0] if current_project else None
+    mozconfig = mozconfig[0] if mozconfig else None
+    mozconfig = loader.find_mozconfig(env={'MOZCONFIG': mozconfig})
+    mozconfig = loader.read_mozconfig(mozconfig, moz_build_app=current_project)
+
+    return mozconfig
+
+
+@template
+@advanced
+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
+    # to be exposed for other usecases.
+    return depends.__self__._helper
+
+
+@depends(mozconfig)
+def mozconfig_options(mozconfig):
+    if mozconfig['path']:
+        helper = command_line_helper()
+        warn('Adding configure options from %s' % mozconfig['path'])
+        for arg in mozconfig['configure_args']:
+            warn('  %s' % arg)
+            # We could be using imply_option() here, but it has other
+            # contraints that don't really apply to the command-line
+            # emulation that mozconfig provides.
+            helper.add(arg, origin='mozconfig', args=helper._args)
+
+        # Ideally we'd handle mozconfig['env'] and mozconfig['vars'] here,
+        # but at the moment, moz.configure has not knowledge of the options
+        # that may appear there. We'll opt-in when we move things from
+        # old-configure.in, which will be tedious but necessary until we
+        # can discriminate what old-configure.in supports.
+
+del command_line_helper
+
+
+option(env='MOZILLABUILD', nargs=1,
+       help='Path to Mozilla Build (Windows-only)')
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/old.configure
@@ -0,0 +1,397 @@
+# -*- 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/.
+
+# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
+# but the end goal being that the configure script would go away...
+@depends('MOZILLABUILD')
+@advanced
+def shell(mozillabuild):
+    import sys
+
+    shell = 'sh'
+    if mozillabuild:
+        shell = mozillabuild[0] + '/msys/bin/sh'
+    if sys.platform == 'win32':
+        shell = shell + '.exe'
+    return shell
+
+
+option(env='AUTOCONF', nargs=1, help='Path to autoconf 2.13')
+
+@depends(mozconfig, 'AUTOCONF')
+@advanced
+def autoconf(mozconfig, autoconf):
+    import re
+
+    mozconfig_autoconf = None
+    if mozconfig['path']:
+        make_extra = mozconfig['make_extra']
+        if make_extra:
+            for assignment in make_extra:
+                m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
+                             assignment)
+                if m:
+                    mozconfig_autoconf = m.group(1)
+
+    autoconf = autoconf[0] if autoconf else None
+
+    for ac in (mozconfig_autoconf, autoconf, 'autoconf-2.13', 'autoconf2.13',
+               'autoconf213'):
+        if ac:
+            autoconf = find_program(ac)
+            if autoconf:
+                break
+    else:
+        fink = find_program('fink')
+        if find:
+            autoconf = os.path.normpath(os.path.join(
+                fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
+
+    if not autoconf:
+        error('Could not find autoconf 2.13')
+
+    set_config('AUTOCONF', autoconf)
+    return autoconf
+
+
+option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
+
+@depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell)
+@advanced
+def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell):
+    import glob
+    import itertools
+    import os
+    import subprocess
+    import sys
+
+    from mozbuild.shellutil import quote
+
+    if not old_configure:
+        error('The OLD_CONFIGURE environment variable must be set')
+
+    # We need to replace backslashes with forward slashes on Windows because
+    # this path actually ends up literally as $0, which breaks autoconf's
+    # detection of the source directory.
+    old_configure = os.path.abspath(old_configure[0]).replace(os.sep, '/')
+
+    refresh = True
+    if os.path.exists(old_configure):
+        mtime = os.path.getmtime(old_configure)
+        aclocal = os.path.join(build_env['TOPSRCDIR'], 'build', 'autoconf',
+                               '*.m4')
+        for input in itertools.chain(
+            (old_configure + '.in',
+             os.path.join(os.path.dirname(old_configure), 'aclocal.m4')),
+            glob.iglob(aclocal),
+        ):
+            if os.path.getmtime(input) > mtime:
+                break
+        else:
+            refresh = False
+
+    if refresh:
+        warn('Refreshing %s with %s' % (old_configure, autoconf))
+        with open(old_configure, 'wb') as fh:
+            subprocess.check_call([
+                shell, autoconf,
+                '--localdir=%s' % os.path.dirname(old_configure),
+                old_configure + '.in'], stdout=fh)
+
+    cmd = [shell, old_configure] + sys.argv[1:]
+    with open('old-configure.vars', 'w') as out:
+        if mozconfig['path']:
+            if mozconfig['configure_args'] and 'COMM_BUILD' not in os.environ:
+                cmd += mozconfig['configure_args']
+
+            for key, value in mozconfig['env']['added'].items():
+                print("export %s=%s" % (key, quote(value)), file=out)
+            for key, (old, value) in mozconfig['env']['modified'].items():
+                print("export %s=%s" % (key, quote(value)), file=out)
+            for key, value in mozconfig['vars']['added'].items():
+                print("%s=%s" % (key, quote(value)), file=out)
+            for key, (old, value) in mozconfig['vars']['modified'].items():
+                print("%s=%s" % (key, quote(value)), file=out)
+            for t in ('env', 'vars'):
+                for key in mozconfig[t]['removed'].keys():
+                    print("unset %s" % key, file=out)
+
+    return cmd
+
+
+@template
+def old_configure_options(*options):
+    for opt in options:
+        option(opt, nargs='*', help='Help missing for old configure options')
+
+    @depends('--help')
+    def all_options(help):
+        return set(options)
+
+    return depends(prepare_configure, all_options, *options)
+
+
+@old_configure_options(
+    '--cache-file',
+    '--enable-accessibility',
+    '--enable-address-sanitizer',
+    '--enable-alsa',
+    '--enable-android-apz',
+    '--enable-android-omx',
+    '--enable-android-resource-constrained',
+    '--enable-application',
+    '--enable-approximate-location',
+    '--enable-artifact-builds',
+    '--enable-b2g-bt',
+    '--enable-b2g-camera',
+    '--enable-b2g-ril',
+    '--enable-build-backend',
+    '--enable-bundled-fonts',
+    '--enable-callgrind',
+    '--enable-chrome-format',
+    '--enable-clang-plugin',
+    '--enable-compile-environment',
+    '--enable-content-sandbox',
+    '--enable-cookies',
+    '--enable-cpp-rtti',
+    '--enable-crashreporter',
+    '--enable-ctypes',
+    '--enable-dbm',
+    '--enable-dbus',
+    '--enable-debug',
+    '--enable-debug-js-modules',
+    '--enable-debug-symbols',
+    '--enable-default-toolkit',
+    '--enable-directshow',
+    '--enable-dmd',
+    '--enable-dtrace',
+    '--enable-dump-painting',
+    '--enable-elf-hack',
+    '--enable-eme',
+    '--enable-export-js',
+    '--enable-extensions',
+    '--enable-faststripe',
+    '--enable-feeds',
+    '--enable-ffmpeg',
+    '--enable-fmp4',
+    '--enable-gamepad',
+    '--enable-gc-trace',
+    '--enable-gconf',
+    '--enable-gczeal',
+    '--enable-gio',
+    '--enable-gnomeui',
+    '--enable-gold',
+    '--enable-gps-debug',
+    '--enable-hardware-aec-ns',
+    '--enable-icf',
+    '--enable-install-strip',
+    '--enable-instruments',
+    '--enable-ion',
+    '--enable-ios-target',
+    '--enable-ipdl-tests',
+    '--enable-jemalloc',
+    '--enable-jitspew',
+    '--enable-jprof',
+    '--enable-libjpeg-turbo',
+    '--enable-libproxy',
+    '--enable-llvm-hacks',
+    '--enable-logrefcnt',
+    '--enable-macos-target',
+    '--enable-maintenance-service',
+    '--enable-media-navigator',
+    '--enable-memory-sanitizer',
+    '--enable-mobile-optimize',
+    '--enable-more-deterministic',
+    '--enable-mozril-geoloc',
+    '--enable-necko-protocols',
+    '--enable-necko-wifi',
+    '--enable-negotiateauth',
+    '--enable-nfc',
+    '--enable-nspr-build',
+    '--enable-official-branding',
+    '--enable-omx-plugin',
+    '--enable-oom-breakpoint',
+    '--enable-optimize',
+    '--enable-parental-controls',
+    '--enable-perf',
+    '--enable-permissions',
+    '--enable-pie',
+    '--enable-png-arm-neon-support',
+    '--enable-posix-nspr-emulation',
+    '--enable-pref-extensions',
+    '--enable-printing',
+    '--enable-profilelocking',
+    '--enable-profiling',
+    '--enable-pulseaudio',
+    '--enable-raw',
+    '--enable-readline',
+    '--enable-reflow-perf',
+    '--enable-release',
+    '--enable-replace-malloc',
+    '--enable-require-all-d3dc-versions',
+    '--enable-rust',
+    '--enable-safe-browsing',
+    '--enable-sandbox',
+    '--enable-shared-js',
+    '--enable-signmar',
+    '--enable-simulator',
+    '--enable-skia',
+    '--enable-skia-gpu',
+    '--enable-small-chunk-size',
+    '--enable-startup-notification',
+    '--enable-startupcache',
+    '--enable-stdcxx-compat',
+    '--enable-strip',
+    '--enable-synth-pico',
+    '--enable-synth-speechd',
+    '--enable-system-cairo',
+    '--enable-system-extension-dirs',
+    '--enable-system-ffi',
+    '--enable-system-hunspell',
+    '--enable-system-pixman',
+    '--enable-system-sqlite',
+    '--enable-systrace',
+    '--enable-tasktracer',
+    '--enable-tests',
+    '--enable-thread-sanitizer',
+    '--enable-trace-logging',
+    '--enable-tree-freetype',
+    '--enable-ui-locale',
+    '--enable-universalchardet',
+    '--enable-update-channel',
+    '--enable-update-packaging',
+    '--enable-updater',
+    '--enable-url-classifier',
+    '--enable-valgrind',
+    '--enable-verify-mar',
+    '--enable-vtune',
+    '--enable-warnings-as-errors',
+    '--enable-webapp-runtime',
+    '--enable-webrtc',
+    '--enable-websms-backend',
+    '--enable-webspeech',
+    '--enable-webspeechtestbackend',
+    '--enable-wmf',
+    '--enable-xterm-updates',
+    '--enable-xul',
+    '--enable-zipwriter',
+    '--host',
+    '--no-create',
+    '--prefix',
+    '--target',
+    '--with-adjust-sdk-keyfile',
+    '--with-android-cxx-stl',
+    '--with-android-distribution-directory',
+    '--with-android-gnu-compiler-version',
+    '--with-android-max-sdk',
+    '--with-android-min-sdk',
+    '--with-android-ndk',
+    '--with-android-sdk',
+    '--with-android-toolchain',
+    '--with-android-version',
+    '--with-app-basename',
+    '--with-app-name',
+    '--with-arch',
+    '--with-arm-kuser',
+    '--with-bing-api-keyfile',
+    '--with-branding',
+    '--with-ccache',
+    '--with-compiler-wrapper',
+    '--with-crashreporter-enable-percent',
+    '--with-cross-lib',
+    '--with-debug-label',
+    '--with-default-mozilla-five-home',
+    '--with-distribution-id',
+    '--with-doc-include-dirs',
+    '--with-doc-input-dirs',
+    '--with-doc-output-dir',
+    '--with-external-source-dir',
+    '--with-float-abi',
+    '--with-fpu',
+    '--with-gl-provider',
+    '--with-gonk',
+    '--with-gonk-toolchain-prefix',
+    '--with-google-api-keyfile',
+    '--with-google-oauth-api-keyfile',
+    '--with-gradle',
+    '--with-intl-api',
+    '--with-ios-sdk',
+    '--with-java-bin-path',
+    '--with-jitreport-granularity',
+    '--with-l10n-base',
+    '--with-libxul-sdk',
+    '--with-linux-headers',
+    '--with-macbundlename-prefix',
+    '--with-macos-private-frameworks',
+    '--with-macos-sdk',
+    '--with-mozilla-api-keyfile',
+    '--with-nspr-cflags',
+    '--with-nspr-libs',
+    '--with-pthreads',
+    '--with-qemu-exe',
+    '--with-qtdir',
+    '--with-servo',
+    '--with-sixgill',
+    '--with-soft-float',
+    '--with-system-bz2',
+    '--with-system-icu',
+    '--with-system-jpeg',
+    '--with-system-libevent',
+    '--with-system-libvpx',
+    '--with-system-nspr',
+    '--with-system-nss',
+    '--with-system-png',
+    '--with-system-zlib',
+    '--with-thumb',
+    '--with-thumb-interwork',
+    '--with-unify-dist',
+    '--with-user-appdir',
+    '--with-windows-version',
+    '--with-x',
+    '--with-xulrunner-stub-name',
+    '--x-includes',
+    '--x-libraries',
+)
+@advanced
+def old_configure(prepare_configure, all_options, *options):
+    import codecs
+    import os
+    import subprocess
+    import sys
+    import types
+
+    ret = subprocess.call(prepare_configure)
+    if ret:
+        sys.exit(ret)
+
+    raw_config = {}
+    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
+    with codecs.open('config.data', 'r', encoding) as fh:
+	code = compile(fh.read(), 'config.data', 'exec')
+	# Every variation of the exec() function I tried led to:
+	# SyntaxError: unqualified exec is not allowed in function 'main' it
+	# contains a nested function with free variables
+	exec code in raw_config
+
+    # Ensure all the flags known to old-configure appear in the
+    # @old_configure_options above.
+    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.
+    os.remove('config.data')
+
+    config = {}
+    for k, v in raw_config['substs']:
+        set_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'])
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,67 @@
+# -*- 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/.
+
+@template
+@advanced
+def warn(*args):
+    import sys
+    print(*args, file=sys.stderr)
+
+
+@template
+@advanced
+def error(*args):
+    import sys
+    print(*args, file=sys.stderr)
+    sys.exit(1)
+
+
+@template
+@advanced
+def is_absolute_or_relative(path):
+    import os
+    if os.altsep and os.altsep in path:
+        return True
+    return os.sep in path
+
+
+@template
+@advanced
+def normsep(path):
+    import mozpack.path as mozpath
+    return mozpath.normsep(path)
+
+
+@template
+@advanced
+def find_program(file):
+    if is_absolute_or_relative(file):
+        return os.path.abspath(file) if os.path.isfile(file) else None
+    from which import which, WhichError
+    try:
+        return normsep(which(file))
+    except WhichError:
+        return None
+
+
+@depends('--help')
+def _defines(help):
+    ret = {}
+    set_config('DEFINES', ret)
+    return ret
+
+
+@template
+def set_define(name, value):
+    @depends(_defines)
+    @advanced
+    def _add_define(defines):
+        from mozbuild.configure import ConfigureError
+        if name in defines:
+            raise ConfigureError("'%s' is already defined" % name)
+        defines[name] = value
+
+del _defines
--- a/configure.py
+++ b/configure.py
@@ -1,189 +1,74 @@
 # 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 print_function, unicode_literals
 
 import codecs
-import glob
-import itertools
 import json
 import os
 import subprocess
 import sys
-import re
-import types
 
 
 base_dir = os.path.abspath(os.path.dirname(__file__))
 sys.path.append(os.path.join(base_dir, 'python', 'which'))
 sys.path.append(os.path.join(base_dir, 'python', 'mozbuild'))
-from which import which, WhichError
-from mozbuild.mozconfig import MozconfigLoader
-
-
-# If feel dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
-# but the end goal being that the configure script would go away...
-shell = 'sh'
-if 'MOZILLABUILD' in os.environ:
-    shell = os.environ['MOZILLABUILD'] + '/msys/bin/sh'
-if sys.platform == 'win32':
-    shell = shell + '.exe'
-
-
-def is_absolute_or_relative(path):
-    if os.altsep and os.altsep in path:
-        return True
-    return os.sep in path
-
-
-def find_program(file):
-    if is_absolute_or_relative(file):
-        return os.path.abspath(file) if os.path.isfile(file) else None
-    try:
-        return which(file)
-    except WhichError:
-        return None
+from mozbuild.configure import ConfigureSandbox
 
 
-def autoconf_refresh(configure):
-    if os.path.exists(configure):
-        mtime = os.path.getmtime(configure)
-        aclocal = os.path.join(base_dir, 'build', 'autoconf', '*.m4')
-        for input in itertools.chain(
-            (configure + '.in',
-             os.path.join(os.path.dirname(configure), 'aclocal.m4')),
-            glob.iglob(aclocal),
-        ):
-            if os.path.getmtime(input) > mtime:
-                break
-        else:
-            return
+def main(argv):
+    config = {}
+    sandbox = ConfigureSandbox(config, os.environ, argv)
+    sandbox.run(os.path.join(os.path.dirname(__file__), 'moz.configure'))
 
-    mozconfig_autoconf = None
-    configure_dir = os.path.dirname(configure)
-    # Don't read the mozconfig for the js configure (yay backwards
-    # compatibility)
-    if not configure_dir.replace(os.sep, '/').endswith('/js/src'):
-        loader = MozconfigLoader(os.path.dirname(configure))
-        project = os.environ.get('MOZ_CURRENT_PROJECT')
-        mozconfig = loader.find_mozconfig(env=os.environ)
-        mozconfig = loader.read_mozconfig(mozconfig, moz_build_app=project)
-        make_extra = mozconfig['make_extra']
-        if make_extra:
-            for assignment in make_extra:
-                m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
-                             assignment)
-                if m:
-                    mozconfig_autoconf = m.group(1)
-
-    for ac in (mozconfig_autoconf, os.environ.get('AUTOCONF'), 'autoconf-2.13',
-               'autoconf2.13', 'autoconf213'):
-        if ac:
-            autoconf = find_program(ac)
-            if autoconf:
-                break
-    else:
-        fink = find_program('fink')
-        if fink:
-            autoconf = os.path.normpath(os.path.join(
-                fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
-
-    if not autoconf:
-        raise RuntimeError('Could not find autoconf 2.13')
-
-    # Add or adjust AUTOCONF for subprocesses, especially the js/src configure
-    os.environ['AUTOCONF'] = autoconf
-
-    print('Refreshing %s with %s' % (configure, autoconf), file=sys.stderr)
+    if sandbox._help:
+        return 0
 
-    with open(configure, 'wb') as fh:
-        subprocess.check_call([
-            shell, autoconf, '--localdir=%s' % os.path.dirname(configure),
-            configure + '.in'], stdout=fh)
-
-
-def main(args):
-    old_configure = os.environ.get('OLD_CONFIGURE')
-
-    if not old_configure:
-        raise Exception('The OLD_CONFIGURE environment variable must be set')
-
-    # We need to replace backslashes with forward slashes on Windows because
-    # this path actually ends up literally as $0, which breaks autoconf's
-    # detection of the source directory.
-    old_configure = os.path.abspath(old_configure).replace(os.sep, '/')
-
-    try:
-        autoconf_refresh(old_configure)
-    except RuntimeError as e:
-        print(e.message, file=sys.stderr)
-        return 1
-
-    ret = subprocess.call([shell, old_configure] + args)
-    # We don't want to create and run config.status if --help was one of the
-    # command line arguments.
-    if ret or '--help' in args:
-        return ret
-
-    raw_config = {}
-    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
-    with codecs.open('config.data', 'r', encoding) as fh:
-        code = compile(fh.read(), 'config.data', 'exec')
-        # Every variation of the exec() function I tried led to:
-        # SyntaxError: unqualified exec is not allowed in function 'main' it
-        # contains a nested function with free variables
-        exec code in raw_config
-    # If the code execution above fails, we want to keep the file around for
-    # debugging.
-    os.remove('config.data')
-
-    # Sanitize config data
-    config = {}
-    substs = config['substs'] = {
-        k[1:-1]: v[1:-1] if isinstance(v, types.StringTypes) else v
-        for k, v in raw_config['substs']
+    # Sanitize config data to feed config.status
+    sanitized_config = {}
+    sanitized_config['substs'] = {
+        k: v for k, v in config.iteritems()
+        if k not in ('DEFINES', 'non_global_defines', 'TOPSRCDIR', 'TOPOBJDIR')
     }
-    config['defines'] = {
-        k[1:-1]: v[1:-1]
-        for k, v in raw_config['defines']
-    }
-    config['non_global_defines'] = raw_config['non_global_defines']
-    config['topsrcdir'] = base_dir
-    config['topobjdir'] = os.path.abspath(os.getcwd())
+    sanitized_config['defines'] = config['DEFINES']
+    sanitized_config['non_global_defines'] = config['non_global_defines']
+    sanitized_config['topsrcdir'] = config['TOPSRCDIR']
+    sanitized_config['topobjdir'] = config['TOPOBJDIR']
 
     # Create config.status. Eventually, we'll want to just do the work it does
     # here, when we're able to skip configure tests/use cached results/not rely
     # on autoconf.
     print("Creating config.status", file=sys.stderr)
+    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
     with codecs.open('config.status', 'w', encoding) as fh:
-        fh.write('#!%s\n' % config['substs']['PYTHON'])
+        fh.write('#!%s\n' % config['PYTHON'])
         fh.write('# coding=%s\n' % encoding)
-        for k, v in config.iteritems():
+        for k, v in sanitized_config.iteritems():
             fh.write('%s = ' % k)
             json.dump(v, fh, sort_keys=True, indent=4, ensure_ascii=False)
             fh.write('\n')
         fh.write("__all__ = ['topobjdir', 'topsrcdir', 'defines', "
                  "'non_global_defines', 'substs']")
 
-        if not substs.get('BUILDING_JS') or substs.get('JS_STANDALONE'):
+        if not config.get('BUILDING_JS') or config.get('JS_STANDALONE'):
             fh.write('''
 if __name__ == '__main__':
     args = dict([(name, globals()[name]) for name in __all__])
     from mozbuild.config_status import config_status
     config_status(**args)
 ''')
 
     # Other things than us are going to run this file, so we need to give it
     # executable permissions.
     os.chmod('config.status', 0755)
-    if not substs.get('BUILDING_JS') or substs.get('JS_STANDALONE'):
-        if not substs.get('JS_STANDALONE'):
+    if not config.get('BUILDING_JS') or config.get('JS_STANDALONE'):
+        if not config.get('JS_STANDALONE'):
             os.environ['WRITE_MOZINFO'] = '1'
         # Until we have access to the virtualenv from this script, execute
         # config.status externally, with the virtualenv python.
-        return subprocess.call([config['substs']['PYTHON'], 'config.status'])
+        return subprocess.call([config['PYTHON'], 'config.status'])
     return 0
 
 if __name__ == '__main__':
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv))
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -71,52 +71,16 @@ dnl ====================================
 USE_PTHREADS=
 _PTHREAD_LDFLAGS=""
 
 dnl Do not allow objdir == srcdir builds
 dnl ==============================================================
 _topsrcdir=`cd $srcdir; pwd -W 2>/dev/null || pwd -P`
 _objdir=`pwd -P`
 
-if test "$_topsrcdir" = "$_objdir"
-then
-  echo "  ***"
-  echo "  * Building directly in the main source directory is not allowed."
-  echo "  *"
-  echo "  * To build, you must run configure from a separate directory"
-  echo "  * (referred to as an object directory)."
-  echo "  ***"
-  exit 1
-fi
-
-# Check for a couple representative files in the source tree
-_conflict_files=
-for file in $_topsrcdir/Makefile $_topsrcdir/config/autoconf.mk; do
-  if test -f $file; then
-    _conflict_files="$_conflict_files $file"
-  fi
-done
-if test "$_conflict_files"; then
-  echo "***"
-  echo "*   Your source tree contains these files:"
-  for file in $_conflict_files; do
-    echo "*         $file"
-  done
-  cat 1>&2 <<-EOF
-	*   This indicates that you previously built in the source tree.
-	*   A source tree build can confuse the separate objdir build.
-	*
-	*   To clean up the source tree:
-	*     1. cd $_topsrcdir
-	*     2. gmake distclean
-	***
-	EOF
-  exit 1
-  break
-fi
 MOZ_BUILD_ROOT=`pwd -W 2>/dev/null || pwd -P`
 
 MOZ_BUILD_BACKEND(../..)
 
 MOZ_DEFAULT_COMPILER
 
 COMPILE_ENVIRONMENT=1
 MOZ_ARG_DISABLE_BOOL(compile-environment,
@@ -3430,18 +3394,16 @@ AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(INCREMENTAL_LINKER)
 AC_SUBST(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
 
 AC_SUBST(MOZ_FIX_LINK_PATHS)
 
 AC_SUBST(USE_DEPENDENT_LIBS)
 
-AC_SUBST(MOZ_BUILD_ROOT)
-
 AC_SUBST(MOZ_POST_PROGRAM_COMMAND)
 
 AC_SUBST(MOZ_APP_NAME)
 AC_SUBST(MOZ_APP_DISPLAYNAME)
 AC_SUBST(MOZ_APP_VERSION)
 
 AC_SUBST(MOZ_PKG_SPECIAL)
 
new file mode 100644
--- /dev/null
+++ b/moz.configure
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+include('build/moz.configure/init.configure')
+
+# Fallthrough to autoconf-based configure
+include('build/moz.configure/old.configure')
--- a/old-configure.in
+++ b/old-configure.in
@@ -91,54 +91,16 @@ dnl ====================================
 MOZ_USE_PTHREADS=
 _PTHREAD_LDFLAGS=""
 
 dnl Do not allow objdir == srcdir builds.
 dnl ==============================================================
 _topsrcdir=`cd \`dirname $0\`; pwd -W 2>/dev/null || pwd -P`
 _objdir=`pwd -P`
 
-if test "$_topsrcdir" = "$_objdir"; then
-  echo "  ***"
-  echo "  * Building directly in the main source directory is not allowed."
-  echo "  *"
-  echo "  * To build, you must run configure from a separate directory"
-  echo "  * (referred to as an object directory)."
-  echo "  *"
-  echo "  * If you are building with a mozconfig, you will need to change your"
-  echo "  * mozconfig to point to a different object directory."
-  echo "  ***"
-  exit 1
-fi
-
-# Check for a couple representative files in the source tree
-_conflict_files=
-for file in $_topsrcdir/Makefile $_topsrcdir/config/autoconf.mk; do
-  if test -f $file; then
-    _conflict_files="$_conflict_files $file"
-  fi
-done
-if test "$_conflict_files"; then
-  echo "***"
-  echo "*   Your source tree contains these files:"
-  for file in $_conflict_files; do
-    echo "*         $file"
-  done
-  cat 1>&2 <<-EOF
-  *   This indicates that you previously built in the source tree.
-  *   A source tree build can confuse the separate objdir build.
-  *
-  *   To clean up the source tree:
-  *     1. cd $_topsrcdir
-  *     2. gmake distclean
-  ***
-EOF
-  exit 1
-  break
-fi
 MOZ_BUILD_ROOT=`pwd -W 2>/dev/null || pwd -P`
 DIST="$MOZ_BUILD_ROOT/dist"
 
 MOZ_PYTHON
 
 MOZ_DEFAULT_COMPILER
 
 COMPILE_ENVIRONMENT=1
@@ -8488,18 +8450,16 @@ AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
 AC_SUBST(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
 AC_SUBST(MOZ_COMPONENT_NSPR_LIBS)
 
 AC_SUBST(MOZ_FIX_LINK_PATHS)
 
 AC_SUBST(USE_DEPENDENT_LIBS)
 
-AC_SUBST(MOZ_BUILD_ROOT)
-
 AC_SUBST(MOZ_POST_PROGRAM_COMMAND)
 AC_SUBST(MOZ_LINKER_EXTRACT)
 
 AC_SUBST(MOZ_ADDON_SIGNING)
 AC_SUBST(MOZ_REQUIRE_SIGNING)
 
 if test -n "$MOZ_BINARY_EXTENSIONS"; then
   AC_DEFINE(MOZ_BINARY_EXTENSIONS)
--- a/python/mozbuild/mozbuild/configure/options.py
+++ b/python/mozbuild/mozbuild/configure/options.py
@@ -353,34 +353,37 @@ class CommandLineHelper(object):
     Extra options can be added afterwards through API calls. For those,
     conflicting values will raise an exception.
     '''
     def __init__(self, environ=os.environ, argv=sys.argv):
         self._environ = dict(environ)
         self._args = OrderedDict()
         self._extra_args = OrderedDict()
         self._origins = {}
+        self._last = 0
 
-        for position, arg in enumerate(argv[1:]):
-            prefix, name, values = Option.split_option(arg)
-            self._args[name] = arg, position
+        for arg in argv[1:]:
+            self.add(arg, 'command-line', self._args)
 
-    def add(self, arg, origin='command-line'):
+    def add(self, arg, origin='command-line', args=None):
         assert origin != 'default'
         prefix, name, values = Option.split_option(arg)
-        if name in self._extra_args:
+        if args is None:
+            args = self._extra_args
+        if args is self._extra_args and name in self._extra_args:
             old_arg = self._extra_args[name][0]
             old_prefix, _, old_values = Option.split_option(old_arg)
             if prefix != old_prefix or values != old_values:
                 raise ConflictingOption(
                     "Cannot add '{arg}' to the {origin} set because it "
                     "conflicts with '{old_arg}' that was added earlier",
                     arg=arg, origin=origin, old_arg=old_arg,
                     old_origin=self._origins[old_arg])
-        self._extra_args[name] = arg, -1 - len(self._extra_args)
+        self._last += 1
+        args[name] = arg, self._last
         self._origins[arg] = origin
 
     def _prepare(self, option, args):
         arg, pos = None, 0
         origin = 'command-line'
         from_name = args.get(option.name)
         from_env = args.get(option.env)
         if from_name and from_env:
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1391,41 +1391,16 @@ class MachDebug(MachCommandBase):
         print('MOZ_OBJDIR=%s' % objdir, file=out)
         if 'MOZ_CURRENT_PROJECT' in os.environ:
             objdir = mozpath.join(objdir, os.environ['MOZ_CURRENT_PROJECT'])
         print('OBJDIR=%s' % objdir, file=out)
         if self.mozconfig['path']:
             print('FOUND_MOZCONFIG=%s' % mozpath.normsep(self.mozconfig['path']),
                 file=out)
 
-    def _environment_configure(self, out, verbose):
-        if self.mozconfig['path']:
-            # Replace ' with '"'"', so that shell quoting e.g.
-            # a'b becomes 'a'"'"'b'.
-            quote = lambda s: s.replace("'", """'"'"'""")
-            if self.mozconfig['configure_args'] and \
-                    'COMM_BUILD' not in os.environ:
-                print('echo Adding configure options from %s' %
-                    mozpath.normsep(self.mozconfig['path']), file=out)
-                for arg in self.mozconfig['configure_args']:
-                    quoted_arg = quote(arg)
-                    print("echo '  %s'" % quoted_arg, file=out)
-                    print("""set -- "$@" '%s'""" % quoted_arg, file=out)
-            for key, value in self.mozconfig['env']['added'].items():
-                print("export %s='%s'" % (key, quote(value)), file=out)
-            for key, (old, value) in self.mozconfig['env']['modified'].items():
-                print("export %s='%s'" % (key, quote(value)), file=out)
-            for key, value in self.mozconfig['vars']['added'].items():
-                print("%s='%s'" % (key, quote(value)), file=out)
-            for key, (old, value) in self.mozconfig['vars']['modified'].items():
-                print("%s='%s'" % (key, quote(value)), file=out)
-            for key in self.mozconfig['env']['removed'].keys() + \
-                    self.mozconfig['vars']['removed'].keys():
-                print("unset %s" % key, file=out)
-
     def _environment_json(self, out, verbose):
         import json
         class EnvironmentEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, MozbuildObject):
                     result = {
                         'topsrcdir': obj.topsrcdir,
                         'topobjdir': obj.topobjdir,