Add support for MIDL. (Greg Spencer)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 23 Apr 2003 22:27:58 +0000 (22:27 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 23 Apr 2003 22:27:58 +0000 (22:27 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@656 fdb21ef1-2011-0410-befe-b5e4ea1792b1

17 files changed:
doc/man/scons.1
src/CHANGES.txt
src/engine/MANIFEST.in
src/engine/SCons/Platform/win32.py
src/engine/SCons/Scanner/C.py
src/engine/SCons/Scanner/Fortran.py
src/engine/SCons/Scanner/IDL.py [new file with mode: 0644]
src/engine/SCons/Scanner/IDLTests.py [new file with mode: 0644]
src/engine/SCons/Scanner/ScannerTests.py
src/engine/SCons/Scanner/__init__.py
src/engine/SCons/Tool/__init__.py
src/engine/SCons/Tool/midl.py [new file with mode: 0644]
src/engine/SCons/Tool/mslink.py
src/engine/SCons/Tool/msvc.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/midl.py [new file with mode: 0644]

index 91afc373cc6c599ae28227fbd8085aea8dd7c1d5..ffed57a6f2281670356100064485a9bb609d29de 100644 (file)
@@ -881,6 +881,7 @@ latex
 lex
 linkloc
 masm
+midl
 mingw
 mslib
 mslink
@@ -1231,6 +1232,23 @@ must have been built for a shared library
 builder).
 .B scons
 will raise an error if there is any mismatch.
+.IP
+On WIN32 systems, specifying "register=1" will cause the dll to be
+registered after it is built using REGSVR32.  The command that is run
+("regsvr32" by default) is determined by $REGSVR construction
+variable, and the flags passed are determined by $REGSVRFLAGS.  By
+default, $REGSVRFLAGS includes "/s", to prevent dialogs from popping
+up and requiring user attention when it is run.  If you change
+$REGSVRFLAGS, be sure to include "/s".  For example,
+
+.ES
+env.SharedLibrary(target = 'bar',
+                  source = ['bar.cxx', 'foo.obj'],
+                  register=1)
+.EE
+
+.IP
+will register "bar.dll" as a COM object when it is done linking it.
 
 .IP Library
 A synonym for the
@@ -1310,6 +1328,19 @@ Example:
 env.Java(target = 'classes', source = 'src')
 .EE
 
+.IP TypeLibrary
+Builds a Windows type library (.tlb) file from and input IDL file
+(.idl).  In addition, it will build the associated inteface stub and
+proxy source files.  It names them according to the base name of the .idl file.
+.IP
+For example,
+
+.ES
+env.TypeLibrary(source="foo.idl")
+.EE
+.IP
+Will create foo.tlb, foo.h, foo_i.c, foo_p.c, and foo_data.c.
+
 .IP DVI
 Builds a .dvi file from a .tex, .ltx or .latex input file.
 The suffix .dvi
@@ -4453,6 +4484,16 @@ not backslashes.
 This is sometimes necessary on Win32 systems
 when a path references a file on other (POSIX) systems.
 
+.IP srcpath
+The directory and file name to the source file linked to this file
+through BuildDir.  If this file isn't linked, it just returns the
+directory and filename unchanged.
+
+.IP srcdir
+The directory containing the source file linked to this file
+through BuildDir.  If this file isn't linked, it just returns the
+directory part of the filename.
+
 .LP
 For example, the specified target will
 expand as follows for the corresponding modifiers:
@@ -4465,6 +4506,11 @@ ${TARGET.file}       => file.x
 ${TARGET.filebase}   => file
 ${TARGET.suffix}     => .x
 ${TARGET.abspath}    => /top/dir/sub/dir/file.x
+
+BuildDir('sub/dir','src')
+$SOURCE              => sub/dir/file.x
+${SOURCE.srcpath}    => src/file.x
+${SOURCE.srcdir}     => src
 .EE
 
 Lastly, a variable name
index dba2c7ce882a16bac6deb8cc2fcc562675ec824a..0e22ea7ace293453376db7546da1b9de8649ea2a 100644 (file)
@@ -88,6 +88,17 @@ RELEASE 0.14 - XXX
 
   - Support the C preprocessor #import statement.
 
+  - Allow the SharedLibrary() Builder on Win32 systems to be able to
+    register a newly-built dll using regsvr32.
+
+  - Add a Builder for Windows type library (.tlb) files from IDL files.
+
+  - Add an IDL scanner.
+
+  - Refactor the Fortran, C and IDL scanners to share common logic.
+
+  - Add .srcpath and .srcdir attributes to $TARGET and $SOURCE.
+
   From Christoph Wiedemann:
 
   - Integrate David Snopek's "Autoscons" code as the new SConf
index 251b48a5cc535b917f53b8a6e182e4230d9157a5..cb26d182b1b72e95f47509dd50c2fedcf780f8df 100644 (file)
@@ -22,6 +22,7 @@ SCons/Platform/posix.py
 SCons/Platform/win32.py
 SCons/Scanner/__init__.py
 SCons/Scanner/C.py
+SCons/Scanner/IDL.py
 SCons/Scanner/Fortran.py
 SCons/Scanner/Prog.py
 SCons/SConf.py
@@ -54,6 +55,7 @@ SCons/Tool/latex.py
 SCons/Tool/lex.py
 SCons/Tool/linkloc.py
 SCons/Tool/masm.py
+SCons/Tool/midl.py
 SCons/Tool/mingw.py
 SCons/Tool/mslib.py
 SCons/Tool/mslink.py
index d97e61e995ed8260dc121497b91cf770ff39beab..4f7b05bdb55cedfbf3bd4f89d4b89dc5ea2a7aed 100644 (file)
@@ -34,7 +34,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
-import popen2
 import string
 import sys
 import tempfile
@@ -126,7 +125,7 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
                 # What went wrong here ??
                 pass
         return ret
-        
+
 def spawn(sh, escape, cmd, args, env):
     if not sh:
         sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
@@ -145,8 +144,54 @@ def spawn(sh, escape, cmd, args, env):
 # the arg.
 escape = lambda x: '"' + x + '"'
 
-def generate(env):
+# Get the windows system directory name
+def get_system_root():
+    # A resonable default if we can't read the registry
+    try:
+        val = os.environ['SYSTEMROOT']
+    except:
+        val = "C:/WINDOWS"
+        pass
 
+    # First see if we can look in the registry...
+    if SCons.Util.can_read_reg:
+        try:
+            # Look for Windows NT system root
+            k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
+                                      'Software\\Microsoft\\Windows NT\\CurrentVersion')
+            val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
+        except SCons.Util.RegError:
+            try:
+                # Okay, try the Windows 9x system root
+                k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
+                                          'Software\\Microsoft\\Windows\\CurrentVersion')
+                val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
+            except:
+                pass
+    return val
+
+# Get the location of the program files directory
+def get_program_files_dir():
+    # Now see if we can look in the registry...
+    val = ''
+    if SCons.Util.can_read_reg:
+        try:
+            # Look for Windows Program Files directory
+            k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
+                                      'Software\\Microsoft\\Windows\\CurrentVersion')
+            val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir')
+        except SCons.Util.RegError:
+            val = ''
+            pass
+
+    if val == '':
+        # A reasonable default if we can't read the registry
+        # (Actually, it's pretty reasonable even if we can :-)
+        val = os.path.join(os.path.dirname(get_system_root()),"Program Files")
+        
+    return val
+
+def generate(env):
     # Attempt to find cmd.exe (for WinNT/2k/XP) or
     # command.com for Win9x
     cmd_interp = ''
index c06774d29e45c3dcd9fd468b9c5186d2f4a87f9c..44007f9f90420d779ab64bf2c33f585acff4f573 100644 (file)
@@ -29,106 +29,17 @@ This module implements the depenency scanner for C/C++ code.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-
-import re
-
-import SCons.Node
 import SCons.Node.FS
 import SCons.Scanner
-import SCons.Util
-import SCons.Warnings
-
-include_re = re.compile('^[ \t]*#[ \t]*(?:include|import)[ \t]+(<|")([^>"]+)(>|")', re.M)
 
 def CScan(fs = SCons.Node.FS.default_fs):
     """Return a prototype Scanner instance for scanning source files
     that use the C pre-processor"""
-    cs = SCons.Scanner.Current(scan, "CScan", fs,
-                               [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
-                                ".h", ".H", ".hxx", ".hpp", ".hh",
-                                ".F", ".fpp", ".FPP"],
-                               path_function = path,
-                               recursive = 1)
+    cs = SCons.Scanner.ClassicCPP("CScan",
+                                  [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
+                                   ".h", ".H", ".hxx", ".hpp", ".hh",
+                                   ".F", ".fpp", ".FPP"],
+                                  "CPPPATH",
+                                  '^[ \t]*#[ \t]*(?:include|import)[ \t]+(<|")([^>"]+)(>|")',
+                                  fs = fs)
     return cs
-
-def path(env, dir, fs = SCons.Node.FS.default_fs):
-    try:
-        cpppath = env['CPPPATH']
-    except KeyError:
-        return ()
-    return tuple(fs.Rsearchall(SCons.Util.mapPaths(cpppath, dir, env),
-                               clazz = SCons.Node.FS.Dir,
-                               must_exist = 0))
-
-def scan(node, env, cpppath = (), fs = SCons.Node.FS.default_fs):
-    """
-    scan(node, Environment) -> [node]
-
-    the C/C++ dependency scanner function
-
-    This function is intentionally simple. There are two rules it
-    follows:
-    
-    1) #include <foo.h> - search for foo.h in CPPPATH followed by the
-        directory 'filename' is in
-    2) #include \"foo.h\" - search for foo.h in the directory 'filename' is
-       in followed by CPPPATH
-
-    These rules approximate the behaviour of most C/C++ compilers.
-
-    This scanner also ignores #ifdef and other preprocessor conditionals, so
-    it may find more depencies than there really are, but it never misses
-    dependencies.
-    """
-
-    node = node.rfile()
-
-    # This function caches the following information:
-    # node.includes - the result of include_re.findall()
-
-    if not node.exists():
-        return []
-
-    # cache the includes list in node so we only scan it once:
-    if node.includes != None:
-        includes = node.includes
-    else:
-        includes = include_re.findall(node.get_contents())
-        node.includes = includes
-
-    nodes = []
-    source_dir = node.get_dir()
-    for include in includes:
-        if include[0] == '"':
-            n = SCons.Node.FS.find_file(include[1],
-                                        (source_dir,) + cpppath,
-                                        fs.File)
-        else:
-            n = SCons.Node.FS.find_file(include[1],
-                                        cpppath + (source_dir,),
-                                        fs.File)
-
-        if not n is None:
-            nodes.append(n)
-        else:
-            SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
-                                "No dependency generated for file: %s (included from: %s) -- file not found" % (include[1], node))
-
-    # Schwartzian transform from the Python FAQ Wizard
-    def st(List, Metric):
-        def pairing(element, M = Metric):
-            return (M(element), element)
-        def stripit(pair):
-            return pair[1]
-        paired = map(pairing, List)
-        paired.sort()
-        return map(stripit, paired)
-    
-    def normalize(node):
-        # We don't want the order of includes to be 
-        # modified by case changes on case insensitive OSes, so
-        # normalize the case of the filename here:
-        # (see test/win32pathmadness.py for a test of this)
-        return SCons.Node.FS._my_normcase(str(node))
-
-    return st(nodes, normalize)
index 10f0836f14005f185b3c666786ebbe78607549f2..2e44eb9dbce002f10b2445d95a9d81ae8471203a 100644 (file)
@@ -38,76 +38,12 @@ import SCons.Scanner
 import SCons.Util
 import SCons.Warnings
 
-include_re = re.compile("INCLUDE[ \t]+'([\\w./\\\\]+)'", re.M)
-
 def FortranScan(fs = SCons.Node.FS.default_fs):
     """Return a prototype Scanner instance for scanning source files
     for Fortran INCLUDE statements"""
-    scanner = SCons.Scanner.Current(scan, "FortranScan", fs,
+    scanner = SCons.Scanner.Classic("FortranScan",
                                     [".f", ".F", ".for", ".FOR"],
-                                    path_function = path,
-                                    recursive = 1)
+                                    "F77PATH",
+                                    "INCLUDE[ \t]+'([\\w./\\\\]+)'",
+                                    fs = fs)
     return scanner
-
-def path(env, dir, fs = SCons.Node.FS.default_fs):
-    try:
-        f77path = env['F77PATH']
-    except KeyError:
-        return ()
-    return tuple(fs.Rsearchall(SCons.Util.mapPaths(f77path, dir, env),
-                               clazz = SCons.Node.FS.Dir,
-                               must_exist = 0))
-
-def scan(node, env, f77path = (), fs = SCons.Node.FS.default_fs):
-    """
-    scan(node, Environment) -> [node]
-
-    the Fortran dependency scanner function
-    """
-
-    node = node.rfile()
-
-    # This function caches the following information:
-    # node.includes - the result of include_re.findall()
-
-    if not node.exists():
-        return []
-
-    # cache the includes list in node so we only scan it once:
-    if node.includes != None:
-        includes = node.includes
-    else:
-        includes = include_re.findall(node.get_contents())
-        node.includes = includes
-
-    source_dir = node.get_dir()
-    
-    nodes = []
-    for include in includes:
-        n = SCons.Node.FS.find_file(include,
-                                    (source_dir,) + f77path,
-                                    fs.File)
-        if not n is None:
-            nodes.append(n)
-        else:
-            SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
-                                "No dependency generated for file: %s (included from: %s) -- file not found" % (include, node))
-
-    # Schwartzian transform from the Python FAQ Wizard
-    def st(List, Metric):
-        def pairing(element, M = Metric):
-            return (M(element), element)
-        def stripit(pair):
-            return pair[1]
-        paired = map(pairing, List)
-        paired.sort()
-        return map(stripit, paired)
-
-    def normalize(node):
-        # We don't want the order of includes to be 
-        # modified by case changes on case insensitive OSes, so
-        # normalize the case of the filename here:
-        # (see test/win32pathmadness.py for a test of this)
-        return SCons.Node.FS._my_normcase(str(node))
-
-    return st(nodes, normalize)
diff --git a/src/engine/SCons/Scanner/IDL.py b/src/engine/SCons/Scanner/IDL.py
new file mode 100644 (file)
index 0000000..e4ceac8
--- /dev/null
@@ -0,0 +1,43 @@
+"""SCons.Scanner.IDL
+
+This module implements the depenency scanner for IDL (Interface
+Definition Language) files.
+
+"""
+
+#
+# Copyright (c) 2001, 2002, 2003 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/Scanner/IDL.py 0.D013 2003/03/31 21:46:41 software"
+
+import SCons.Node.FS
+import SCons.Scanner
+
+def IDLScan(fs = SCons.Node.FS.default_fs):
+    """Return a prototype Scanner instance for scanning IDL source files"""
+    cs = SCons.Scanner.ClassicCPP("IDLScan",
+                                  [".idl", ".IDL"],
+                                  "CPPPATH",
+                                  '^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")',
+                                  fs = fs)
+    return cs
diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py
new file mode 100644 (file)
index 0000000..09e8427
--- /dev/null
@@ -0,0 +1,428 @@
+#
+# Copyright (c) 2001, 2002, 2003 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/Scanner/IDLTests.py 0.D013 2003/03/31 21:46:41 software"
+
+import TestCmd
+import SCons.Scanner.IDL
+import unittest
+import sys
+import os
+import os.path
+import SCons.Node.FS
+import SCons.Warnings
+
+test = TestCmd.TestCmd(workdir = '')
+
+os.chdir(test.workpath(''))
+
+# create some source files and headers:
+
+test.write('t1.idl','''
+#include "f1.idl"
+#include <f2.idl>
+import "f3.idl";
+
+[
+       object,
+       uuid(22995106-CE26-4561-AF1B-C71C6934B840),
+       dual,
+       helpstring("IBarObject Interface"),
+       pointer_default(unique)
+]
+interface IBarObject : IDispatch
+{
+};
+''')
+
+test.write('t2.idl',"""
+#include \"d1/f1.idl\"
+#include <d2/f1.idl>
+#include \"f1.idl\"
+import <f3.idl>;
+
+[
+       object,
+       uuid(22995106-CE26-4561-AF1B-C71C6934B840),
+       dual,
+       helpstring(\"IBarObject Interface\"),
+       pointer_default(unique)
+]
+interface IBarObject : IDispatch
+{
+};
+""")
+
+test.write('t3.idl',"""
+#include \t \"f1.idl\"
+   \t #include \"f2.idl\"
+#   \t include \"f3-test.idl\"
+
+#include \t <d1/f1.idl>
+   \t #include <d1/f2.idl>
+#   \t include <d1/f3-test.idl>
+
+import \t \"d1/f1.idl\"
+   \t import \"d1/f2.idl\"
+
+include \t \"never.idl\"
+   \t include \"never.idl\"
+
+// #include \"never.idl\"
+
+const char* x = \"#include <never.idl>\"
+
+[
+       object,
+       uuid(22995106-CE26-4561-AF1B-C71C6934B840),
+       dual,
+       helpstring(\"IBarObject Interface\"),
+       pointer_default(unique)
+]
+interface IBarObject : IDispatch
+{
+};
+""")
+
+test.subdir('d1', ['d1', 'd2'])
+
+headers = ['f1.idl','f2.idl', 'f3.idl', 'f3-test.idl', 'fi.idl', 'fj.idl', 'never.idl',
+           'd1/f1.idl', 'd1/f2.idl', 'd1/f3-test.idl', 'd1/fi.idl', 'd1/fj.idl',
+           'd1/d2/f1.idl', 'd1/d2/f2.idl', 'd1/d2/f3-test.idl',
+           'd1/d2/f4.idl', 'd1/d2/fi.idl', 'd1/d2/fj.idl']
+
+for h in headers:
+    test.write(h, " ")
+
+test.write('f2.idl',"""
+#include "fi.idl"
+""")
+
+test.write('f3-test.idl',"""
+#include <fj.idl>
+""")
+
+
+test.subdir('include', 'subdir', ['subdir', 'include'])
+
+test.write('t4.idl',"""
+#include \"fa.idl\"
+#include <fb.idl>
+
+[
+       object,
+       uuid(22995106-CE26-4561-AF1B-C71C6934B840),
+       dual,
+       helpstring(\"IBarObject Interface\"),
+       pointer_default(unique)
+]
+interface IBarObject : IDispatch
+{
+};
+""")
+
+test.write(['include', 'fa.idl'], "\n")
+test.write(['include', 'fb.idl'], "\n")
+test.write(['subdir', 'include', 'fa.idl'], "\n")
+test.write(['subdir', 'include', 'fb.idl'], "\n")
+
+test.subdir('repository', ['repository', 'include'],
+            ['repository', 'src' ])
+test.subdir('work', ['work', 'src'])
+
+test.write(['repository', 'include', 'iii.idl'], "\n")
+
+test.write(['work', 'src', 'fff.c'], """
+#include <iii.idl>
+#include <jjj.idl>
+
+int main()
+{
+    return 0;
+}
+""")
+
+test.write([ 'work', 'src', 'aaa.c'], """
+#include "bbb.idl"
+
+int main()
+{
+   return 0;
+}
+""")
+
+test.write([ 'work', 'src', 'bbb.idl'], "\n")
+
+test.write([ 'repository', 'src', 'ccc.c'], """
+#include "ddd.idl"
+
+int main()
+{
+   return 0;
+}
+""")
+
+test.write([ 'repository', 'src', 'ddd.idl'], "\n")
+
+# define some helpers:
+
+class DummyEnvironment:
+    def __init__(self, listCppPath):
+        self.path = listCppPath
+        
+    def Dictionary(self, *args):
+        if not args:
+            return { 'CPPPATH': self.path }
+        elif len(args) == 1 and args[0] == 'CPPPATH':
+            return self.path
+        else:
+            raise KeyError, "Dummy environment only has CPPPATH attribute."
+
+    def subst(self, arg):
+        return arg
+
+    def has_key(self, key):
+        return self.Dictionary().has_key(key)
+
+    def __getitem__(self,key):
+        return self.Dictionary()[key]
+
+    def __setitem__(self,key,value):
+        self.Dictionary()[key] = value
+
+    def __delitem__(self,key):
+        del self.Dictionary()[key]
+
+global my_normpath
+my_normpath = os.path.normpath
+
+if os.path.normcase('foo') == os.path.normcase('FOO'):
+    my_normpath = os.path.normcase
+
+def deps_match(self, deps, headers):
+    scanned = map(my_normpath, map(str, deps))
+    expect = map(my_normpath, headers)
+    self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned))
+
+def make_node(filename, fs=SCons.Node.FS.default_fs):
+    return fs.File(test.workpath(filename))
+
+# define some tests:
+
+class IDLScannerTestCase1(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment([])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+        deps = s(make_node('t1.idl'), env, path)
+        headers = ['f1.idl', 'f2.idl', 'f3.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+
+class IDLScannerTestCase2(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment([test.workpath("d1")])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+        deps = s(make_node('t1.idl'), env, path)
+        headers = ['d1/f2.idl', 'f1.idl', 'f3.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+
+class IDLScannerTestCase3(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment([test.workpath("d1")])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+        deps = s(make_node('t2.idl'), env, path)
+        headers = ['d1/d2/f1.idl', 'd1/f1.idl', 'f1.idl', 'f3.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+
+class IDLScannerTestCase4(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+        deps = s(make_node('t2.idl'), env, path)
+        headers =  ['d1/d2/f1.idl', 'd1/f1.idl', 'f1.idl', 'f3.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+        
+class IDLScannerTestCase5(unittest.TestCase):
+    def runTest(self):
+        env = DummyEnvironment([])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+
+        n = make_node('t3.idl')
+        def my_rexists(s=n):
+            s.rexists_called = 1
+            return s.old_rexists()
+        setattr(n, 'old_rexists', n.rexists)
+        setattr(n, 'rexists', my_rexists)
+
+        deps = s(n, env, path)
+
+        # Make sure rexists() got called on the file node being
+        # scanned, essential for cooperation with BuildDir functionality.
+        assert n.rexists_called
+        
+        headers =  ['d1/f1.idl', 'd1/f1.idl', 'd1/f2.idl', 'd1/f2.idl', 'd1/f3-test.idl',
+                    'f1.idl', 'f2.idl', 'f3-test.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+
+class IDLScannerTestCase6(unittest.TestCase):
+    def runTest(self):
+        env1 = DummyEnvironment([test.workpath("d1")])
+        env2 = DummyEnvironment([test.workpath("d1/d2")])
+        s = SCons.Scanner.IDL.IDLScan()
+        path1 = s.path(env1)
+        path2 = s.path(env2)
+        deps1 = s(make_node('t1.idl'), env1, path1)
+        deps2 = s(make_node('t1.idl'), env2, path2)
+        headers1 =  ['d1/f2.idl', 'f1.idl', 'f3.idl']
+        headers2 =  ['d1/d2/f2.idl', 'f1.idl', 'f3.idl']
+        deps_match(self, deps1, map(test.workpath, headers1))
+        deps_match(self, deps2, map(test.workpath, headers2))
+
+class IDLScannerTestCase7(unittest.TestCase):
+    def runTest(self):
+        fs = SCons.Node.FS.FS(test.workpath(''))
+        env = DummyEnvironment(["include"])
+        s = SCons.Scanner.IDL.IDLScan(fs = fs)
+        path = s.path(env)
+        deps1 = s(fs.File('t4.idl'), env, path)
+        fs.chdir(fs.Dir('subdir'))
+        dir = fs.getcwd()
+        fs.chdir(fs.Dir('..'))
+        path = s.path(env, dir)
+        deps2 = s(fs.File('#t4.idl'), env, path)
+        headers1 =  ['include/fa.idl', 'include/fb.idl']
+        headers2 =  ['subdir/include/fa.idl', 'subdir/include/fb.idl']
+        deps_match(self, deps1, headers1)
+        deps_match(self, deps2, headers2)
+
+class IDLScannerTestCase8(unittest.TestCase):
+    def runTest(self):
+        SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning)
+        class TestOut:
+            def __call__(self, x):
+                self.out = x
+
+        to = TestOut()
+        to.out = None
+        SCons.Warnings._warningOut = to
+        test.write('fa.idl','\n')
+        fs = SCons.Node.FS.FS(test.workpath(''))
+        env = DummyEnvironment([])
+        s = SCons.Scanner.IDL.IDLScan(fs=fs)
+        path = s.path(env)
+        deps = s(fs.File('t4.idl'), env, path)
+
+        # Did we catch the warning associated with not finding fb.idl?
+        assert to.out
+        
+        deps_match(self, deps, [ 'fa.idl' ])
+        test.unlink('fa.idl')
+
+class IDLScannerTestCase9(unittest.TestCase):
+    def runTest(self):
+        fs = SCons.Node.FS.FS(test.workpath(''))
+        fs.chdir(fs.Dir('include'))
+        env = DummyEnvironment([])
+        s = SCons.Scanner.IDL.IDLScan(fs=fs)
+        path = s.path(env)
+        test.write('include/t4.idl', test.read('t4.idl'))
+        deps = s(fs.File('#include/t4.idl'), env, path)
+        fs.chdir(fs.Dir('..'))
+        deps_match(self, deps, [ 'include/fa.idl', 'include/fb.idl' ])
+        test.unlink('include/t4.idl')
+
+class IDLScannerTestCase10(unittest.TestCase):
+    def runTest(self):
+        os.chdir(test.workpath('work'))
+        fs = SCons.Node.FS.FS(test.workpath('work'))
+        fs.Repository(test.workpath('repository'))
+
+        # Create a derived file in a directory that does not exist yet.
+        # This was a bug at one time.
+        f1=fs.File('include2/jjj.idl')
+        f1.builder=1
+        env = DummyEnvironment(['include', 'include2'])
+        s = SCons.Scanner.IDL.IDLScan(fs=fs)
+        path = s.path(env)
+        deps = s(fs.File('src/fff.c'), env, path)
+        deps_match(self, deps, [ test.workpath('repository/include/iii.idl'), 'include2/jjj.idl' ])
+        os.chdir(test.workpath(''))
+
+class IDLScannerTestCase11(unittest.TestCase):
+    def runTest(self):
+        os.chdir(test.workpath('work'))
+        fs = SCons.Node.FS.FS(test.workpath('work'))
+        fs.BuildDir('build1', 'src', 1)
+        fs.BuildDir('build2', 'src', 0)
+        fs.Repository(test.workpath('repository'))
+        env = DummyEnvironment([])
+        s = SCons.Scanner.IDL.IDLScan(fs = fs)
+        path = s.path(env)
+        deps1 = s(fs.File('build1/aaa.c'), env, path)
+        deps_match(self, deps1, [ 'build1/bbb.idl' ])
+        deps2 = s(fs.File('build2/aaa.c'), env, path)
+        deps_match(self, deps2, [ 'src/bbb.idl' ])
+        deps3 = s(fs.File('build1/ccc.c'), env, path)
+        deps_match(self, deps3, [ 'build1/ddd.idl' ])
+        deps4 = s(fs.File('build2/ccc.c'), env, path)
+        deps_match(self, deps4, [ test.workpath('repository/src/ddd.idl') ])
+        os.chdir(test.workpath(''))
+
+class IDLScannerTestCase12(unittest.TestCase):
+    def runTest(self):
+        class SubstEnvironment(DummyEnvironment):
+            def subst(self, arg, test=test):
+                return test.workpath("d1")
+        env = SubstEnvironment(["blah"])
+        s = SCons.Scanner.IDL.IDLScan()
+        path = s.path(env)
+        deps = s(make_node('t1.idl'), env, path)
+        headers = ['d1/f2.idl', 'f1.idl', 'f3.idl']
+        deps_match(self, deps, map(test.workpath, headers))
+        
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(IDLScannerTestCase1())
+    suite.addTest(IDLScannerTestCase2())
+    suite.addTest(IDLScannerTestCase3())
+    suite.addTest(IDLScannerTestCase4())
+    suite.addTest(IDLScannerTestCase5())
+    suite.addTest(IDLScannerTestCase6())
+    suite.addTest(IDLScannerTestCase7())
+    suite.addTest(IDLScannerTestCase8())
+    suite.addTest(IDLScannerTestCase9())
+    suite.addTest(IDLScannerTestCase10())
+    suite.addTest(IDLScannerTestCase11())
+    suite.addTest(IDLScannerTestCase12())
+    return suite
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner()
+    result = runner.run(suite())
+    if not result.wasSuccessful():
+        sys.exit(1)
index d9882a69bd1f64d0b3f909d07d682a5a23c922f5..8c8744dd2b500ef47647e3b765ac883876ab2a83 100644 (file)
@@ -27,7 +27,10 @@ import unittest
 import SCons.Scanner
 import sys
 
-class ScannerTestBase:
+class DummyEnvironment:
+    pass
+
+class ScannerTestCase(unittest.TestCase):
     
     def func(self, filename, env, target, *args):
         self.filename = filename
@@ -39,7 +42,6 @@ class ScannerTestBase:
         
         return self.deps
 
-
     def test(self, scanner, env, filename, deps, *args):
         self.deps = deps
         path = scanner.path(env)
@@ -57,13 +59,8 @@ class ScannerTestBase:
         else:
             self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been")
 
-class DummyEnvironment:
-    pass
-
-
-class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class using the position argument"
-    def runTest(self):
+    def test_positional(self):
+        """Test the Scanner.Base class using positional arguments"""
         s = SCons.Scanner.Base(self.func, "Pos")
         env = DummyEnvironment()
         env.VARIABLE = "var1"
@@ -73,9 +70,8 @@ class ScannerPositionalTestCase(ScannerTestBase, unittest.TestCase):
         env.VARIABLE = "i1"
         self.test(s, env, 'i1.cpp', ['i1.h', 'i1.hpp'])
 
-class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class using the keyword argument"
-    def runTest(self):
+    def test_keywords(self):
+        """Test the Scanner.Base class using keyword arguments"""
         s = SCons.Scanner.Base(function = self.func, name = "Key")
         env = DummyEnvironment()
         env.VARIABLE = "var2"
@@ -85,9 +81,8 @@ class ScannerKeywordTestCase(ScannerTestBase, unittest.TestCase):
         env.VARIABLE = "i2"
         self.test(s, env, 'i2.cpp', ['i2.h', 'i2.hpp'])
 
-class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class using both position and optional arguments"
-    def runTest(self):
+    def test_pos_opt(self):
+        """Test the Scanner.Base class using both position and optional arguments"""
         arg = "this is the argument"
         s = SCons.Scanner.Base(self.func, "PosArg", arg)
         env = DummyEnvironment()
@@ -98,9 +93,8 @@ class ScannerPositionalArgumentTestCase(ScannerTestBase, unittest.TestCase):
         env.VARIABLE = "i3"
         self.test(s, env, 'i3.cpp', ['i3.h', 'i3.hpp'], arg)
 
-class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class using both keyword and optional arguments"
-    def runTest(self):
+    def test_key_opt(self):
+        """Test the Scanner.Base class using both keyword and optional arguments"""
         arg = "this is another argument"
         s = SCons.Scanner.Base(function = self.func, name = "KeyArg",
                                argument = arg)
@@ -112,20 +106,16 @@ class ScannerKeywordArgumentTestCase(ScannerTestBase, unittest.TestCase):
         env.VARIABLE = "i4"
         self.test(s, env, 'i4.cpp', ['i4.h', 'i4.hpp'], arg)
 
-class ScannerHashTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class __hash__() method"
-    def runTest(self):
+    def test_hash(self):
+        """Test the Scanner.Base class __hash__() method"""
         s = SCons.Scanner.Base(self.func, "Hash")
         dict = {}
         dict[s] = 777
         self.failUnless(hash(dict.keys()[0]) == hash(repr(s)),
                         "did not hash Scanner base class as expected")
 
-class ScannerCheckTestCase(unittest.TestCase):
-    "Test the Scanner.Base class scan_check method"
-    def setUp(self):
-        self.checked = {}
-    def runTest(self):
+    def test_scan_check(self):
+        """Test the Scanner.Base class scan_check() method"""
         def my_scan(filename, env, target, *args):
             return []
         def check(node, s=self):
@@ -133,14 +123,14 @@ class ScannerCheckTestCase(unittest.TestCase):
             return 1
         env = DummyEnvironment()
         s = SCons.Scanner.Base(my_scan, "Check", scan_check = check)
+        self.checked = {}
         path = s.path(env)
         scanned = s('x', env, path)
         self.failUnless(self.checked['x'] == 1,
                         "did not call check function")
 
-class ScannerRecursiveTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Base class recursive flag"
-    def runTest(self):
+    def test_recursive(self):
+        """Test the Scanner.Base class recursive flag"""
         s = SCons.Scanner.Base(function = self.func)
         self.failUnless(s.recursive == None,
                         "incorrect default recursive value")
@@ -151,9 +141,9 @@ class ScannerRecursiveTestCase(ScannerTestBase, unittest.TestCase):
         self.failUnless(s.recursive == 1,
                         "did not set recursive flag to 1")
 
-class CurrentTestCase(ScannerTestBase, unittest.TestCase):
-    "Test the Scanner.Current class"
-    def runTest(self):
+class CurrentTestCase(unittest.TestCase):
+    def test_class(self):
+        """Test the Scanner.Current class"""
         class MyNode:
             def __init__(self):
                 self.called_has_builder = None
@@ -199,16 +189,122 @@ class CurrentTestCase(ScannerTestBase, unittest.TestCase):
         self.failUnless(ic.called_current, "did not call current()")
         self.failUnless(ic.func_called, "did not call func()")
 
+class ClassicTestCase(unittest.TestCase):
+    def test_find_include(self):
+        """Test the Scanner.Classic find_include() method"""
+        env = DummyEnvironment()
+        s = SCons.Scanner.Classic("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
+
+        def _find_file(filename, paths, factory):
+            return paths[0]+'/'+filename
+
+        save = SCons.Node.FS.find_file
+        SCons.Node.FS.find_file = _find_file
+
+        try:
+            n, i = s.find_include('aaa', 'foo', ('path',))
+            assert n == 'foo/aaa', n
+            assert i == 'aaa', i
+
+        finally:
+            SCons.Node.FS.find_file = save
+
+    def test_scan(self):
+        """Test the Scanner.Classic scan() method"""
+        class MyNode:
+            def __init__(self, name):
+                self.name = name
+                self._rfile = self
+                self.includes = None
+            def rfile(self):
+                return self._rfile
+            def exists(self):
+                return self._exists
+            def get_contents(self):
+                return self._contents
+            def get_dir(self):
+                return self._dir
+
+        class MyScanner(SCons.Scanner.Classic):
+            def find_include(self, include, source_dir, path):
+                return include, include
+
+        env = DummyEnvironment()
+        s = MyScanner("t", ['.suf'], 'MYPATH', '^my_inc (\S+)')
+
+        # If the node doesn't exist, scanning turns up nothing.
+        n1 = MyNode("n1")
+        n1._exists = None
+        ret = s.scan(n1, env)
+        assert ret == [], ret
+
+        # Verify that it finds includes from the contents.
+        n = MyNode("n")
+        n._exists = 1
+        n._dir = MyNode("n._dir")
+        n._contents = 'my_inc abc\n'
+        ret = s.scan(n, env)
+        assert ret == ['abc'], ret
+
+        # Verify that it uses the cached include info.
+        n._contents = 'my_inc def\n'
+        ret = s.scan(n, env)
+        assert ret == ['abc'], ret
+
+        # Verify that if we wipe the cache, it uses the new contents.
+        n.includes = None
+        ret = s.scan(n, env)
+        assert ret == ['def'], ret
+
+        # Verify that it sorts what it finds.
+        n.includes = ['xyz', 'uvw']
+        ret = s.scan(n, env)
+        assert ret == ['uvw', 'xyz'], ret
+
+        # Verify that we use the rfile() node.
+        nr = MyNode("nr")
+        nr._exists = 1
+        nr._dir = MyNode("nr._dir")
+        nr.includes = ['jkl', 'mno']
+        n._rfile = nr
+        ret = s.scan(n, env)
+        assert ret == ['jkl', 'mno'], ret
+
+class ClassicCPPTestCase(unittest.TestCase):
+    def test_find_include(self):
+        """Test the Scanner.ClassicCPP find_include() method"""
+        env = DummyEnvironment()
+        s = SCons.Scanner.ClassicCPP("Test", [], None, "")
+
+        def _find_file(filename, paths, factory):
+            return paths[0]+'/'+filename
+
+        save = SCons.Node.FS.find_file
+        SCons.Node.FS.find_file = _find_file
+
+        try:
+            n, i = s.find_include(('"', 'aaa'), 'foo', ('path',))
+            assert n == 'foo/aaa', n
+            assert i == 'aaa', i
+
+            n, i = s.find_include(('<', 'bbb'), 'foo', ('path',))
+            assert n == 'path/bbb', n
+            assert i == 'bbb', i
+
+        finally:
+            SCons.Node.FS.find_file = _find_file
+
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(ScannerPositionalTestCase())
-    suite.addTest(ScannerKeywordTestCase())
-    suite.addTest(ScannerPositionalArgumentTestCase())
-    suite.addTest(ScannerKeywordArgumentTestCase())
-    suite.addTest(ScannerHashTestCase())
-    suite.addTest(ScannerCheckTestCase())
-    suite.addTest(ScannerRecursiveTestCase())
-    suite.addTest(CurrentTestCase())
+    tclasses = [
+                 ScannerTestCase,
+                 CurrentTestCase,
+                 ClassicTestCase,
+                 ClassicCPPTestCase,
+               ]
+    for tclass in tclasses:
+        names = unittest.getTestCaseNames(tclass, 'test_')
+        suite.addTests(map(tclass, names))
     return suite
 
 if __name__ == "__main__":
@@ -216,5 +312,3 @@ if __name__ == "__main__":
     result = runner.run(suite())
     if not result.wasSuccessful():
         sys.exit(1)
-
-
index d4ee52323343bad4ed1fb485c0991b462a975889..2146ebebdb0ee6d1af40972218c81a38a6ed65f6 100644 (file)
@@ -29,6 +29,7 @@ The Scanner package for the SCons software construction utility.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import re
 
 import SCons.Node.FS
 import SCons.Sig
@@ -180,3 +181,110 @@ class Current(Base):
             return c
         kw['scan_check'] = current_check
         apply(Base.__init__, (self,) + args, kw)
+
+class Classic(Current):
+    """
+    A Scanner subclass to contain the common logic for classic CPP-style
+    include scanning, but which can be customized to use different
+    regular expressions to find the includes.
+
+    Note that in order for this to work "out of the box" (without
+    overriding the find_include() method), the regular expression passed
+    to the constructor must return the name of the include file in group
+    0.
+    """
+
+    def __init__(self, name, suffixes, path_variable, regex,
+                 fs=SCons.Node.FS.default_fs, *args, **kw):
+
+        self.cre = re.compile(regex, re.M)
+        self.fs = fs
+
+        def _path(env, dir, pv=path_variable, fs=fs):
+            try:
+                path = env[pv]
+            except KeyError:
+                return ()
+            return tuple(fs.Rsearchall(SCons.Util.mapPaths(path, dir, env),
+                                       clazz = SCons.Node.FS.Dir,
+                                       must_exist = 0))
+
+        def _scan(node, env, path, self=self, fs=fs):
+            return self.scan(node, env, path)
+
+        kw['function'] = _scan
+        kw['path_function'] = _path
+        kw['recursive'] = 1
+        kw['skeys'] = suffixes
+
+        apply(Current.__init__, (self,) + args, kw)
+
+    def find_include(self, include, source_dir, path):
+        n = SCons.Node.FS.find_file(include, (source_dir,) + path, self.fs.File)
+        return n, include
+
+    def scan(self, node, env, path=()):
+        node = node.rfile()
+
+        if not node.exists():
+            return []
+
+        # cache the includes list in node so we only scan it once:
+        if node.includes != None:
+            includes = node.includes
+        else:
+            includes = self.cre.findall(node.get_contents())
+            node.includes = includes
+
+        nodes = []
+        source_dir = node.get_dir()
+        for include in includes:
+            n, i = self.find_include(include, source_dir, path)
+
+            if not n is None:
+                nodes.append(n)
+            else:
+                SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
+                                    "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node))
+
+        # Schwartzian transform from the Python FAQ Wizard
+        def st(List, Metric):
+            def pairing(element, M = Metric):
+                return (M(element), element)
+            def stripit(pair):
+                return pair[1]
+            paired = map(pairing, List)
+            paired.sort()
+            return map(stripit, paired)
+
+        def normalize(node):
+            # We don't want the order of includes to be
+            # modified by case changes on case insensitive OSes, so
+            # normalize the case of the filename here:
+            # (see test/win32pathmadness.py for a test of this)
+            return SCons.Node.FS._my_normcase(str(node))
+
+        transformed = st(nodes, normalize)
+        # print "Classic: " + str(node) + " => " + str(map(lambda x: str(x),list(transformed)))
+        return transformed
+
+class ClassicCPP(Classic):
+    """
+    A Classic Scanner subclass which takes into account the type of
+    bracketing used to include the file, and uses classic CPP rules
+    for searching for the files based on the bracketing.
+
+    Note that in order for this to work, the regular expression passed
+    to the constructor must return the leading bracket in group 0, and
+    the contained filename in group 1.
+    """
+    def find_include(self, include, source_dir, path):
+        if include[0] == '"':
+            n = SCons.Node.FS.find_file(include[1],
+                                        (source_dir,) + path,
+                                        self.fs.File)
+        else:
+            n = SCons.Node.FS.find_file(include[1],
+                                        path + (source_dir,),
+                                        self.fs.File)
+        return n, include[1]
index 27ef3e132b68a019b2b8e40e190bc6cddb2b481f..c1e4d01f0ca16d87cda128323e422de57d0ca82f 100644 (file)
@@ -203,7 +203,7 @@ def tool_list(platform, env):
     other_tools = FindAllTools(['BitKeeper', 'CVS',
                                 'dvipdf', 'dvips', 'gs',
                                 'jar', 'javac',
-                                'latex', 'lex',
+                                'latex', 'lex', 'midl',
                                 'pdflatex', 'pdftex', 'Perforce',
                                 'RCS', 'SCCS',
                                 # 'Subversion',
diff --git a/src/engine/SCons/Tool/midl.py b/src/engine/SCons/Tool/midl.py
new file mode 100644 (file)
index 0000000..e78f76b
--- /dev/null
@@ -0,0 +1,70 @@
+"""SCons.Tool.midl
+
+Tool-specific initialization for midl (Microsoft IDL compiler).
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+"""
+
+#
+# Copyright (c) 2001, 2002, 2003 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "src/engine/SCons/Tool/midl.py 0.D013 2003/03/31 21:46:41 software"
+
+import SCons.Defaults
+import SCons.Scanner.IDL
+import os.path
+
+def midl_emitter(target, source, env):
+    """Produces a list of outputs from the MIDL compiler"""
+    base, ext = os.path.splitext(source[0])
+    tlb = base + '.tlb'
+    incl = base + '.h'
+    interface = base + '_i.c'
+    proxy = base + '_p.c'
+    dlldata = base + '_data.c'
+
+    t = [tlb, incl, interface, proxy, dlldata]
+
+    return (t,source)
+
+idl_scanner = SCons.Scanner.IDL.IDLScan()
+
+midl_builder = SCons.Builder.Builder(action='$MIDLCOM',
+                                     src_suffix = '.idl',
+                                     suffix='.tlb',
+                                     emitter = midl_emitter,
+                                     scanner = idl_scanner)
+
+def generate(env):
+    """Add Builders and construction variables for midl to an Environment."""
+
+    env['MIDL']          = 'MIDL.EXE'
+    env['MIDLFLAGS']     = '/nologo'
+    env['MIDLCOM']       = "$MIDL $MIDLFLAGS /tlb ${TARGETS[0]} /h ${TARGETS[1]} /iid ${TARGETS[2]} /proxy ${TARGETS[3]} /dlldata ${TARGETS[4]} $SOURCE 2> NUL"
+    env['BUILDERS']['TypeLibrary'] = midl_builder
+
+def exists(env):
+    return env.Detect('midl')
index 5522b0a06fd77b9d41f6bd72e44eda35fc7a86ae..4bf8f0e4d290950c03c28d08c18323dd94358ce2 100644 (file)
@@ -106,6 +106,10 @@ def win32LibEmitter(target, source, env):
         target.append(env.ReplaceIxes(dll, 
                                       "SHLIBPREFIX", "SHLIBSUFFIX",
                                       "LIBPREFIX", "LIBSUFFIX"))
+        # and .exp file is created if there are exports from a DLL
+        target.append(env.ReplaceIxes(dll, 
+                                      "SHLIBPREFIX", "SHLIBSUFFIX",
+                                      "WIN32EXPPREFIX", "WIN32EXPSUFFIX"))
 
     return (target, source)
 
@@ -118,6 +122,21 @@ def prog_emitter(target, source, env):
         
     return (target,source)
 
+def RegServerFunc(target, source, env):
+    if env.has_key('register') and env['register']:
+        ret = regServerAction([target[0]], [source[0]], env)
+        if ret:
+            raise SCons.Errors.UserError, "Unable to register %s" % target[0]
+        else:
+            print "Registered %s sucessfully" % target[0]
+        return ret
+    return 0
+
+regServerAction = SCons.Action.Action("$REGSVRCOM")
+regServerCheck = SCons.Action.Action(RegServerFunc, None)
+shlibLinkAction = SCons.Action.Action('${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}')
+compositeLinkAction = shlibLinkAction + regServerCheck
+
 def generate(env):
     """Add Builders and construction variables for ar to an Environment."""
     env['BUILDERS']['SharedLibrary'] = SCons.Defaults.SharedLibrary
@@ -127,7 +146,7 @@ def generate(env):
     env['SHLINKFLAGS'] = '$LINKFLAGS /dll'
     env['_SHLINK_TARGETS'] = win32ShlinkTargets
     env['_SHLINK_SOURCES'] = win32ShlinkSources
-    env['SHLINKCOM']   = '${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $( $_LIBDIRFLAGS $) $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}'
+    env['SHLINKCOM']   =  compositeLinkAction
     env['SHLIBEMITTER']= win32LibEmitter
     env['LINK']        = 'link'
     env['LINKFLAGS']   = '/nologo'
@@ -143,6 +162,14 @@ def generate(env):
     env['WIN32DEFSUFFIX']        = '.def'
     env['WIN32_INSERT_DEF']      = 0
 
+    env['WIN32EXPPREFIX']        = ''
+    env['WIN32EXPSUFFIX']        = '.exp'
+
+    env['REGSVRACTION'] = regServerCheck
+    env['REGSVR'] = 'regsvr32'
+    env['REGSVRFLAGS'] = '/s '
+    env['REGSVRCOM'] = '$REGSVR $REGSVRFLAGS $TARGET'
+
     if SCons.Util.can_read_reg:
         include_path, lib_path, exe_path = get_msdev_paths()
         env['ENV']['LIB']            = lib_path
index eaaec3e92c1896d275190358636ddf1985432e4f..6f0c516379aa1c287d398d77aa6a84122605b19d 100644 (file)
@@ -41,6 +41,7 @@ import SCons.Tool
 import SCons.Errors
 import SCons.Builder
 import SCons.Util
+import SCons.Platform.win32
 
 CSuffixes = ['.c', '.C']
 CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']
@@ -172,7 +173,8 @@ def get_msdev_paths(version=None):
         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'
+            progfiles = SCons.Platform.win32.get_program_files_dir()
+            MVSdir = os.path.join(progfiles,r'Microsoft Visual Studio')
             MVSVCdir = r'%s\VC98' % MVSdir
             MVSCommondir = r'%s\Common' % MVSdir
             try:
index 3a9d3b729218c436a45d1db9f22d254ae2d316ca..a570a716daa5b9a297dd7a1f27273e029e23b899 100644 (file)
@@ -40,6 +40,7 @@ import sys
 import types
 import UserDict
 import UserList
+import SCons.Node
 
 try:
     from UserString import UserString
@@ -166,6 +167,31 @@ class PathList(UserList.UserList):
         # available even if this object is a Lister, not a PathList.
         return PathList(map(lambda x: updrive(os.path.abspath(x)), self.data))
 
+    def __getSrcDir(self):
+        """Return the directory containing the linked
+           source file, or this file path, if not linked"""
+        sp = self.__splitPath()[0]
+        rv = []
+        for dir in sp:
+            dn = SCons.Node.FS.default_fs.Dir(str(dir))
+            if (dn == None):
+                rv = rv + ['']
+            else:
+                rv = rv + [str(dn.srcnode())]
+        return PathList(rv)
+
+    def __getSrcPath(self):
+        """Return the path to the linked source file,
+           or this file path, if not linked"""
+        rv = []
+        for dir in self.data:
+            fn = SCons.Node.FS.default_fs.File(str(dir))
+            if (fn == None):
+                rv = rv + ['']
+            else:
+                rv = rv + [str(fn.srcnode())]
+        return PathList(rv)
+
     def __posix(self):
         if os.sep == '/':
             return self
@@ -178,11 +204,14 @@ class PathList(UserList.UserList):
                          "dir" : __getDir,
                          "suffix" : __getSuffix,
                          "abspath" : __getAbsPath,
-                         "posix" : __posix}
+                         "srcpath" : __getSrcPath,
+                         "srcdir" : __getSrcDir,
+                         "posix" : __posix
+                       }
 
     def is_literal(self):
         return 1
-    
+
     def __str__(self):
         return string.join(self.data)
 
index e0788da19cac0c9d35b7a88212ddf189c73d78ab..5f0eaa8c35ef63b17b81f8804379eca2e4b6d3b2 100644 (file)
@@ -163,6 +163,18 @@ class UtilTestCase(unittest.TestCase):
                              target=target, source=source)
         assert newcom == "test foo/bar.exe foo/blah.cpp", newcom
 
+        SCons.Node.FS.default_fs.BuildDir("#baz","#foo")
+
+        newcom = scons_subst("test ${SOURCE.srcdir}", env,
+                             target=target, source=['baz/bar.c'])
+
+        assert newcom == cvt("test foo"), newcom
+
+        newcom = scons_subst("test ${SOURCE.srcpath}", env,
+                             target=target, source=['baz/bar.c'])
+
+        assert newcom == cvt("test foo/bar.c"), newcom
+
         newcom = scons_subst("test $xxx", env)
         assert newcom == cvt("test"), newcom
 
diff --git a/test/midl.py b/test/midl.py
new file mode 100644 (file)
index 0000000..59fba57
--- /dev/null
@@ -0,0 +1,410 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002, 2003 Steven Knight
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "test/midl.py 0.D013 2003/03/31 21:46:41 software"
+
+import TestSCons
+import sys
+import os.path
+import os
+import TestCmd
+import time
+
+test = TestSCons.TestSCons(match = TestCmd.match_re)
+
+if sys.platform != 'win32':
+    test.pass_test()
+
+#####
+# Test the basics
+
+test.write('SConstruct',"""
+import os.path
+import os
+
+build = '#build'
+env = Environment(CCFLAGS = ' -nologo ', CPPPATH='${TARGET.dir}')
+Export('env','build')
+
+BuildDir(build, 'src')
+SConscript(os.path.join(build,'SConscript'))
+""")
+
+test.subdir('src','build')
+
+test.write('src/SConscript',"""
+import os.path
+
+Import('env','build')
+
+local = env.Copy(WIN32_INSERT_DEF = 1)
+
+barsrc = [
+    'BarObject.cpp',
+    'bar.cpp',
+    local.RES('bar.rc', RCFLAGS= '/I\"${SOURCE.srcdir}\"'),
+    ]
+
+local.TypeLibrary('bar.idl')
+
+local.SharedLibrary(target = 'bar.dll',
+                    source = barsrc,
+                    PCH=local.PCH('BarPCH.cpp')[0],
+                    PCHSTOP = 'BarPCH.h',
+                    register=1)
+""")
+
+test.write('src/BarObject.cpp','''
+#include "BarPCH.h"
+#include "Bar.h"
+#include "BarObject.h"
+''')
+
+test.write('src/BarObject.h','''
+#ifndef __BAROBJECT_H_
+#define __BAROBJECT_H_
+
+#include "resource.h"
+
+class ATL_NO_VTABLE CBarObject : 
+       public CComObjectRootEx<CComSingleThreadModel>,
+       public CComCoClass<CBarObject, &CLSID_BarObject>,
+       public IDispatchImpl<IBarObject, &IID_IBarObject, &LIBID_BARLib>
+{
+public:
+       CBarObject()
+       {
+       }
+
+DECLARE_REGISTRY_RESOURCEID(IDR_BAROBJECT)
+
+DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+BEGIN_COM_MAP(CBarObject)
+       COM_INTERFACE_ENTRY(IBarObject)
+       COM_INTERFACE_ENTRY(IDispatch)
+END_COM_MAP()
+
+public:
+};
+
+#endif
+''')
+
+test.write('src/BarObject.rgs',"""
+HKCR
+{
+       Bar.BarObject.1 = s 'BarObject Class'
+       {
+               CLSID = s '{640BE9EC-B79D-4C9E-BB64-95D24854A303}'
+       }
+       Bar.BarObject = s 'BarObject Class'
+       {
+               CLSID = s '{640BE9EC-B79D-4C9E-BB64-95D24854A303}'
+               CurVer = s 'Bar.BarObject.1'
+       }
+       NoRemove CLSID
+       {
+               ForceRemove {640BE9EC-B79D-4C9E-BB64-95D24854A303} = s 'BarObject Class'
+               {
+                       ProgID = s 'Bar.BarObject.1'
+                       VersionIndependentProgID = s 'Bar.BarObject'
+                       ForceRemove 'Programmable'
+                       InprocServer32 = s '%MODULE%'
+                       {
+                               val ThreadingModel = s 'Apartment'
+                       }
+                       'TypeLib' = s '{73E5EA5F-9D45-463F-AA33-9F376AF7B643}'
+               }
+       }
+}
+""")
+
+test.write('src/BarPCH.cpp','''
+#include "BarPCH.h"
+
+#ifdef _ATL_STATIC_REGISTRY
+#include <statreg.h>
+#include <statreg.cpp>
+#endif
+
+#include <atlimpl.cpp>
+''')
+
+test.write('src/BarPCH.h','''
+#ifndef BarPCH_h
+#define BarPCH_h
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+#define STRICT
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0400
+#endif
+#define _ATL_APARTMENT_THREADED
+
+#include <atlbase.h>
+extern CComModule _Module;
+#include <atlcom.h>
+
+#endif
+''')
+
+test.write('src/bar.cpp','''
+#include "BarPCH.h"
+#include "resource.h"
+#include <initguid.h>
+#include "bar.h"
+
+#include "bar_i.c"
+#include "BarObject.h"
+
+CComModule _Module;
+
+BEGIN_OBJECT_MAP(ObjectMap)
+OBJECT_ENTRY(CLSID_BarObject, CBarObject)
+END_OBJECT_MAP()
+
+extern "C"
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+    lpReserved;
+    if (dwReason == DLL_PROCESS_ATTACH)
+    {
+        _Module.Init(ObjectMap, hInstance, &LIBID_BARLib);
+        DisableThreadLibraryCalls(hInstance);
+    }
+    else if (dwReason == DLL_PROCESS_DETACH)
+        _Module.Term();
+    return TRUE;    // ok
+}
+
+STDAPI DllCanUnloadNow(void)
+{
+    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
+}
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
+{
+    return _Module.GetClassObject(rclsid, riid, ppv);
+}
+
+STDAPI DllRegisterServer(void)
+{
+    return _Module.RegisterServer(TRUE);
+}
+
+STDAPI DllUnregisterServer(void)
+{
+    return _Module.UnregisterServer(TRUE);
+}
+''')
+
+test.write('src/bar.def','''
+; bar.def : Declares the module parameters.
+
+LIBRARY      "bar.DLL"
+
+EXPORTS
+       DllCanUnloadNow     @1 PRIVATE
+       DllGetClassObject   @2 PRIVATE
+       DllRegisterServer   @3 PRIVATE
+       DllUnregisterServer     @4 PRIVATE
+''')
+
+test.write('src/bar.idl','''
+import "oaidl.idl";
+import "ocidl.idl";
+       [
+               object,
+               uuid(22995106-CE26-4561-AF1B-C71C6934B840),
+               dual,
+               helpstring("IBarObject Interface"),
+               pointer_default(unique)
+       ]
+       interface IBarObject : IDispatch
+       {
+       };
+
+[
+       uuid(73E5EA5F-9D45-463F-AA33-9F376AF7B643),
+       version(1.0),
+       helpstring("bar 1.0 Type Library")
+]
+library BARLib
+{
+       importlib("stdole32.tlb");
+       importlib("stdole2.tlb");
+
+       [
+               uuid(640BE9EC-B79D-4C9E-BB64-95D24854A303),
+               helpstring("BarObject Class")
+       ]
+       coclass BarObject
+       {
+               [default] interface IBarObject;
+       };
+};
+''')
+
+test.write('src/bar.rc','''
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+
+#include "winres.h"
+
+#undef APSTUDIO_READONLY_SYMBOLS
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+
+1 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "resource.h\\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "#include ""winres.h""\\r\\n"
+    "\\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "1 TYPELIB ""bar.tlb""\\r\\n"
+    "\\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+#ifndef _MAC
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,1
+ PRODUCTVERSION 1,0,0,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904B0"
+        BEGIN
+            VALUE "CompanyName", "\\0"
+            VALUE "FileDescription", "bar Module\\0"
+            VALUE "FileVersion", "1, 0, 0, 1\\0"
+            VALUE "InternalName", "bar\\0"
+            VALUE "LegalCopyright", "Copyright 2003\\0"
+            VALUE "OriginalFilename", "bar.DLL\\0"
+            VALUE "ProductName", "bar Module\\0"
+            VALUE "ProductVersion", "1, 0, 0, 1\\0"
+            VALUE "OLESelfRegister", "\\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+#endif    // !_MAC
+
+IDR_BAROBJECT           REGISTRY DISCARDABLE    "BarObject.rgs"
+
+STRINGTABLE DISCARDABLE 
+BEGIN
+    IDS_PROJNAME            "bar"
+END
+
+#endif    // English (U.S.) resources
+
+#ifndef APSTUDIO_INVOKED
+
+1 TYPELIB "bar.tlb"
+
+#endif    // not APSTUDIO_INVOKED
+''')
+
+test.write('src/resource.h','''
+#define IDS_PROJNAME                    100
+#define IDR_BAROBJECT                   101
+
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        201
+#define _APS_NEXT_COMMAND_VALUE         32768
+#define _APS_NEXT_CONTROL_VALUE         201
+#define _APS_NEXT_SYMED_VALUE           102
+#endif
+#endif
+''')
+
+test.run(arguments=os.path.join('build','bar.dll'))
+
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','BarPCH.pch'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','BarPCH.obj'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.tlb'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.h'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar_i.c'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar_p.c'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar_data.c'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','BarObject.obj'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.obj'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.res'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.dll'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.lib'))))
+test.fail_test(not os.path.exists(test.workpath(os.path.join('build','bar.exp'))))
+
+test.run(arguments='-c .')
+
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','BarPCH.pch'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','BarPCH.obj'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.tlb'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.h'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar_i.c'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar_p.c'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar_data.c'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','BarObject.obj'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.obj'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.res'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.dll'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.lib'))))
+test.fail_test(os.path.exists(test.workpath(os.path.join('build','bar.exp'))))
+
+test.pass_test()