http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Defaults.py
index e7a49b8656722ec5c67cbdf5f2222b7b64707d51..cc2e0a00bdbbe4ab810c8f22fc456758ba77551c 100644 (file)
@@ -10,7 +10,7 @@ from distutils.msvccompiler.
 """
 
 #
-# Copyright (c) 2001, 2002 Steven Knight
+# __COPYRIGHT__
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -37,611 +37,444 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
 import os
+import os.path
+import errno
+import shutil
 import stat
-import string
+import time
 import sys
-import os.path
 
 import SCons.Action
 import SCons.Builder
-import SCons.Errors
-import SCons.Node.Alias
-import SCons.Node.FS
-import SCons.Platform
-import SCons.Scanner.C
-import SCons.Scanner.Fortran
-import SCons.Scanner.Prog
-import SCons.Util
-
-def yaccEmitter(target, source, env, **kw):
-    # Yacc can be configured to emit a .h file as well
-    # as a .c file, if -d is specified on the command line.
-    if len(source) and \
-       os.path.splitext(SCons.Util.to_String(source[0]))[1] in \
-       [ '.y', '.yy'] and \
-       '-d' in string.split(env.subst("$YACCFLAGS")):
-        target.append(os.path.splitext(SCons.Util.to_String(target[0]))[0] + \
-                      '.h')
+import SCons.CacheDir
+import SCons.Environment
+import SCons.PathList
+import SCons.Subst
+import SCons.Tool
+
+# A placeholder for a default Environment (for fetching source files
+# from source code management systems and the like).  This must be
+# initialized later, after the top-level directory is set by the calling
+# interface.
+_default_env = None
+
+# Lazily instantiate the default environment so the overhead of creating
+# it doesn't apply when it's not needed.
+def _fetch_DefaultEnvironment(*args, **kw):
+    """
+    Returns the already-created default construction environment.
+    """
+    global _default_env
+    return _default_env
+
+def DefaultEnvironment(*args, **kw):
+    """
+    Initial public entry point for creating the default construction
+    Environment.
+
+    After creating the environment, we overwrite our name
+    (DefaultEnvironment) with the _fetch_DefaultEnvironment() function,
+    which more efficiently returns the initialized default construction
+    environment without checking for its existence.
+
+    (This function still exists with its _default_check because someone
+    else (*cough* Script/__init__.py *cough*) may keep a reference
+    to this function.  So we can't use the fully functional idiom of
+    having the name originally be a something that *only* creates the
+    construction environment and then overwrites the name.)
+    """
+    global _default_env
+    if not _default_env:
+        import SCons.Util
+        _default_env = SCons.Environment.Environment(*args, **kw)
+        if SCons.Util.md5:
+            _default_env.Decider('MD5')
+        else:
+            _default_env.Decider('timestamp-match')
+        global DefaultEnvironment
+        DefaultEnvironment = _fetch_DefaultEnvironment
+        _default_env._CacheDir_path = None
+    return _default_env
+
+# Emitters for setting the shared attribute on object files,
+# and an action for checking that all of the source files
+# going into a shared library are, in fact, shared.
+def StaticObjectEmitter(target, source, env):
+    for tgt in target:
+        tgt.attributes.shared = None
     return (target, source)
 
-CFile = SCons.Builder.Builder(action = { '.l'    : '$LEXCOM',
-                                         '.y'    : '$YACCCOM',
-                                       },
-                              emitter = yaccEmitter,
-                              suffix = '$CFILESUFFIX')
-
-CXXFile = SCons.Builder.Builder(action = { '.ll' : '$LEXCOM',
-                                           '.yy' : '$YACCCOM',
-                                         },
-                                emitter = yaccEmitter,
-                                suffix = '$CXXFILESUFFIX')
-
-class SharedFlagChecker:
-    """This is a callable class that is used as
-    a build action for all objects, libraries, and programs.
-    Its job is to run before the "real" action that builds the
-    file, to make sure we aren't trying to link shared objects
-    into a static library/program, or static objects into a
-    shared library."""
-
-    def __init__(self, shared):
-        self.shared = shared
-
-    def __call__(self, source, target, env, **kw):
-        if kw.has_key('shared'):
-            raise SCons.Errors.UserError, "The shared= parameter to Library() or Object() no longer works.\nUse SharedObject() or SharedLibrary() instead."
-        for tgt in target:
-            tgt.attributes.shared = self.shared
+def SharedObjectEmitter(target, source, env):
+    for tgt in target:
+        tgt.attributes.shared = 1
+    return (target, source)
 
+def SharedFlagChecker(source, target, env):
+    same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME')
+    if same == '0' or same == '' or same == 'False':
         for src in source:
-            if hasattr(src.attributes, 'shared'):
-                if self.shared and not src.attributes.shared:
-                    raise SCons.Errors.UserError, "Source file: %s is static and is not compatible with shared target: %s" % (src, target[0])
-                elif not self.shared and src.attributes.shared:
-                    raise SCons.Errors.UserError, "Source file: %s is shared and is not compatible with static target: %s" % (src, target[0])
-
-SharedCheck = SharedFlagChecker(1)
-StaticCheck = SharedFlagChecker(0)
-
-CAction = SCons.Action.Action([ StaticCheck, "$CCCOM" ])
-ShCAction = SCons.Action.Action([ SharedCheck, "$SHCCCOM" ])
-CXXAction = SCons.Action.Action([ StaticCheck, "$CXXCOM" ])
-ShCXXAction = SCons.Action.Action([ SharedCheck, "$SHCXXCOM" ])
-F77Action = SCons.Action.Action([ StaticCheck, "$F77COM" ])
-ShF77Action = SCons.Action.Action([ SharedCheck, "$SHF77COM" ])
-F77PPAction = SCons.Action.Action([ StaticCheck, "$F77PPCOM" ])
-ShF77PPAction = SCons.Action.Action([ SharedCheck, "$SHF77PPCOM" ])
-
-if os.path.normcase('.c') == os.path.normcase('.C'):
-    # We're on a case-insensitive system, so .[CF] (upper case)
-    # files should be treated like .[cf] (lower case) files.
-    C_static = CAction
-    C_shared = ShCAction
-    F_static = F77Action
-    F_shared = ShF77Action
-else:
-    # We're on a case-sensitive system, so .C (upper case) files
-    # are C++, and .F (upper case) files get run through the C
-    # preprocessor.
-    C_static = CXXAction
-    C_shared = ShCXXAction
-    F_static = F77PPAction
-    F_shared = ShF77PPAction
-
-StaticObject = SCons.Builder.Builder(action = { ".C"   : C_static,
-                                                ".cc"  : CXXAction,
-                                                ".cpp" : CXXAction,
-                                                ".cxx" : CXXAction,
-                                                ".c++" : CXXAction,
-                                                ".C++" : CXXAction,
-                                                ".c"   : CAction,
-                                                ".f"   : F77Action,
-                                                ".for" : F77Action,
-                                                ".F"   : F_static,
-                                                ".FOR" : F77Action,
-                                                ".fpp" : F77PPAction,
-                                                ".FPP" : F77PPAction },
-                                     prefix = '$OBJPREFIX',
-                                     suffix = '$OBJSUFFIX',
-                                     src_builder = [CFile, CXXFile])
-
-SharedObject = SCons.Builder.Builder(action = { ".C"   : C_shared,
-                                                ".cc"  : ShCXXAction,
-                                                ".cpp" : ShCXXAction,
-                                                ".cxx" : ShCXXAction,
-                                                ".c++" : ShCXXAction,
-                                                ".C++" : ShCXXAction,
-                                                ".c"   : ShCAction,
-                                                ".f"   : ShF77Action,
-                                                ".for" : ShF77Action,
-                                                ".FOR" : ShF77Action,
-                                                ".F"   : F_shared,
-                                                ".fpp" : ShF77PPAction,
-                                                ".FPP" : ShF77PPAction },
-                                     prefix = '$OBJPREFIX',
-                                     suffix = '$OBJSUFFIX',
-                                     src_builder = [CFile, CXXFile])
-
-def win32TempFileMunge(env, cmd_list, for_signature): 
-    """Given a list of command line arguments, see if it is too
-    long to pass to the win32 command line interpreter.  If so,
-    create a temp file, then pass "@tempfile" as the sole argument
-    to the supplied command (which is the first element of cmd_list).
-    Otherwise, just return [cmd_list]."""
-    cmd = env.subst_list(cmd_list)[0]
-    if for_signature or \
-       (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= 2048:
-        return [cmd_list]
+            try:
+                shared = src.attributes.shared
+            except AttributeError:
+                shared = None
+            if not shared:
+                raise SCons.Errors.UserError("Source file: %s is static and is not compatible with shared target: %s" % (src, target[0]))
+
+SharedCheck = SCons.Action.Action(SharedFlagChecker, None)
+
+# Some people were using these variable name before we made
+# SourceFileScanner part of the public interface.  Don't break their
+# SConscript files until we've given them some fair warning and a
+# transition period.
+CScan = SCons.Tool.CScanner
+DScan = SCons.Tool.DScanner
+LaTeXScan = SCons.Tool.LaTeXScanner
+ObjSourceScan = SCons.Tool.SourceFileScanner
+ProgScan = SCons.Tool.ProgramScanner
+
+# These aren't really tool scanners, so they don't quite belong with
+# the rest of those in Tool/__init__.py, but I'm not sure where else
+# they should go.  Leave them here for now.
+import SCons.Scanner.Dir
+DirScanner = SCons.Scanner.Dir.DirScanner()
+DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner()
+
+# Actions for common languages.
+CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR")
+ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR")
+CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR")
+ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR")
+
+ASAction = SCons.Action.Action("$ASCOM", "$ASCOMSTR")
+ASPPAction = SCons.Action.Action("$ASPPCOM", "$ASPPCOMSTR")
+
+LinkAction = SCons.Action.Action("$LINKCOM", "$LINKCOMSTR")
+ShLinkAction = SCons.Action.Action("$SHLINKCOM", "$SHLINKCOMSTR")
+
+LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR")
+
+# Common tasks that we allow users to perform in platform-independent
+# ways by creating ActionFactory instances.
+ActionFactory = SCons.Action.ActionFactory
+
+def get_paths_str(dest):
+    # If dest is a list, we need to manually call str() on each element
+    if SCons.Util.is_List(dest):
+        elem_strs = []
+        for element in dest:
+            elem_strs.append('"' + str(element) + '"')
+        return '[' + ', '.join(elem_strs) + ']'
     else:
-        import tempfile
-        # We do a normpath because mktemp() has what appears to be
-        # a bug in Win32 that will use a forward slash as a path
-        # delimiter.  Win32's link mistakes that for a command line
-        # switch and barfs.
-        tmp = os.path.normpath(tempfile.mktemp())
-        args = map(SCons.Util.quote_spaces, cmd[1:])
-        open(tmp, 'w').write(string.join(args, " ") + "\n")
-        return [ [cmd[0], '@' + tmp],
-                 ['del', tmp] ]
-    
-def win32LinkGenerator(env, target, source, for_signature, **kw):
-    args = [ '$LINK', '$LINKFLAGS', '/OUT:%s' % target[0],
-             '$(', '$_LIBDIRFLAGS', '$)', '$_LIBFLAGS' ]
-    args.extend(map(SCons.Util.to_String, source))
-    return win32TempFileMunge(env, args, for_signature)
-
-ProgScan = SCons.Scanner.Prog.ProgScan()
+        return '"' + str(dest) + '"'
+
+def chmod_func(dest, mode):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    if not SCons.Util.is_List(dest):
+        dest = [dest]
+    for element in dest:
+        os.chmod(str(element), mode)
+
+def chmod_strfunc(dest, mode):
+    return 'Chmod(%s, 0%o)' % (get_paths_str(dest), mode)
+
+Chmod = ActionFactory(chmod_func, chmod_strfunc)
+
+def copy_func(dest, src):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    if SCons.Util.is_List(src) and os.path.isdir(dest):
+        for file in src:
+            shutil.copy2(file, dest)
+        return 0
+    elif os.path.isfile(src):
+        return shutil.copy2(src, dest)
+    else:
+        return shutil.copytree(src, dest, 1)
+
+Copy = ActionFactory(copy_func,
+                     lambda dest, src: 'Copy("%s", "%s")' % (dest, src),
+                     convert=str)
+
+def delete_func(dest, must_exist=0):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    if not SCons.Util.is_List(dest):
+        dest = [dest]
+    for entry in dest:
+        entry = str(entry)
+        if not must_exist and not os.path.exists(entry):
+            continue
+        if not os.path.exists(entry) or os.path.isfile(entry):
+            os.unlink(entry)
+            continue
+        else:
+            shutil.rmtree(entry, 1)
+            continue
 
-Program = SCons.Builder.Builder(action=[ StaticCheck, '$LINKCOM' ],
-                                prefix='$PROGPREFIX',
-                                suffix='$PROGSUFFIX',
-                                src_suffix='$OBJSUFFIX',
-                                src_builder=StaticObject,
-                                scanner = ProgScan)
+def delete_strfunc(dest, must_exist=0):
+    return 'Delete(%s)' % get_paths_str(dest)
 
-def win32LibGenerator(target, source, env, for_signature, no_import_lib=0):
-    listCmd = [ "$SHLINK", "$SHLINKFLAGS" ]
+Delete = ActionFactory(delete_func, delete_strfunc)
 
-    for tgt in target:
-        ext = os.path.splitext(str(tgt))[1]
-        if ext == env.subst("$LIBSUFFIX"):
-            # Put it on the command line as an import library.
-            if no_import_lib:
-                raise SCons.Errors.UserError, "%s: You cannot specify a .lib file as a target of a shared library build if no_import_library is nonzero." % tgt
-            listCmd.append("${WIN32IMPLIBPREFIX}%s" % tgt)
-        else:
-            listCmd.append("${WIN32DLLPREFIX}%s" % tgt)
-
-    listCmd.extend([ '$_LIBDIRFLAGS', '$_LIBFLAGS' ])
-    for src in source:
-        ext = os.path.splitext(str(src))[1]
-        if ext == env.subst("$WIN32DEFSUFFIX"):
-            # Treat this source as a .def file.
-            listCmd.append("${WIN32DEFPREFIX}%s" % src)
+def mkdir_func(dest):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    if not SCons.Util.is_List(dest):
+        dest = [dest]
+    for entry in dest:
+        try:
+            os.makedirs(str(entry))
+        except os.error, e:
+            p = str(entry)
+            if (e[0] == errno.EEXIST or (sys.platform=='win32' and e[0]==183)) \
+                    and os.path.isdir(str(entry)):
+                pass            # not an error if already exists
+            else:
+                raise
+
+Mkdir = ActionFactory(mkdir_func,
+                      lambda dir: 'Mkdir(%s)' % get_paths_str(dir))
+
+def move_func(dest, src):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    SCons.Node.FS.invalidate_node_memos(src)
+    shutil.move(src, dest)
+
+Move = ActionFactory(move_func,
+                     lambda dest, src: 'Move("%s", "%s")' % (dest, src),
+                     convert=str)
+
+def touch_func(dest):
+    SCons.Node.FS.invalidate_node_memos(dest)
+    if not SCons.Util.is_List(dest):
+        dest = [dest]
+    for file in dest:
+        file = str(file)
+        mtime = int(time.time())
+        if os.path.exists(file):
+            atime = os.path.getatime(file)
         else:
-            # Just treat it as a generic source file.
-            listCmd.append(str(src))
-    return win32TempFileMunge(env, listCmd, for_signature)
+            open(file, 'w')
+            atime = mtime
+        os.utime(file, (atime, mtime))
 
-def win32LibEmitter(target, source, env, no_import_lib=0):
-    dll = None
-    for tgt in target:
-        ext = os.path.splitext(str(tgt))[1]
-        if ext == env.subst("$SHLIBSUFFIX"):
-            dll = tgt
-            break
-    if not dll:
-        raise SCons.Errors.UserError, "A shared library should have exactly one target with the suffix: %s" % env.subst("$SHLIBSUFFIX")
-        
-    if env.has_key("WIN32_INSERT_DEF") and \
-       env["WIN32_INSERT_DEF"] and \
-       not '.def' in map(lambda x: os.path.split(str(x))[1],
-                         source):
-
-        # append a def file to the list of sources
-        source.append("%s%s" % (os.path.splitext(str(dll))[0],
-                                env.subst("$WIN32DEFSUFFIX")))
-    if not no_import_lib and \
-       not env.subst("$LIBSUFFIX") in \
-       map(lambda x: os.path.split(str(x))[1], target):
-        # Append an import library to the list of targets.
-        target.append("%s%s%s" % (env.subst("$LIBPREFIX"),
-                                  os.path.splitext(str(dll))[0],
-                                  env.subst("$LIBSUFFIX")))
-    return (target, source)
+Touch = ActionFactory(touch_func,
+                      lambda file: 'Touch(%s)' % get_paths_str(file))
+
+# Internal utility functions
 
-StaticLibrary = SCons.Builder.Builder(action=[ StaticCheck, "$ARCOM" ],
-                                      prefix = '$LIBPREFIX',
-                                      suffix = '$LIBSUFFIX',
-                                      src_suffix = '$OBJSUFFIX',
-                                      src_builder = StaticObject)
-
-SharedLibrary = SCons.Builder.Builder(action=[ SharedCheck, "$SHLINKCOM" ],
-                                      emitter="$LIBEMITTER",
-                                      prefix = '$SHLIBPREFIX',
-                                      suffix = '$SHLIBSUFFIX',
-                                      scanner = ProgScan,
-                                      src_suffix = '$OBJSUFFIX',
-                                      src_builder = SharedObject)
-
-LaTeXAction = SCons.Action.Action('$LATEXCOM')
-
-DVI = SCons.Builder.Builder(action = { '.tex'   : '$TEXCOM',
-                                       '.ltx'   : LaTeXAction,
-                                       '.latex' : LaTeXAction,
-                                     },
-                           # The suffix is not configurable via a
-                           # construction variable like $DVISUFFIX
-                           # because the output file name is
-                           # hard-coded within TeX.
-                            suffix = '.dvi')
-
-PDFLaTeXAction = SCons.Action.Action('$PDFLATEXCOM')
-
-PDF = SCons.Builder.Builder(action = { '.dvi'   : '$PDFCOM',
-                                       '.tex'   : '$PDFTEXCOM',
-                                       '.ltx'   : PDFLaTeXAction,
-                                       '.latex' : PDFLaTeXAction,
-                                     },
-                            prefix = '$PDFPREFIX',
-                            suffix = '$PDFSUFFIX')
-
-PostScript = SCons.Builder.Builder(action = '$PSCOM',
-                                   prefix = '$PSPREFIX',
-                                   suffix = '$PSSUFFIX',
-                                   src_suffix = '.dvi',
-                                   src_builder = DVI)
-
-CScan = SCons.Scanner.C.CScan()
-
-FortranScan = SCons.Scanner.Fortran.FortranScan()
-
-def alias_builder(env, target, source):
-    pass
-
-Alias = SCons.Builder.Builder(action = alias_builder,
-                              target_factory = SCons.Node.Alias.default_ans.Alias,
-                              source_factory = SCons.Node.FS.default_fs.Entry,
-                              multi = 1)
-
-def get_devstudio_versions ():
+def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
     """
-    Get list of devstudio versions from the Windows registry.  Return a
-    list of strings containing version numbers; an exception will be raised
-    if we were unable to access the registry (eg. couldn't import
-    a registry-access module) or the appropriate registry keys weren't
-    found.
+    Creates a new list from 'list' by first interpolating each element
+    in the list using the 'env' dictionary and then calling f on the
+    list, and finally calling _concat_ixes to concatenate 'prefix' and
+    'suffix' onto each element of the list.
     """
+    if not list:
+        return list
 
-    if not SCons.Util.can_read_reg:
-        raise SCons.Errors.InternalError, "No Windows registry module was found"
+    l = f(SCons.PathList.PathList(list).subst_path(env, target, source))
+    if l is not None:
+        list = l
 
-    K = 'Software\\Microsoft\\Devstudio'
-    L = []
-    for base in (SCons.Util.HKEY_CLASSES_ROOT,
-                 SCons.Util.HKEY_LOCAL_MACHINE,
-                 SCons.Util.HKEY_CURRENT_USER,
-                 SCons.Util.HKEY_USERS):
-        try:
-            k = SCons.Util.RegOpenKeyEx(base,K)
-            i = 0
-            while 1:
-                try:
-                    p = SCons.Util.RegEnumKey(k,i)
-                    if p[0] in '123456789' and p not in L:
-                        L.append(p)
-                except SCons.Util.RegError:
-                    break
-                i = i + 1
-        except SCons.Util.RegError:
-            pass
-
-    if not L:
-        raise SCons.Errors.InternalError, "DevStudio was not found."
-
-    L.sort()
-    L.reverse()
-    return L
-
-def get_msvc_path (path, version, platform='x86'):
+    return _concat_ixes(prefix, list, suffix, env)
+
+def _concat_ixes(prefix, list, suffix, env):
     """
-    Get a list of devstudio directories (include, lib or path).  Return
-    a string delimited by ';'. An exception will be raised if unable to
-    access the registry or appropriate registry keys not found.
+    Creates a new list from 'list' by concatenating the 'prefix' and
+    'suffix' arguments onto each element of the list.  A trailing space
+    on 'prefix' or leading space on 'suffix' will cause them to be put
+    into separate list elements rather than being concatenated.
     """
 
-    if not SCons.Util.can_read_reg:
-        raise SCons.Errors.InternalError, "No Windows registry module was found"
-
-    if path=='lib':
-        path= 'Library'
-    path = string.upper(path + ' Dirs')
-    K = ('Software\\Microsoft\\Devstudio\\%s\\' +
-         'Build System\\Components\\Platforms\\Win32 (%s)\\Directories') % \
-        (version,platform)
-    for base in (SCons.Util.HKEY_CLASSES_ROOT,
-                 SCons.Util.HKEY_LOCAL_MACHINE,
-                 SCons.Util.HKEY_CURRENT_USER,
-                 SCons.Util.HKEY_USERS):
-        try:
-            k = SCons.Util.RegOpenKeyEx(base,K)
-            i = 0
-            while 1:
-                try:
-                    (p,v,t) = SCons.Util.RegEnumValue(k,i)
-                    if string.upper(p) == path:
-                        return v
-                    i = i + 1
-                except SCons.Util.RegError:
-                    break
-        except SCons.Util.RegError:
-            pass
-
-    # if we got here, then we didn't find the registry entries:
-    raise SCons.Errors.InternalError, "%s was not found in the registry."%path
-
-def get_msdev_dir(version):
-    """Returns the root directory of the MSDev installation from the
-    registry if it can be found, otherwise we guess."""
-    if SCons.Util.can_read_reg:
-        K = ('Software\\Microsoft\\Devstudio\\%s\\' +
-             'Products\\Microsoft Visual C++') % \
-             version
-        for base in (SCons.Util.HKEY_LOCAL_MACHINE,
-                     SCons.Util.HKEY_CURRENT_USER):
-            try:
-                k = SCons.Util.RegOpenKeyEx(base,K)
-                val, tok = SCons.Util.RegQueryValueEx(k, 'ProductDir')
-                return os.path.split(val)[0]
-            except SCons.Util.RegError:
-                pass
+    result = []
 
-def make_win32_env_from_paths(include, lib, path):
+    # ensure that prefix and suffix are strings
+    prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
+    suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
+
+    for x in list:
+        if isinstance(x, SCons.Node.FS.File):
+            result.append(x)
+            continue
+        x = str(x)
+        if x:
+
+            if prefix:
+                if prefix[-1] == ' ':
+                    result.append(prefix[:-1])
+                elif x[:len(prefix)] != prefix:
+                    x = prefix + x
+
+            result.append(x)
+
+            if suffix:
+                if suffix[0] == ' ':
+                    result.append(suffix[1:])
+                elif x[-len(suffix):] != suffix:
+                    result[-1] = result[-1]+suffix
+
+    return result
+
+def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
     """
-    Build a dictionary of construction variables for a win32 platform.
-    include - include path
-    lib - library path
-    path - executable path
+    This is a wrapper around _concat()/_concat_ixes() that checks for
+    the existence of prefixes or suffixes on list items and strips them
+    where it finds them.  This is used by tools (like the GNU linker)
+    that need to turn something like 'libfoo.a' into '-lfoo'.
     """
-    return {
-        'CC'         : 'cl',
-        'CCFLAGS'    : '/nologo',
-        'CCCOM'      : '$CC $CCFLAGS $CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET',
-        'SHCC'      : '$CC',
-        'SHCCFLAGS' : '$CCFLAGS',
-        'SHCCCOM'    : '$SHCC $SHCCFLAGS $CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET',
-        'CFILESUFFIX' : '.c',
-        'CXX'        : '$CC',
-        'CXXFLAGS'   : '$CCFLAGS',
-        'CXXCOM'     : '$CXX $CXXFLAGS $CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET',
-        'SHCXX'      : '$CXX',
-        'SHCXXFLAGS' : '$CXXFLAGS',
-        'SHCXXCOM'   : '$SHCXX $SHCXXFLAGS $CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET',
-        'CXXFILESUFFIX' : '.cc',
-        'F77'        : 'g77',
-        'F77FLAGS'   : '',
-        'F77COM'     : '$F77 $F77FLAGS $_F77INCFLAGS /c $SOURCES /Fo$TARGET',
-        'F77PPCOM'   : '$F77 $F77FLAGS $CPPFLAGS $_F77INCFLAGS /c $SOURCES /Fo$TARGET',
-        'SHF77'      : '$F77',
-        'SHF77FLAGS' : '$F77FLAGS',
-        'SHF77COM'   : '$SHF77 $SHF77FLAGS $_F77INCFLAGS /c $SOURCES /Fo$TARGET',
-        'SHF77PPCOM' : '$SHF77 $SHF77FLAGS $CPPFLAGS $_F77INCFLAGS /c $SOURCES /Fo$TARGET',
-        'LINK'       : 'link',
-        'LINKFLAGS'  : '/nologo',
-        'LINKCOM'    : SCons.Action.CommandGenerator(win32LinkGenerator),
-        'SHLINK'     : '$LINK',
-        'SHLINKFLAGS': '$LINKFLAGS /dll',
-        'SHLINKCOM'  : SCons.Action.CommandGenerator(win32LibGenerator),
-        'AR'         : 'lib',
-        'ARFLAGS'    : '/nologo',
-        'ARCOM'      : '$AR $ARFLAGS /OUT:$TARGET $SOURCES',
-        'LEX'        : 'lex',
-        'LEXFLAGS'   : '',
-        'LEXCOM'     : '$LEX $LEXFLAGS -t $SOURCES > $TARGET',
-        'YACC'       : 'yacc',
-        'YACCFLAGS'  : '',
-        'YACCCOM'    : '$YACC $YACCFLAGS -o $TARGET $SOURCES',
-        'TEX'        : 'tex',
-        'TEXFLAGS'   : '',
-        'TEXCOM'     : '$TEX $TEXFLAGS $SOURCES',
-        'LATEX'      : 'latex',
-        'LATEXFLAGS' : '',
-        'LATEXCOM'   : '$LATEX $LATEXFLAGS $SOURCES',
-        'DVIPDF'     : 'dvipdf',
-        'DVIPDFFLAGS' : '',
-        'PDFCOM'     : '$DVIPDF $DVIPDFFLAGS $SOURCES $TARGET',
-        'PDFPREFIX'  : '',
-        'PDFSUFFIX'  : '.pdf',
-        'PDFTEX'     : 'pdftex',
-        'PDFTEXFLAGS' : '',
-        'PDFTEXCOM'  : '$PDFTEX $PDFTEXFLAGS $SOURCES $TARGET',
-        'PDFLATEX'   : 'pdflatex',
-        'PDFLATEXFLAGS' : '',
-        'PDFLATEXCOM' : '$PDFLATEX $PDFLATEXFLAGS $SOURCES $TARGET',
-        'DVIPS'      : 'dvips',
-        'DVIPSFLAGS' : '',
-        'PSCOM'      : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES',
-        'PSPREFIX'   : '',
-        'PSSUFFIX'   : '.ps',
-        'BUILDERS'   : { 'Alias'          : Alias,
-                         'CFile'          : CFile,
-                         'CXXFile'        : CXXFile,
-                         'DVI'            : DVI,
-                         'Library'        : StaticLibrary,
-                         'StaticLibrary'  : StaticLibrary,
-                         'SharedLibrary'  : SharedLibrary,
-                         'Object'         : StaticObject,
-                         'StaticObject'   : StaticObject,
-                         'SharedObject'   : SharedObject,
-                         'PDF'            : PDF,
-                         'PostScript'     : PostScript,
-                         'Program'        : Program },
-        'SCANNERS'   : [CScan, FortranScan],
-        'LIBDIRPREFIX'          : '/LIBPATH:',
-        'LIBDIRSUFFIX'          : '',
-        'LIBLINKPREFIX'         : '',
-        'LIBLINKSUFFIX'         : '$LIBSUFFIX',
-        'LIBEMITTER'            : win32LibEmitter,
-        'INCPREFIX'             : '/I',
-        'INCSUFFIX'             : '',
-        'WIN32DEFPREFIX'        : '/def:',
-        'WIN32DEFSUFFIX'        : '.def',
-        'WIN32DLLPREFIX'        : '/out:',
-        'WIN32IMPLIBPREFIX'     : '/implib:',
-        'WIN32_INSERT_DEF'      : 0,
-        'ENV'        : {
-            'INCLUDE'  : include,
-            'LIB'      : lib,
-            'PATH'     : path,
-            },
-        }
-
-def make_win32_env(version):
+    
+    if not itms:
+        return itms
+
+    if not callable(c):
+        env_c = env['_concat']
+        if env_c != _concat and callable(env_c):
+            # There's a custom _concat() method in the construction
+            # environment, and we've allowed people to set that in
+            # the past (see test/custom-concat.py), so preserve the
+            # backwards compatibility.
+            c = env_c
+        else:
+            c = _concat_ixes
+    
+    stripprefixes = list(map(env.subst, SCons.Util.flatten(stripprefixes)))
+    stripsuffixes = list(map(env.subst, SCons.Util.flatten(stripsuffixes)))
+
+    stripped = []
+    for l in SCons.PathList.PathList(itms).subst_path(env, None, None):
+        if isinstance(l, SCons.Node.FS.File):
+            stripped.append(l)
+            continue
+
+        if not SCons.Util.is_String(l):
+            l = str(l)
+
+        for stripprefix in stripprefixes:
+            lsp = len(stripprefix)
+            if l[:lsp] == stripprefix:
+                l = l[lsp:]
+                # Do not strip more than one prefix
+                break
+
+        for stripsuffix in stripsuffixes:
+            lss = len(stripsuffix)
+            if l[-lss:] == stripsuffix:
+                l = l[:-lss]
+                # Do not strip more than one suffix
+                break
+
+        stripped.append(l)
+
+    return c(prefix, stripped, suffix, env)
+
+def processDefines(defs):
+    """process defines, resolving strings, lists, dictionaries, into a list of
+    strings
     """
-    Build a dictionary of construction variables for a win32 platform.
-    ver - the version string of DevStudio to use (e.g. "6.0")
+    if SCons.Util.is_List(defs):
+        l = []
+        for d in defs:
+            if SCons.Util.is_List(d) or isinstance(d, tuple):
+                l.append(str(d[0]) + '=' + str(d[1]))
+            else:
+                l.append(str(d))
+    elif SCons.Util.is_Dict(defs):
+        # The items in a dictionary are stored in random order, but
+        # if the order of the command-line options changes from
+        # invocation to invocation, then the signature of the command
+        # line will change and we'll get random unnecessary rebuilds.
+        # Consequently, we have to sort the keys to ensure a
+        # consistent order...
+        l = []
+        for k,v in sorted(defs.items()):
+            if v is None:
+                l.append(str(k))
+            else:
+                l.append(str(k) + '=' + str(v))
+    else:
+        l = [str(defs)]
+    return l
+
+def _defines(prefix, defs, suffix, env, c=_concat_ixes):
+    """A wrapper around _concat_ixes that turns a list or string
+    into a list of C preprocessor command-line definitions.
     """
-    return make_win32_env_from_paths(get_msvc_path("include", version),
-                                     get_msvc_path("lib", version),
-                                     get_msvc_path("path", version)
-                                     + ";" + os.environ['PATH'])
-
-
-if os.name == 'posix':
-    arcom = '$AR $ARFLAGS $TARGET $SOURCES'
-    ranlib = 'ranlib'
-    if SCons.Util.WhereIs(ranlib):
-        arcom = arcom + '\n$RANLIB $RANLIBFLAGS $TARGET'
-
-    ConstructionEnvironment = {
-        'CC'         : 'cc',
-        'CCFLAGS'    : '',
-        'CCCOM'      : '$CC $CCFLAGS $CPPFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES',
-        'SHCC'       : '$CC',
-        'SHCCFLAGS'  : '$CCFLAGS -fPIC',
-        'SHCCCOM'    : '$SHCC $SHCCFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES',
-        'CFILESUFFIX' : '.c',
-        'CXX'        : 'c++',
-        'CXXFLAGS'   : '$CCFLAGS',
-        'CXXCOM'     : '$CXX $CXXFLAGS $CPPFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES',
-        'CXXFILESUFFIX' : '.cc',
-        'SHCXX'      : '$CXX',
-        'SHCXXFLAGS' : '$CXXFLAGS -fPIC',
-        'SHCXXCOM'   : '$SHCXX $SHCXXFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES',
-        'F77'        : 'g77',
-        'F77FLAGS'   : '',
-        'F77COM'     : '$F77 $F77FLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'F77PPCOM'   : '$F77 $F77FLAGS $CPPFLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'SHF77FLAGS' : '$F77FLAGS -fPIC',
-        'SHF77COM'   : '$F77 $SHF77FLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'SHF77PPCOM' : '$F77 $SHF77FLAGS $CPPFLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'SHF77'      : '$F77',
-        'SHF77FLAGS' : '$F77FLAGS -fPIC',
-        'SHF77COM'   : '$SHF77 $SHF77FLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'SHF77PPCOM' : '$SHF77 $SHF77FLAGS $CPPFLAGS $_F77INCFLAGS -c -o $TARGET $SOURCES',
-        'LINK'       : '$CXX',
-        'LINKFLAGS'  : '',
-        'LINKCOM'    : '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS',
-        'SHLINK'     : '$LINK',
-        'SHLINKFLAGS': '$LINKFLAGS -shared',
-        'SHLINKCOM'  : '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS',
-        'AR'         : 'ar',
-        'ARFLAGS'    : 'r',
-        'RANLIB'     : ranlib,
-        'RANLIBFLAGS' : '',
-        'ARCOM'      : arcom,
-        'LEX'        : 'lex',
-        'LEXFLAGS'   : '',
-        'LEXCOM'     : '$LEX $LEXFLAGS -t $SOURCES > $TARGET',
-        'YACC'       : 'yacc',
-        'YACCFLAGS'  : '',
-        'YACCCOM'    : '$YACC $YACCFLAGS -o $TARGET $SOURCES',
-        'TEX'        : 'tex',
-        'TEXFLAGS'   : '',
-        'TEXCOM'     : '$TEX $TEXFLAGS $SOURCES',
-        'LATEX'      : 'latex',
-        'LATEXFLAGS' : '',
-        'LATEXCOM'   : '$LATEX $LATEXFLAGS $SOURCES',
-        'DVIPDF'     : 'dvipdf',
-        'PDFCOM'     : '$DVIPDF $DVIPDFFLAGS $SOURCES $TARGET',
-        'PDFPREFIX'  : '',
-        'PDFSUFFIX'  : '.pdf',
-        'PDFTEX'     : 'pdftex',
-        'PDFTEXFLAGS' : '',
-        'PDFTEXCOM'  : '$PDFTEX $PDFTEXFLAGS $SOURCES $TARGET',
-        'PDFLATEX'   : 'pdflatex',
-        'PDFLATEXFLAGS' : '',
-        'PDFLATEXCOM' : '$PDFLATEX $PDFLATEXFLAGS $SOURCES $TARGET',
-        'DVIPS'      : 'dvips',
-        'PSCOM'      : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES',
-        'PSPREFIX'   : '',
-        'PSSUFFIX'   : '.ps',
-        'BUILDERS'   : { 'Alias'          : Alias,
-                         'CFile'          : CFile,
-                         'CXXFile'        : CXXFile,
-                         'DVI'            : DVI,
-                         'Library'        : StaticLibrary,
-                         'StaticLibrary'  : StaticLibrary,
-                         'SharedLibrary'  : SharedLibrary,
-                         'Object'         : StaticObject,
-                         'StaticObject'   : StaticObject,
-                         'SharedObject'   : SharedObject,
-                         'PDF'            : PDF,
-                         'PostScript'     : PostScript,
-                         'Program'        : Program },
-        'SCANNERS'   : [CScan, FortranScan],
-        'LIBDIRPREFIX'          : '-L',
-        'LIBDIRSUFFIX'          : '',
-        'LIBLINKPREFIX'         : '-l',
-        'LIBLINKSUFFIX'         : '',
-        'INCPREFIX'             : '-I',
-        'INCSUFFIX'             : '',
-    }
-
-elif os.name == 'nt':
-    versions = None
-    try:
-        versions = get_devstudio_versions()
-        ConstructionEnvironment = make_win32_env(versions[0]) #use highest version
-    except (SCons.Util.RegError, SCons.Errors.InternalError):
-        # Could not get the configured directories from the registry.
-        # However, the configured directories only appear if the user
-        # changes them from the default.  Therefore, we'll see if
-        # we can get the path to the MSDev base installation from
-        # the registry and deduce the default directories.
-        MVSdir = None
-        if versions:
-            MVSdir = get_msdev_dir(versions[0])
-        if MVSdir:
-            MVSVCdir = r'%s\VC98' % MVSdir
-            MVSCommondir = r'%s\Common' % MVSdir
-            try:
-                extra_path = os.pathsep + os.environ['PATH']
-            except KeyError:
-                extra_path = ''
-            ConstructionEnvironment = make_win32_env_from_paths(
-                r'%s\atl\include;%s\mfc\include;%s\include' % (MVSVCdir, MVSVCdir, MVSVCdir),
-                r'%s\mfc\lib;%s\lib' % (MVSVCdir, MVSVCdir),
-                (r'%s\MSDev98\Bin;%s\Bin' % (MVSCommondir, MVSVCdir)) + extra_path)
-        else:
-            # The DevStudio environment variables don't exist,
-            # so just use the variables from the source environment.
-            MVSdir = r'C:\Program Files\Microsoft Visual Studio'
-            MVSVCdir = r'%s\VC98' % MVSdir
-            MVSCommondir = r'%s\Common' % MVSdir
-            try:
-                include_path = os.environ['INCLUDE']
-            except KeyError:
-                include_path = ''
-            try:
-                lib_path = os.environ['LIB']
-            except KeyError:
-                lib_path = ''
-            try:
-                exe_path = os.environ['PATH']
-            except KeyError:
-                exe_path = ''
-            ConstructionEnvironment = make_win32_env_from_paths(
-                include_path,
-                lib_path,
-                exe_path)
+
+    return c(prefix, env.subst_path(processDefines(defs)), suffix, env)
+    
+class NullCmdGenerator:
+    """This is a callable class that can be used in place of other
+    command generators if you don't want them to do anything.
+
+    The __call__ method for this class simply returns the thing
+    you instantiated it with.
+
+    Example usage:
+    env["DO_NOTHING"] = NullCmdGenerator
+    env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
+    """
+
+    def __init__(self, cmd):
+        self.cmd = cmd
+
+    def __call__(self, target, source, env, for_signature=None):
+        return self.cmd
+
+class Variable_Method_Caller:
+    """A class for finding a construction variable on the stack and
+    calling one of its methods.
+
+    We use this to support "construction variables" in our string
+    eval()s that actually stand in for methods--specifically, use
+    of "RDirs" in call to _concat that should actually execute the
+    "TARGET.RDirs" method.  (We used to support this by creating a little
+    "build dictionary" that mapped RDirs to the method, but this got in
+    the way of Memoizing construction environments, because we had to
+    create new environment objects to hold the variables.)
+    """
+    def __init__(self, variable, method):
+        self.variable = variable
+        self.method = method
+    def __call__(self, *args, **kw):
+        try: 1/0
+        except ZeroDivisionError: 
+            # Don't start iterating with the current stack-frame to
+            # prevent creating reference cycles (f_back is safe).
+            frame = sys.exc_info()[2].tb_frame.f_back
+        variable = self.variable
+        while frame:
+            if variable in frame.f_locals:
+                v = frame.f_locals[variable]
+                if v:
+                    method = getattr(v, self.method)
+                    return method(*args, **kw)
+            frame = frame.f_back
+        return None
+
+ConstructionEnvironment = {
+    'BUILDERS'      : {},
+    'SCANNERS'      : [],
+    'CONFIGUREDIR'  : '#/.sconf_temp',
+    'CONFIGURELOG'  : '#/config.log',
+    'CPPSUFFIXES'   : SCons.Tool.CSuffixes,
+    'DSUFFIXES'     : SCons.Tool.DSuffixes,
+    'ENV'           : {},
+    'IDLSUFFIXES'   : SCons.Tool.IDLSuffixes,
+#    'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes, # moved to the TeX tools generate functions
+    '_concat'       : _concat,
+    '_defines'      : _defines,
+    '_stripixes'    : _stripixes,
+    '_LIBFLAGS'     : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
+    '_LIBDIRFLAGS'  : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
+    '_CPPINCFLAGS'  : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
+    '_CPPDEFFLAGS'  : '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
+    'TEMPFILE'      : NullCmdGenerator,
+    'Dir'           : Variable_Method_Caller('TARGET', 'Dir'),
+    'Dirs'          : Variable_Method_Caller('TARGET', 'Dirs'),
+    'File'          : Variable_Method_Caller('TARGET', 'File'),
+    'RDirs'         : Variable_Method_Caller('TARGET', 'RDirs'),
+}
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: