Bug 1250297 - Make python configure output config.status instead of old-configure doing it. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 26 Feb 2016 04:59:53 +0900
changeset 325050 49f9aa2b50f0b2248a25376fa065832cb13abf8a
parent 325049 f08207e3060c54aada453f9c36744d7f02bfa55d
child 325051 33848fe940846ded14720d238f0e5ba872221166
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1250297
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1250297 - Make python configure output config.status instead of old-configure doing it. r=gps The nice side effect is that now we can have actual dicts for defines and substs from the start, which simplifies so things, although it requires adjustments to some unit tests.
CLOBBER
build/autoconf/config.status.m4
configure.py
js/src/old-configure.in
old-configure.in
python/mozbuild/mozbuild/backend/configenvironment.py
python/mozbuild/mozbuild/config_status.py
python/mozbuild/mozbuild/test/backend/common.py
python/mozbuild/mozbuild/test/backend/test_configenvironment.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,10 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1238079 seems to have needed a clobber for Mac
+Bug 1250297 - Doesn't actually need a clobber, but updating the tree to an older changeset does.
 
--- a/build/autoconf/config.status.m4
+++ b/build/autoconf/config.status.m4
@@ -78,142 +78,95 @@ define([AC_DEFINE_UNQUOTED],
 EOF
 ifelse($#, 2, _MOZ_AC_DEFINE_UNQUOTED($1, $2), $#, 3, _MOZ_AC_DEFINE_UNQUOTED($1, $2, $3),_MOZ_AC_DEFINE_UNQUOTED($1))dnl
 ])
 
 dnl Replace AC_OUTPUT to create and call a python config.status
 define([MOZ_CREATE_CONFIG_STATUS],
 [dnl Top source directory in Windows format (as opposed to msys format).
 WIN_TOP_SRC=
-encoding=utf-8
 case "$host_os" in
 mingw*)
     WIN_TOP_SRC=`cd $srcdir; pwd -W`
-    encoding=mbcs
     ;;
 esac
 AC_SUBST(WIN_TOP_SRC)
 
 dnl Used in all Makefile.in files
 top_srcdir=$srcdir
 AC_SUBST(top_srcdir)
 
 dnl Picked from autoconf 2.13
 trap '' 1 2 15
 AC_CACHE_SAVE
 
 trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
-: ${CONFIG_STATUS=./config.status}
+: ${CONFIG_STATUS=./config.data}
 
 dnl We're going to need [ ] for python syntax.
 changequote(<<<, >>>)dnl
 echo creating $CONFIG_STATUS
 
 extra_python_path=${COMM_BUILD:+"'mozilla', "}
 
 cat > $CONFIG_STATUS <<EOF
-#!${PYTHON}
-# coding=$encoding
-
-import os
-import types
-dnl topsrcdir is the top source directory in native form, as opposed to a
-dnl form suitable for make.
-topsrcdir = '''${WIN_TOP_SRC:-$srcdir}'''
-if not os.path.isabs(topsrcdir):
-    rel = os.path.join(os.path.dirname(<<<__file__>>>), topsrcdir)
-    topsrcdir = os.path.abspath(rel)
-topsrcdir = os.path.normpath(topsrcdir)
-
-topobjdir = os.path.abspath(os.path.dirname(<<<__file__>>>))
-
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
 dnl All defines and substs are stored with an additional space at the beginning
 dnl and at the end of the string, to avoid any problem with values starting or
 dnl ending with quotes.
-defines = [(name[1:-1], value[1:-1]) for name, value in [
+defines = [
 EOF
 
 dnl confdefs.pytmp contains AC_DEFINEs, in the expected format, but
 dnl lacks the final comma (see above).
 sed 's/$/,/' confdefs.pytmp >> $CONFIG_STATUS
 rm confdefs.pytmp confdefs.h
 
 cat >> $CONFIG_STATUS <<\EOF
-] ]
+]
 
-substs = [(name[1:-1], value[1:-1] if isinstance(value, types.StringTypes) else value) for name, value in [
+substs = [
 EOF
 
 dnl The MOZ_DIVERSION_SUBST output diversion contains AC_SUBSTs, in the
 dnl expected format, but lacks the final comma (see above).
 sed 's/$/,/' >> $CONFIG_STATUS <<EOF
 undivert(MOZ_DIVERSION_SUBST)dnl
 EOF
 
 dnl Add in the output from the subconfigure script
 for ac_subst_arg in $_subconfigure_ac_subst_args; do
   variable='$'$ac_subst_arg
   echo "    (''' $ac_subst_arg ''', r''' `eval echo $variable` ''')," >> $CONFIG_STATUS
 done
 
 cat >> $CONFIG_STATUS <<\EOF
-] ]
+]
 
 dnl List of AC_DEFINEs that aren't to be exposed in ALLDEFINES
 non_global_defines = [
 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
 ]
-
-__all__ = ['topobjdir', 'topsrcdir', 'defines', 'non_global_defines', 'substs']
-EOF
-
-# We don't want js/src/config.status to do anything in gecko builds.
-if test -z "$BUILDING_JS" -o -n "$JS_STANDALONE"; then
-
-    cat >> $CONFIG_STATUS <<EOF
-dnl Do the actual work
-if __name__ == '__main__':
-    args = dict([(name, globals()[name]) for name in __all__])
-    from mozbuild.config_status import config_status
-    config_status(**args)
 EOF
 
-fi
-
 changequote([, ])
-
-chmod +x $CONFIG_STATUS
-])
-
-define([MOZ_RUN_CONFIG_STATUS],
-[
-
-MOZ_RUN_ALL_SUBCONFIGURES()
-
-rm -fr confdefs* $ac_clean_files
-dnl Execute config.status, unless --no-create was passed to configure.
-if test "$no_create" != yes && ! ${PYTHON} $CONFIG_STATUS; then
-    trap '' EXIT
-    exit 1
-fi
 ])
 
 define([m4_fatal],[
 errprint([$1
 ])
 m4exit(1)
 ])
 
--- a/configure.py
+++ b/configure.py
@@ -1,22 +1,26 @@
 # 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.dirname(__file__)
+
+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...
@@ -111,13 +115,75 @@ def main(args):
     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
 
-    return subprocess.call([shell, old_configure] + args)
+    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']
+    }
+    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())
 
+    # 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)
+    with codecs.open('config.status', 'w', encoding) as fh:
+        fh.write('#!%s\n' % config['substs']['PYTHON'])
+        fh.write('# coding=%s\n' % encoding)
+        for k, v in 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'):
+            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'):
+            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 0
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -163,16 +163,17 @@ if test "$JS_STANDALONE" = no; then
   #DIST is exported from top-level configure
 else
   JS_STANDALONE=1
   AC_DEFINE(JS_STANDALONE)
   DIST="$MOZ_BUILD_ROOT/dist"
 fi
 AC_SUBST(JS_STANDALONE)
 BUILDING_JS=1
+AC_SUBST(BUILDING_JS)
 AC_SUBST(autoconfmk)
 
 MOZ_ARG_WITH_STRING(gonk,
 [  --with-gonk=DIR
                location of gonk dir],
     gonkdir=$withval)
 
 MOZ_ARG_WITH_STRING(gonk-toolchain-prefix,
@@ -3662,10 +3663,12 @@ MOZ_SUBCONFIGURE_JEMALLOC()
 # Avoid using obsolete NSPR features
 AC_DEFINE(NO_NSPR_10_SUPPORT)
 
 dnl Spit out some output
 dnl ========================================================
 MOZ_CREATE_CONFIG_STATUS()
 
 if test "$JS_STANDALONE"; then
-  MOZ_RUN_CONFIG_STATUS()
+  MOZ_RUN_ALL_SUBCONFIGURES()
 fi
+
+rm -fr confdefs* $ac_clean_files
--- a/old-configure.in
+++ b/old-configure.in
@@ -9181,16 +9181,16 @@ if ! test -e js; then
     mkdir js
 fi
 
 AC_OUTPUT_SUBDIRS(js/src,$cache_file)
 ac_configure_args="$_SUBDIR_CONFIG_ARGS"
 
 fi # COMPILE_ENVIRONMENT
 
-export WRITE_MOZINFO=1
 dnl we need to run config.status after js/src subconfigure because we're
 dnl traversing its moz.build and we need its config.status for that.
 dnl However, writing our own config.status needs to happen before
 dnl subconfigures because the setup surrounding subconfigures alters
 dnl many AC_SUBSTed variables.
-MOZ_RUN_CONFIG_STATUS()
-unset WRITE_MOZINFO
+MOZ_RUN_ALL_SUBCONFIGURES()
+
+rm -fr confdefs* $ac_clean_files
--- a/python/mozbuild/mozbuild/backend/configenvironment.py
+++ b/python/mozbuild/mozbuild/backend/configenvironment.py
@@ -73,22 +73,21 @@ class ConfigEnvironment(object):
     and output results in the object directory.
 
     There are two types of files: config files and config headers,
     each treated through a different member function.
 
     Creating a ConfigEnvironment requires a few arguments:
       - topsrcdir and topobjdir are, respectively, the top source and
         the top object directory.
-      - defines is a list of (name, value) tuples. In autoconf, these are
-        set with AC_DEFINE and AC_DEFINE_UNQUOTED
+      - defines is a dict filled from AC_DEFINE and AC_DEFINE_UNQUOTED in
+        autoconf.
       - non_global_defines are a list of names appearing in defines above
         that are not meant to be exported in ACDEFINES (see below)
-      - substs is a list of (name, value) tuples. In autoconf, these are
-        set with AC_SUBST.
+      - substs is a dict filled from AC_SUBST in autoconf.
 
     ConfigEnvironment automatically defines one additional substs variable
     from all the defines not appearing in non_global_defines:
       - ACDEFINES contains the defines in the form -DNAME=VALUE, for use on
         preprocessor command lines. The order in which defines were given
         when creating the ConfigEnvironment is preserved.
     and two other additional subst variables from all the other substs:
       - ALLSUBSTS contains the substs in the form NAME = VALUE, in sorted
@@ -101,43 +100,44 @@ class ConfigEnvironment(object):
         NAME =.
 
     ConfigEnvironment expects a "top_srcdir" subst to be set with the top
     source directory, in msys format on windows. It is used to derive a
     "srcdir" subst when treating config files. It can either be an absolute
     path or a path relative to the topobjdir.
     """
 
-    def __init__(self, topsrcdir, topobjdir, defines=[], non_global_defines=[],
-        substs=[], source=None):
+    def __init__(self, topsrcdir, topobjdir, defines=None,
+        non_global_defines=None, substs=None, source=None):
 
         if not source:
             source = mozpath.join(topobjdir, 'config.status')
         self.source = source
-        self.defines = ReadOnlyDict(defines)
-        self.non_global_defines = non_global_defines
-        self.substs = dict(substs)
+        self.defines = ReadOnlyDict(defines or {})
+        self.non_global_defines = non_global_defines or []
+        self.substs = dict(substs or {})
         self.topsrcdir = mozpath.abspath(topsrcdir)
         self.topobjdir = mozpath.abspath(topobjdir)
         self.lib_prefix = self.substs.get('LIB_PREFIX', '')
         if 'LIB_SUFFIX' in self.substs:
             self.lib_suffix = '.%s' % self.substs['LIB_SUFFIX']
         self.dll_prefix = self.substs.get('DLL_PREFIX', '')
         self.dll_suffix = self.substs.get('DLL_SUFFIX', '')
         if self.substs.get('IMPORT_LIB_SUFFIX'):
             self.import_prefix = self.lib_prefix
             self.import_suffix = '.%s' % self.substs['IMPORT_LIB_SUFFIX']
         else:
             self.import_prefix = self.dll_prefix
             self.import_suffix = self.dll_suffix
 
-        global_defines = [name for name, value in defines
-            if not name in non_global_defines]
+        global_defines = [name for name in self.defines
+            if not name in self.non_global_defines]
         self.substs['ACDEFINES'] = ' '.join(['-D%s=%s' % (name,
-            shell_quote(self.defines[name]).replace('$', '$$')) for name in global_defines])
+            shell_quote(self.defines[name]).replace('$', '$$'))
+            for name in sorted(global_defines)])
         def serialize(obj):
             if isinstance(obj, StringTypes):
                 return obj
             if isinstance(obj, Iterable):
                 return ' '.join(obj)
             raise Exception('Unhandled type %s', type(obj))
         self.substs['ALLSUBSTS'] = '\n'.join(sorted(['%s = %s' % (name,
             serialize(self.substs[name])) for name in self.substs if self.substs[name]]))
--- a/python/mozbuild/mozbuild/config_status.py
+++ b/python/mozbuild/mozbuild/config_status.py
@@ -57,18 +57,18 @@ Visual Studio project files (yes, Intell
 following:
 
    mach build-backend --backend=VisualStudio
 
 ===============================
 '''.strip()
 
 
-def config_status(topobjdir='.', topsrcdir='.',
-        defines=[], non_global_defines=[], substs=[], source=None):
+def config_status(topobjdir='.', topsrcdir='.', defines=None,
+                  non_global_defines=None, substs=None, source=None):
     '''Main function, providing config.status functionality.
 
     Contrary to config.status, it doesn't use CONFIG_FILES or CONFIG_HEADERS
     variables.
 
     Without the -n option, this program acts as config.status and considers
     the current directory as the top object directory, even when config.status
     is in a different directory. It will, however, treat the directory
@@ -90,23 +90,17 @@ def config_status(topobjdir='.', topsrcd
         raise Exception('Using the CONFIG_HEADERS environment variable is not '
             'supported.')
 
     if not os.path.isabs(topsrcdir):
         raise Exception('topsrcdir must be defined as an absolute directory: '
             '%s' % topsrcdir)
 
     default_backends = ['RecursiveMake']
-    # We have a chicken/egg problem, where we only have a dict for substs after
-    # creating the ConfigEnvironment, which requires argument parsing to have
-    # occurred.
-    for name, value in substs:
-        if name == 'BUILD_BACKENDS':
-            default_backends = value
-            break
+    default_backends = (substs or {}).get('BUILD_BACKENDS', ['RecursiveMake'])
 
     parser = ArgumentParser()
     parser.add_argument('--recheck', dest='recheck', action='store_true',
                         help='update config.status by reconfiguring in the same conditions')
     parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
                         help='display verbose output')
     parser.add_argument('-n', dest='not_topobjdir', action='store_true',
                         help='do not consider current directory as top object directory')
--- a/python/mozbuild/mozbuild/test/backend/common.py
+++ b/python/mozbuild/mozbuild/test/backend/common.py
@@ -24,84 +24,84 @@ log_manager = LoggingManager()
 log_manager.add_terminal_logging()
 
 
 test_data_path = mozpath.abspath(mozpath.dirname(__file__))
 test_data_path = mozpath.join(test_data_path, 'data')
 
 
 CONFIGS = defaultdict(lambda: {
-    'defines': [],
+    'defines': {},
     'non_global_defines': [],
-    'substs': [('OS_TARGET', 'WINNT')],
+    'substs': {'OS_TARGET': 'WINNT'},
 }, {
     'android_eclipse': {
-        'defines': [
-            ('MOZ_ANDROID_MIN_SDK_VERSION', '15'),
-        ],
+        'defines': {
+            'MOZ_ANDROID_MIN_SDK_VERSION': '15',
+        },
         'non_global_defines': [],
-        'substs': [
-            ('ANDROID_TARGET_SDK', '16'),
-            ('MOZ_WIDGET_TOOLKIT', 'android'),
-        ],
+        'substs': {
+            'ANDROID_TARGET_SDK': '16',
+            'MOZ_WIDGET_TOOLKIT': 'android',
+        },
     },
     'binary-components': {
-        'defines': [],
+        'defines': {},
         'non_global_defines': [],
-        'substs': [
-            ('LIB_PREFIX', 'lib'),
-            ('LIB_SUFFIX', 'a'),
-        ],
+        'substs': {
+            'LIB_PREFIX': 'lib',
+            'LIB_SUFFIX': 'a',
+        },
     },
     'sources': {
-        'defines': [],
+        'defines': {},
         'non_global_defines': [],
-        'substs': [
-            ('LIB_PREFIX', 'lib'),
-            ('LIB_SUFFIX', 'a'),
-        ],
+        'substs': {
+            'LIB_PREFIX': 'lib',
+            'LIB_SUFFIX': 'a',
+        },
     },
     'stub0': {
-        'defines': [
-            ('MOZ_TRUE_1', '1'),
-            ('MOZ_TRUE_2', '1'),
-        ],
+        'defines': {
+            'MOZ_TRUE_1': '1',
+            'MOZ_TRUE_2': '1',
+        },
         'non_global_defines': [
-            ('MOZ_NONGLOBAL_1', '1'),
-            ('MOZ_NONGLOBAL_2', '1'),
+            'MOZ_NONGLOBAL_1',
+            'MOZ_NONGLOBAL_2',
         ],
-        'substs': [
-            ('MOZ_FOO', 'foo'),
-            ('MOZ_BAR', 'bar'),
-        ],
+        'substs': {
+            'MOZ_FOO': 'foo',
+            'MOZ_BAR': 'bar',
+        },
     },
     'substitute_config_files': {
-        'defines': [],
+        'defines': {},
         'non_global_defines': [],
-        'substs': [
-            ('MOZ_FOO', 'foo'),
-            ('MOZ_BAR', 'bar'),
-        ],
+        'substs': {
+            'MOZ_FOO': 'foo',
+            'MOZ_BAR': 'bar',
+        },
     },
     'test_config': {
-        'defines': [
-            ('foo', 'baz qux'),
-            ('baz', 1)
-        ],
+        'defines': {
+            'foo': 'baz qux',
+            'baz': 1,
+        },
         'non_global_defines': [],
-        'substs': [
-            ('foo', 'bar baz'),
-        ],
+        'substs': {
+            'foo': 'bar baz',
+        },
     },
     'visual-studio': {
-        'defines': [],
+        'defines': {},
         'non_global_defines': [],
-        'substs': [
-            ('MOZ_APP_NAME', 'my_app'),
-        ],
+        'substs': {
+            'MOZ_APP_NAME': 'my_app',
+        },
     },
 })
 
 
 class BackendTester(unittest.TestCase):
     def setUp(self):
         self._old_env = dict(os.environ)
         os.environ.pop('MOZ_OBJDIR', None)
@@ -117,17 +117,17 @@ class BackendTester(unittest.TestCase):
         environment is cleaned up automatically when the test finishes.
         """
         config = CONFIGS[name]
 
         objdir = mkdtemp()
         self.addCleanup(rmtree, objdir)
 
         srcdir = mozpath.join(test_data_path, name)
-        config['substs'].append(('top_srcdir', srcdir))
+        config['substs']['top_srcdir'] = srcdir
         return ConfigEnvironment(srcdir, objdir, **config)
 
     def _emit(self, name, env=None):
         env = env or self._get_environment(name)
         reader = BuildReader(env)
         emitter = TreeMetadataEmitter(env)
 
         return env, emitter.emit(reader.read_topsrcdir())
--- a/python/mozbuild/mozbuild/test/backend/test_configenvironment.py
+++ b/python/mozbuild/mozbuild/test/backend/test_configenvironment.py
@@ -34,28 +34,28 @@ class ConfigEnvironment(ConfigStatus.Con
 
 
 class TestEnvironment(unittest.TestCase):
     def test_auto_substs(self):
         '''Test the automatically set values of ACDEFINES, ALLSUBSTS
         and ALLEMPTYSUBSTS.
         '''
         env = ConfigEnvironment('.', '.',
-                  defines = [ ('foo', 'bar'), ('baz', 'qux 42'),
-                              ('abc', "d'e'f"), ('extra', 'foobar') ],
+                  defines = { 'foo': 'bar', 'baz': 'qux 42',
+                              'abc': "d'e'f", 'extra': 'foobar' },
                   non_global_defines = ['extra', 'ignore'],
-                  substs = [ ('FOO', 'bar'), ('FOOBAR', ''), ('ABC', 'def'),
-                             ('bar', 'baz qux'), ('zzz', '"abc def"'),
-                             ('qux', '') ])
+                  substs = { 'FOO': 'bar', 'FOOBAR': '', 'ABC': 'def',
+                             'bar': 'baz qux', 'zzz': '"abc def"',
+                             'qux': '' })
         # non_global_defines should be filtered out in ACDEFINES.
         # Original order of the defines need to be respected in ACDEFINES
-        self.assertEqual(env.substs['ACDEFINES'], """-Dfoo=bar -Dbaz='qux 42' -Dabc='d'\\''e'\\''f'""")
+        self.assertEqual(env.substs['ACDEFINES'], """-Dabc='d'\\''e'\\''f' -Dbaz='qux 42' -Dfoo=bar""")
         # Likewise for ALLSUBSTS, which also must contain ACDEFINES
         self.assertEqual(env.substs['ALLSUBSTS'], '''ABC = def
-ACDEFINES = -Dfoo=bar -Dbaz='qux 42' -Dabc='d'\\''e'\\''f'
+ACDEFINES = -Dabc='d'\\''e'\\''f' -Dbaz='qux 42' -Dfoo=bar
 FOO = bar
 bar = baz qux
 zzz = "abc def"''')
         # ALLEMPTYSUBSTS contains all substs with no value.
         self.assertEqual(env.substs['ALLEMPTYSUBSTS'], '''FOOBAR =
 qux =''')