http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Defaults.py
index bf6d59f86ab16e1c41d7cbb397de18ec31a9a8b3..f532354dcf98670a17a8ae58dd26043e59d21725 100644 (file)
@@ -38,18 +38,19 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
+import errno
 import shutil
 import stat
-import string
-import types
+import time
+import sys
 
 import SCons.Action
 import SCons.Builder
+import SCons.CacheDir
 import SCons.Environment
-import SCons.Scanner.C
-import SCons.Scanner.Fortran
-import SCons.Scanner.Prog
-import SCons.Sig
+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
@@ -59,12 +60,40 @@ _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:
-        _default_env = apply(SCons.Environment.Environment, args, kw)
-        _default_env._build_signature = 1
-        _default_env._calc_module = SCons.Sig.default_module
+        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,
@@ -93,127 +122,257 @@ def SharedFlagChecker(source, target, env):
 
 SharedCheck = SCons.Action.Action(SharedFlagChecker, None)
 
-# Scanners and actions for common language(s).
-CScan = SCons.Scanner.C.CScan()
-
-FortranScan = SCons.Scanner.Fortran.FortranScan()
-
-CAction = SCons.Action.Action("$CCCOM")
-ShCAction = SCons.Action.Action("$SHCCCOM")
-CXXAction = SCons.Action.Action("$CXXCOM")
-ShCXXAction = SCons.Action.Action("$SHCXXCOM")
-
-F77Action = SCons.Action.Action("$F77COM")
-ShF77Action = SCons.Action.Action("$SHF77COM")
-F77PPAction = SCons.Action.Action("$F77PPCOM")
-ShF77PPAction = SCons.Action.Action("$SHF77PPCOM")
-
-ASAction = SCons.Action.Action("$ASCOM")
-ASPPAction = SCons.Action.Action("$ASPPCOM")
-
-ProgScan = SCons.Scanner.Prog.ProgScan()
-
-def DVI():
-    """Common function to generate a DVI file Builder."""
-    return SCons.Builder.Builder(action = {},
-                                 # The suffix is not configurable via a
-                                 # construction variable like $DVISUFFIX
-                                 # because the output file name is
-                                 # hard-coded within TeX.
-                                 suffix = '.dvi')
-
-def PDF():
-    """A function for generating the PDF Builder."""
-    return SCons.Builder.Builder(action = { },
-                                 prefix = '$PDFPREFIX',
-                                 suffix = '$PDFSUFFIX')
-
-def copyFunc(dest, source, env):
-    """Install a source file into a destination by copying it (and its
-    permission/mode bits)."""
-    shutil.copy2(source, dest)
-    st = os.stat(source)
-    os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
-    return 0
-
-def _concat(prefix, list, suffix, env, f=lambda x: x):
-    """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 concatenating 'prefix' and 'suffix' onto
-    each element of the list. A trailing space on 'prefix' or leading
-    space on 'suffix' will cause them to be put into seperate list
-    elements rather than being concatenated."""
-    
+# 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:
+        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
+
+def delete_strfunc(dest, must_exist=0):
+    return 'Delete(%s)' % get_paths_str(dest)
+
+Delete = ActionFactory(delete_func, delete_strfunc)
+
+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:
+            open(file, 'w')
+            atime = mtime
+        os.utime(file, (atime, mtime))
+
+Touch = ActionFactory(touch_func,
+                      lambda file: 'Touch(%s)' % get_paths_str(file))
+
+# Internal utility functions
+
+def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
+    """
+    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.is_List(list):
-        list = [list]
+    l = f(SCons.PathList.PathList(list).subst_path(env, target, source))
+    if l is not None:
+        list = l
 
-    def subst(x, env = env):
-        if SCons.Util.is_String(x):
-            return env.subst(x)
-        else:
-            return x
-
-    list = map(subst, list)
+    return _concat_ixes(prefix, list, suffix, env)
 
-    list = f(list)
+def _concat_ixes(prefix, list, suffix, env):
+    """
+    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.
+    """
 
-    ret = []
+    result = []
 
     # ensure that prefix and suffix are strings
-    prefix = str(prefix)
-    suffix = str(suffix)
+    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 and prefix[-1] == ' ':
-            ret.append(prefix[:-1])
-            ret.append(x)
-        else:
-            ret.append(prefix+x)
+            if prefix:
+                if prefix[-1] == ' ':
+                    result.append(prefix[:-1])
+                elif x[:len(prefix)] != prefix:
+                    x = prefix + x
 
-        if suffix and suffix[0] == ' ':
-            ret.append(suffix[1:])
-        else:
-            ret[-1] = ret[-1]+suffix
+            result.append(x)
+
+            if suffix:
+                if suffix[0] == ' ':
+                    result.append(suffix[1:])
+                elif x[-len(suffix):] != suffix:
+                    result[-1] = result[-1]+suffix
 
-    return ret
+    return result
 
-def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
-    """This is a wrapper around _concat() that checks for the existence
-    of prefixes or suffixes on list elements 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'."""
+def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
+    """
+    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'.
+    """
     
+    if not itms:
+        return itms
+
     if not callable(c):
-        if callable(env["_concat"]):
-            c = env["_concat"]
+        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
-    def f(list, sp=stripprefix, ss=stripsuffix):
-        ret = []
-        for l in list:
-            if not SCons.Util.is_String(l):
-                l = str(l)
-            if l[:len(sp)] == sp:
-                l = l[len(sp):]
-            if l[-len(ss):] == ss:
-                l = l[:-len(ss)]
-            ret.append(l)
-        return ret
-    return c(prefix, list, suffix, env, f)
-
-def _defines(prefix, defs, suffix, env, c=_concat):
-    """A wrapper around _concat that turns a list or string
-    into a list of C preprocessor command-line definitions.
+            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
     """
     if SCons.Util.is_List(defs):
         l = []
         for d in defs:
-            if SCons.Util.is_List(d) or type(d) is types.TupleType:
+            if SCons.Util.is_List(d) or isinstance(d, tuple):
                 l.append(str(d[0]) + '=' + str(d[1]))
             else:
                 l.append(str(d))
@@ -225,17 +384,21 @@ def _defines(prefix, defs, suffix, env, c=_concat):
         # Consequently, we have to sort the keys to ensure a
         # consistent order...
         l = []
-        keys = defs.keys()
-        keys.sort()
-        for k in keys:
-            v = defs[k]
+        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 c(prefix, l, suffix, env)
+    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 c(prefix, env.subst_path(processDefines(defs)), suffix, env)
     
 class NullCmdGenerator:
     """This is a callable class that can be used in place of other
@@ -255,22 +418,63 @@ class NullCmdGenerator:
     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'   : [CScan, FortranScan],
-    'PDFPREFIX'  : '',
-    'PDFSUFFIX'  : '.pdf',
-    'PSPREFIX'   : '',
-    'PSSUFFIX'   : '.ps',
-    'ENV'        : {},
-    'INSTALL'    : copyFunc,
-    '_concat'     : _concat,
-    '_defines'    : _defines,
-    '_stripixes'  : _stripixes,
-    '_LIBFLAGS'    : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
-    '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs)} $)',
-    '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)',
-    '_F77INCFLAGS' : '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs)} $)',
-    '_CPPDEFFLAGS' : '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
-    'TEMPFILE'     : NullCmdGenerator
-    }
+    '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: