misc: Restore ostream flags where needed
[gem5.git] / SConstruct
index b175cb4911f1e834d93520738de34672dff5387d..9a64840fc1b4e1fdccaf65bc7e230f8e9b43a63b 100755 (executable)
@@ -1,5 +1,17 @@
 # -*- mode:python -*-
 
+# Copyright (c) 2013 ARM Limited
+# All rights reserved.
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
 # Copyright (c) 2011 Advanced Micro Devices, Inc.
 # Copyright (c) 2009 The Hewlett-Packard Development Company
 # Copyright (c) 2004-2005 The Regents of The University of Michigan
@@ -81,16 +93,15 @@ For more details, see:
 """
     raise
 
-# We ensure the python version early because we have stuff that
-# requires python 2.4
+# We ensure the python version early because because python-config
+# requires python 2.5
 try:
-    EnsurePythonVersion(2, 4)
+    EnsurePythonVersion(2, 5)
 except SystemExit, e:
     print """
 You can use a non-default installation of the Python interpreter by
-either (1) rearranging your PATH so that scons finds the non-default
-'python' first or (2) explicitly invoking an alternative interpreter
-on the scons script.
+rearranging your PATH so that scons finds the non-default 'python' and
+'python-config' first.
 
 For more details, see:
     http://gem5.org/wiki/index.php/Using_a_non-default_Python_installation
@@ -98,6 +109,7 @@ For more details, see:
     raise
 
 # Global Python includes
+import itertools
 import os
 import re
 import subprocess
@@ -179,9 +191,11 @@ termcap = get_termcap(GetOption('use_colors'))
 # Set up the main build environment.
 #
 ########################################################################
+
+# export TERM so that clang reports errors in color
 use_vars = set([ 'AS', 'AR', 'CC', 'CXX', 'HOME', 'LD_LIBRARY_PATH',
-                 'LIBRARY_PATH', 'PATH', 'PKG_CONFIG_PATH', 'PYTHONPATH',
-                 'RANLIB', 'SWIG' ])
+                 'LIBRARY_PATH', 'PATH', 'PKG_CONFIG_PATH', 'PROTOC',
+                 'PYTHONPATH', 'RANLIB', 'SWIG', 'TERM' ])
 
 use_prefixes = [
     "M5",           # M5 configuration (e.g., path to kernels)
@@ -554,52 +568,62 @@ else:
     Exit(1)
 
 if main['GCC']:
-    # Check for a supported version of gcc, >= 4.4 is needed for c++0x
-    # support. See http://gcc.gnu.org/projects/cxx0x.html for details
+    # Check for a supported version of gcc. >= 4.6 is chosen for its
+    # level of c++11 support. See
+    # http://gcc.gnu.org/projects/cxx0x.html for details. 4.6 is also
+    # the first version with proper LTO support.
     gcc_version = readCommand([main['CXX'], '-dumpversion'], exception=False)
-    if compareVersions(gcc_version, "4.4") < 0:
-        print 'Error: gcc version 4.4 or newer required.'
+    if compareVersions(gcc_version, "4.6") < 0:
+        print 'Error: gcc version 4.6 or newer required.'
         print '       Installed version:', gcc_version
         Exit(1)
 
     main['GCC_VERSION'] = gcc_version
 
-    # Check for versions with bugs
-    if not compareVersions(gcc_version, '4.4.1') or \
-       not compareVersions(gcc_version, '4.4.2'):
-        print 'Info: Tree vectorizer in GCC 4.4.1 & 4.4.2 is buggy, disabling.'
-        main.Append(CCFLAGS=['-fno-tree-vectorize'])
-
-    # LTO support is only really working properly from 4.6 and beyond
-    if compareVersions(gcc_version, '4.6') >= 0:
-        # Add the appropriate Link-Time Optimization (LTO) flags
-        # unless LTO is explicitly turned off. Note that these flags
-        # are only used by the fast target.
-        if not GetOption('no_lto'):
-            # Pass the LTO flag when compiling to produce GIMPLE
-            # output, we merely create the flags here and only append
-            # them later/
-            main['LTO_CCFLAGS'] = ['-flto=%d' % GetOption('num_jobs')]
-
-            # Use the same amount of jobs for LTO as we are running
-            # scons with, we hardcode the use of the linker plugin
-            # which requires either gold or GNU ld >= 2.21
-            main['LTO_LDFLAGS'] = ['-flto=%d' % GetOption('num_jobs'),
-                                   '-fuse-linker-plugin']
+    # gcc from version 4.8 and above generates "rep; ret" instructions
+    # to avoid performance penalties on certain AMD chips. Older
+    # assemblers detect this as an error, "Error: expecting string
+    # instruction after `rep'"
+    if compareVersions(gcc_version, "4.8") > 0:
+        as_version = readCommand([main['AS'], '-v', '/dev/null'],
+                                 exception=False).split()
+
+        if not as_version or compareVersions(as_version[-1], "2.23") < 0:
+            print termcap.Yellow + termcap.Bold + \
+                'Warning: This combination of gcc and binutils have' + \
+                ' known incompatibilities.\n' + \
+                '         If you encounter build problems, please update ' + \
+                'binutils to 2.23.' + \
+                termcap.Normal
+
+    # Add the appropriate Link-Time Optimization (LTO) flags
+    # unless LTO is explicitly turned off. Note that these flags
+    # are only used by the fast target.
+    if not GetOption('no_lto'):
+        # Pass the LTO flag when compiling to produce GIMPLE
+        # output, we merely create the flags here and only append
+        # them later/
+        main['LTO_CCFLAGS'] = ['-flto=%d' % GetOption('num_jobs')]
+
+        # Use the same amount of jobs for LTO as we are running
+        # scons with, we hardcode the use of the linker plugin
+        # which requires either gold or GNU ld >= 2.21
+        main['LTO_LDFLAGS'] = ['-flto=%d' % GetOption('num_jobs'),
+                               '-fuse-linker-plugin']
 
     main.Append(TCMALLOC_CCFLAGS=['-fno-builtin-malloc', '-fno-builtin-calloc',
                                   '-fno-builtin-realloc', '-fno-builtin-free'])
 
 elif main['CLANG']:
-    # Check for a supported version of clang, >= 2.9 is needed to
-    # support similar features as gcc 4.4. See
+    # Check for a supported version of clang, >= 3.0 is needed to
+    # support similar features as gcc 4.6. See
     # http://clang.llvm.org/cxx_status.html for details
     clang_version_re = re.compile(".* version (\d+\.\d+)")
-    clang_version_match = clang_version_re.match(CXX_version)
+    clang_version_match = clang_version_re.search(CXX_version)
     if (clang_version_match):
         clang_version = clang_version_match.groups()[0]
-        if compareVersions(clang_version, "2.9") < 0:
-            print 'Error: clang version 2.9 or newer required.'
+        if compareVersions(clang_version, "3.0") < 0:
+            print 'Error: clang version 3.0 or newer required.'
             print '       Installed version:', clang_version
             Exit(1)
     else:
@@ -614,20 +638,20 @@ elif main['CLANG']:
     # is relying on this
     main.Append(CCFLAGS=['-Wno-tautological-compare',
                          '-Wno-parentheses',
-                         '-Wno-self-assign'])
+                         '-Wno-self-assign',
+                         # Some versions of libstdc++ (4.8?) seem to
+                         # use struct hash and class hash
+                         # interchangeably.
+                         '-Wno-mismatched-tags',
+                         ])
 
     main.Append(TCMALLOC_CCFLAGS=['-fno-builtin'])
 
     # On Mac OS X/Darwin we need to also use libc++ (part of XCode) as
-    # opposed to libstdc++ to make the transition from TR1 to
-    # C++11. See http://libcxx.llvm.org. However, clang has chosen a
-    # strict implementation of the C++11 standard, and does not allow
-    # incomplete types in template arguments (besides unique_ptr and
-    # shared_ptr), and the libc++ STL containers create problems in
-    # combination with the current gem5 code. For now, we stick with
-    # libstdc++ and use the TR1 namespace.
-    # if sys.platform == "darwin":
-    #     main.Append(CXXFLAGS=['-stdlib=libc++'])
+    # opposed to libstdc++, as the later is dated.
+    if sys.platform == "darwin":
+        main.Append(CXXFLAGS=['-stdlib=libc++'])
+        main.Append(LIBS=['c++'])
 
 else:
     print termcap.Yellow + termcap.Bold + 'Error' + termcap.Normal,
@@ -718,21 +742,12 @@ if len(swig_version) < 3 or \
     print 'Error determining SWIG version.'
     Exit(1)
 
-min_swig_version = '1.3.34'
+min_swig_version = '2.0.4'
 if compareVersions(swig_version[2], min_swig_version) < 0:
     print 'Error: SWIG version', min_swig_version, 'or newer required.'
     print '       Installed version:', swig_version[2]
     Exit(1)
 
-if swig_version[2] == "2.0.9":
-    print '\n' + termcap.Yellow + termcap.Bold + \
-        'Warning: SWIG version 2.0.9 sometimes generates broken code.\n' + \
-        termcap.Normal + \
-        'This problem only affects some platforms and some Python\n' + \
-        'versions. See the following SWIG bug report for details:\n' + \
-        'http://sourceforge.net/p/swig/bugs/1297/\n'
-
-
 # Set up SWIG flags & scanner
 swig_flags=Split('-c++ -python -modern -templatereduce $_CPPINCFLAGS')
 main.Append(SWIGFLAGS=swig_flags)
@@ -784,12 +799,35 @@ def CheckLeading(context):
     context.Result(ret)
     return ret
 
+# Add a custom Check function to test for structure members.
+def CheckMember(context, include, decl, member, include_quotes="<>"):
+    context.Message("Checking for member %s in %s..." %
+                    (member, decl))
+    text = """
+#include %(header)s
+int main(){
+  %(decl)s test;
+  (void)test.%(member)s;
+  return 0;
+};
+""" % { "header" : include_quotes[0] + include + include_quotes[1],
+        "decl" : decl,
+        "member" : member,
+        }
+
+    ret = context.TryCompile(text, extension=".cc")
+    context.Result(ret)
+    return ret
+
 # Platform-specific configuration.  Note again that we assume that all
 # builds under a given build root run on the same host platform.
 conf = Configure(main,
                  conf_dir = joinpath(build_root, '.scons_config'),
                  log_file = joinpath(build_root, 'scons_config.log'),
-                 custom_tests = { 'CheckLeading' : CheckLeading })
+                 custom_tests = {
+        'CheckLeading' : CheckLeading,
+        'CheckMember' : CheckMember,
+        })
 
 # Check for leading underscores.  Don't really need to worry either
 # way so don't need to check the return code.
@@ -827,55 +865,41 @@ if not conf:
 
     conf = NullConf(main)
 
-# Find Python include and library directories for embedding the
-# interpreter.  For consistency, we will use the same Python
-# installation used to run scons (and thus this script).  If you want
-# to link in an alternate version, see above for instructions on how
-# to invoke scons with a different copy of the Python interpreter.
-from distutils import sysconfig
-
-py_getvar = sysconfig.get_config_var
-
-py_debug = getattr(sys, 'pydebug', False)
-py_version = 'python' + py_getvar('VERSION') + (py_debug and "_d" or "")
-
-py_general_include = sysconfig.get_python_inc()
-py_platform_include = sysconfig.get_python_inc(plat_specific=True)
-py_includes = [ py_general_include ]
-if py_platform_include != py_general_include:
-    py_includes.append(py_platform_include)
-
-py_lib_path = [ py_getvar('LIBDIR') ]
-# add the prefix/lib/pythonX.Y/config dir, but only if there is no
-# shared library in prefix/lib/.
-if not py_getvar('Py_ENABLE_SHARED'):
-    py_lib_path.append(py_getvar('LIBPL'))
-    # Python requires the flags in LINKFORSHARED to be added the
-    # linker flags when linking with a statically with Python. Failing
-    # to do so can lead to errors from the Python's dynamic module
-    # loader at start up.
-    main.Append(LINKFLAGS=[py_getvar('LINKFORSHARED').split()])
-
-py_libs = []
-for lib in py_getvar('LIBS').split() + py_getvar('SYSLIBS').split():
-    if not lib.startswith('-l'):
-        # Python requires some special flags to link (e.g. -framework
-        # common on OS X systems), assume appending preserves order
-        main.Append(LINKFLAGS=[lib])
-    else:
-        lib = lib[2:]
-        if lib not in py_libs:
-            py_libs.append(lib)
-py_libs.append(py_version)
-
-main.Append(CPPPATH=py_includes)
-main.Append(LIBPATH=py_lib_path)
-
 # Cache build files in the supplied directory.
 if main['M5_BUILD_CACHE']:
     print 'Using build cache located at', main['M5_BUILD_CACHE']
     CacheDir(main['M5_BUILD_CACHE'])
 
+# Find Python include and library directories for embedding the
+# interpreter. We rely on python-config to resolve the appropriate
+# includes and linker flags. ParseConfig does not seem to understand
+# the more exotic linker flags such as -Xlinker and -export-dynamic so
+# we add them explicitly below. If you want to link in an alternate
+# version of python, see above for instructions on how to invoke
+# scons with the appropriate PATH set.
+#
+# First we check if python2-config exists, else we use python-config
+python_config = readCommand(['which', 'python2-config'], exception='').strip()
+if not os.path.exists(python_config):
+    python_config = readCommand(['which', 'python-config'],
+                                exception='').strip()
+py_includes = readCommand([python_config, '--includes'],
+                          exception='').split()
+# Strip the -I from the include folders before adding them to the
+# CPPPATH
+main.Append(CPPPATH=map(lambda inc: inc[2:], py_includes))
+
+# Read the linker flags and split them into libraries and other link
+# flags. The libraries are added later through the call the CheckLib.
+py_ld_flags = readCommand([python_config, '--ldflags'], exception='').split()
+py_libs = []
+for lib in py_ld_flags:
+     if not lib.startswith('-l'):
+         main.Append(LINKFLAGS=[lib])
+     else:
+         lib = lib[2:]
+         if lib not in py_libs:
+             py_libs.append(lib)
 
 # verify that this stuff works
 if not conf.CheckHeader('Python.h', '<>'):
@@ -925,7 +949,13 @@ have_posix_clock = \
     conf.CheckLibWithHeader('rt', 'time.h', 'C',
                             'clock_nanosleep(0,0,NULL,NULL);')
 
-if conf.CheckLib('tcmalloc_minimal'):
+have_posix_timers = \
+    conf.CheckLibWithHeader([None, 'rt'], [ 'time.h', 'signal.h' ], 'C',
+                            'timer_create(CLOCK_MONOTONIC, NULL, NULL);')
+
+if conf.CheckLib('tcmalloc'):
+    main.Append(CCFLAGS=main['TCMALLOC_CCFLAGS'])
+elif conf.CheckLib('tcmalloc_minimal'):
     main.Append(CCFLAGS=main['TCMALLOC_CCFLAGS'])
 else:
     print termcap.Yellow + termcap.Bold + \
@@ -942,6 +972,38 @@ if not have_fenv:
     print "Warning: Header file <fenv.h> not found."
     print "         This host has no IEEE FP rounding mode control."
 
+# Check if we should enable KVM-based hardware virtualization. The API
+# we rely on exists since version 2.6.36 of the kernel, but somehow
+# the KVM_API_VERSION does not reflect the change. We test for one of
+# the types as a fall back.
+have_kvm = conf.CheckHeader('linux/kvm.h', '<>') and \
+    conf.CheckTypeSize('struct kvm_xsave', '#include <linux/kvm.h>') != 0
+if not have_kvm:
+    print "Info: Compatible header file <linux/kvm.h> not found, " \
+        "disabling KVM support."
+
+# Check if the requested target ISA is compatible with the host
+def is_isa_kvm_compatible(isa):
+    isa_comp_table = {
+        "arm" : ( "armv7l" ),
+        "x86" : ( "x86_64" ),
+        }
+    try:
+        import platform
+        host_isa = platform.machine()
+    except:
+        print "Warning: Failed to determine host ISA."
+        return False
+
+    return host_isa in isa_comp_table.get(isa, [])
+
+
+# Check if the exclude_host attribute is available. We want this to
+# get accurate instruction counts in KVM.
+main['HAVE_PERF_ATTR_EXCLUDE_HOST'] = conf.CheckMember(
+    'linux/perf_event.h', 'struct perf_event_attr', 'exclude_host')
+
+
 ######################################################################
 #
 # Finish the configuration
@@ -963,17 +1025,10 @@ class CpuModel(object):
 
     # Dict of available CPU model objects.  Accessible as CpuModel.dict.
     dict = {}
-    list = []
-    defaults = []
 
     # Constructor.  Automatically adds models to CpuModel.dict.
-    def __init__(self, name, filename, includes, strings, default=False):
+    def __init__(self, name, default=False):
         self.name = name           # name of model
-        self.filename = filename   # filename for output exec code
-        self.includes = includes   # include files needed in exec file
-        # The 'strings' dict holds all the per-CPU symbols we can
-        # substitute into templates etc.
-        self.strings = strings
 
         # This cpu is enabled by default
         self.default = default
@@ -982,7 +1037,6 @@ class CpuModel(object):
         if name in CpuModel.dict:
             raise AttributeError, "CpuModel '%s' already registered" % name
         CpuModel.dict[name] = self
-        CpuModel.list.append(name)
 
 Export('CpuModel')
 
@@ -1006,7 +1060,7 @@ Export('slicc_includes')
 
 # Walk the tree and execute all SConsopts scripts that wil add to the
 # above variables
-if not GetOption('verbose'):
+if GetOption('verbose'):
     print "Reading SConsopts"
 for bdir in [ base_dir ] + extras_dir_list:
     if not isdir(bdir):
@@ -1024,7 +1078,7 @@ sticky_vars.AddVariables(
     EnumVariable('TARGET_ISA', 'Target ISA', 'alpha', all_isa_list),
     ListVariable('CPU_MODELS', 'CPU models',
                  sorted(n for n,m in CpuModel.dict.iteritems() if m.default),
-                 sorted(CpuModel.list)),
+                 sorted(CpuModel.dict.keys())),
     BoolVariable('EFENCE', 'Link with Electric Fence malloc debugger',
                  False),
     BoolVariable('SS_COMPATIBLE_FP',
@@ -1036,13 +1090,15 @@ sticky_vars.AddVariables(
     BoolVariable('USE_POSIX_CLOCK', 'Use POSIX Clocks', have_posix_clock),
     BoolVariable('USE_FENV', 'Use <fenv.h> IEEE mode control', have_fenv),
     BoolVariable('CP_ANNOTATE', 'Enable critical path annotation capability', False),
+    BoolVariable('USE_KVM', 'Enable hardware virtualized (KVM) CPU models', have_kvm),
     EnumVariable('PROTOCOL', 'Coherence protocol for Ruby', 'None',
                   all_protocols),
     )
 
 # These variables get exported to #defines in config/*.hh (see src/SConscript).
 export_vars += ['USE_FENV', 'SS_COMPATIBLE_FP', 'TARGET_ISA', 'CP_ANNOTATE',
-                'USE_POSIX_CLOCK', 'PROTOCOL', 'HAVE_PROTOBUF']
+                'USE_POSIX_CLOCK', 'PROTOCOL', 'HAVE_PROTOBUF',
+                'HAVE_PERF_ATTR_EXCLUDE_HOST']
 
 ###################################################
 #
@@ -1097,6 +1153,14 @@ main.SConscript('ext/gzstream/SConscript',
 main.SConscript('ext/libfdt/SConscript',
                 variant_dir = joinpath(build_root, 'libfdt'))
 
+# fputils build is shared across all configs in the build root.
+main.SConscript('ext/fputils/SConscript',
+                variant_dir = joinpath(build_root, 'fputils'))
+
+# DRAMSim2 build is shared across all configs in the build root.
+main.SConscript('ext/dramsim2/SConscript',
+                variant_dir = joinpath(build_root, 'dramsim2'))
+
 ###################################################
 #
 # This function is used to set up a directory with switching headers
@@ -1104,16 +1168,21 @@ main.SConscript('ext/libfdt/SConscript',
 ###################################################
 
 main['ALL_ISA_LIST'] = all_isa_list
+all_isa_deps = {}
 def make_switching_dir(dname, switch_headers, env):
     # Generate the header.  target[0] is the full path of the output
     # header to generate.  'source' is a dummy variable, since we get the
     # list of ISAs from env['ALL_ISA_LIST'].
     def gen_switch_hdr(target, source, env):
         fname = str(target[0])
-        f = open(fname, 'w')
         isa = env['TARGET_ISA'].lower()
-        print >>f, '#include "%s/%s/%s"' % (dname, isa, basename(fname))
-        f.close()
+        try:
+            f = open(fname, 'w')
+            print >>f, '#include "%s/%s/%s"' % (dname, isa, basename(fname))
+            f.close()
+        except IOError:
+            print "Failed to create %s" % fname
+            raise
 
     # Build SCons Action object. 'varlist' specifies env vars that this
     # action depends on; when env['ALL_ISA_LIST'] changes these actions
@@ -1124,8 +1193,37 @@ def make_switching_dir(dname, switch_headers, env):
     # Instantiate actions for each header
     for hdr in switch_headers:
         env.Command(hdr, [], switch_hdr_action)
+
+    isa_target = Dir('.').up().name.lower().replace('_', '-')
+    env['PHONY_BASE'] = '#'+isa_target
+    all_isa_deps[isa_target] = None
+
 Export('make_switching_dir')
 
+# all-isas -> all-deps -> all-environs -> all_targets
+main.Alias('#all-isas', [])
+main.Alias('#all-deps', '#all-isas')
+
+# Dummy target to ensure all environments are created before telling
+# SCons what to actually make (the command line arguments).  We attach
+# them to the dependence graph after the environments are complete.
+ORIG_BUILD_TARGETS = list(BUILD_TARGETS) # force a copy; gets closure to work.
+def environsComplete(target, source, env):
+    for t in ORIG_BUILD_TARGETS:
+        main.Depends('#all-targets', t)
+
+# Each build/* switching_dir attaches its *-environs target to #all-environs.
+main.Append(BUILDERS = {'CompleteEnvirons' :
+                        Builder(action=MakeAction(environsComplete, None))})
+main.CompleteEnvirons('#all-environs', [])
+
+def doNothing(**ignored): pass
+main.Append(BUILDERS = {'Dummy': Builder(action=MakeAction(doNothing, None))})
+
+# The final target to which all the original targets ultimately get attached.
+main.Dummy('#all-targets', '#all-environs')
+BUILD_TARGETS[:] = ['#all-targets']
+
 ###################################################
 #
 # Define build environments for selected configurations.
@@ -1133,7 +1231,8 @@ Export('make_switching_dir')
 ###################################################
 
 for variant_path in variant_paths:
-    print "Building in", variant_path
+    if not GetOption('silent'):
+        print "Building in", variant_path
 
     # Make a copy of the build-root environment to use for this config.
     env = main.Clone()
@@ -1151,7 +1250,8 @@ for variant_path in variant_paths:
     current_vars_file = joinpath(build_root, 'variables', variant_dir)
     if isfile(current_vars_file):
         sticky_vars.files.append(current_vars_file)
-        print "Using saved variables file %s" % current_vars_file
+        if not GetOption('silent'):
+            print "Using saved variables file %s" % current_vars_file
     else:
         # Build dir-specific variables file doesn't exist.
 
@@ -1203,6 +1303,26 @@ for variant_path in variant_paths:
     if env['EFENCE']:
         env.Append(LIBS=['efence'])
 
+    if env['USE_KVM']:
+        if not have_kvm:
+            print "Warning: Can not enable KVM, host seems to lack KVM support"
+            env['USE_KVM'] = False
+        elif not have_posix_timers:
+            print "Warning: Can not enable KVM, host seems to lack support " \
+                "for POSIX timers"
+            env['USE_KVM'] = False
+        elif not is_isa_kvm_compatible(env['TARGET_ISA']):
+            print "Info: KVM support disabled due to unsupported host and " \
+                "target ISA combination"
+            env['USE_KVM'] = False
+
+    # Warn about missing optional functionality
+    if env['USE_KVM']:
+        if not main['HAVE_PERF_ATTR_EXCLUDE_HOST']:
+            print "Warning: perf_event headers lack support for the " \
+                "exclude_host attribute. KVM instruction counts will " \
+                "be inaccurate."
+
     # Save sticky variable settings back to current variables file
     sticky_vars.Save(current_vars_file, env)
 
@@ -1212,14 +1332,25 @@ for variant_path in variant_paths:
     # The src/SConscript file sets up the build rules in 'env' according
     # to the configured variables.  It returns a list of environments,
     # one for each variant build (debug, opt, etc.)
-    envList = SConscript('src/SConscript', variant_dir = variant_path,
-                         exports = 'env')
-
-    # Set up the regression tests for each build.
-    for e in envList:
-        SConscript('tests/SConscript',
-                   variant_dir = joinpath(variant_path, 'tests', e.Label),
-                   exports = { 'env' : e }, duplicate = False)
+    SConscript('src/SConscript', variant_dir = variant_path, exports = 'env')
+
+def pairwise(iterable):
+    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
+    a, b = itertools.tee(iterable)
+    b.next()
+    return itertools.izip(a, b)
+
+# Create false dependencies so SCons will parse ISAs, establish
+# dependencies, and setup the build Environments serially. Either
+# SCons (likely) and/or our SConscripts (possibly) cannot cope with -j
+# greater than 1. It appears to be standard race condition stuff; it
+# doesn't always fail, but usually, and the behaviors are different.
+# Every time I tried to remove this, builds would fail in some
+# creative new way. So, don't do that. You'll want to, though, because
+# tests/SConscript takes a long time to make its Environments.
+for t1, t2 in pairwise(sorted(all_isa_deps.iterkeys())):
+    main.Depends('#%s-deps'     % t2, '#%s-deps'     % t1)
+    main.Depends('#%s-environs' % t2, '#%s-environs' % t1)
 
 # base help text
 Help('''