diff options
author | Steven Knight <knight@baldmt.com> | 2003-04-23 22:27:58 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2003-04-23 22:27:58 (GMT) |
commit | 9587e1d2dad1c532d86f664f5cbd6266ebd77808 (patch) | |
tree | 71ab8dbc059c0d16de3f5088427e288716d9dd43 | |
parent | 9c4ebd90350becd6ff9b1b4e4049546680c849b6 (diff) | |
download | SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.zip SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.tar.gz SCons-9587e1d2dad1c532d86f664f5cbd6266ebd77808.tar.bz2 |
Add support for MIDL. (Greg Spencer)
-rw-r--r-- | doc/man/scons.1 | 46 | ||||
-rw-r--r-- | src/CHANGES.txt | 11 | ||||
-rw-r--r-- | src/engine/MANIFEST.in | 2 | ||||
-rw-r--r-- | src/engine/SCons/Platform/win32.py | 51 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 103 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/Fortran.py | 72 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDL.py | 43 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/IDLTests.py | 428 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/ScannerTests.py | 178 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/__init__.py | 108 | ||||
-rw-r--r-- | src/engine/SCons/Tool/__init__.py | 2 | ||||
-rw-r--r-- | src/engine/SCons/Tool/midl.py | 70 | ||||
-rw-r--r-- | src/engine/SCons/Tool/mslink.py | 29 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msvc.py | 4 | ||||
-rw-r--r-- | src/engine/SCons/Util.py | 33 | ||||
-rw-r--r-- | src/engine/SCons/UtilTests.py | 12 | ||||
-rw-r--r-- | test/midl.py | 410 |
17 files changed, 1388 insertions, 214 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 91afc37..ffed57a 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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 diff --git a/src/CHANGES.txt b/src/CHANGES.txt index dba2c7c..0e22ea7 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 251b48a..cb26d18 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -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 diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py index d97e61e..4f7b05b 100644 --- a/src/engine/SCons/Platform/win32.py +++ b/src/engine/SCons/Platform/win32.py @@ -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 = '' diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index c06774d..44007f9 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -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) diff --git a/src/engine/SCons/Scanner/Fortran.py b/src/engine/SCons/Scanner/Fortran.py index 10f0836..2e44eb9 100644 --- a/src/engine/SCons/Scanner/Fortran.py +++ b/src/engine/SCons/Scanner/Fortran.py @@ -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 index 0000000..e4ceac8 --- /dev/null +++ b/src/engine/SCons/Scanner/IDL.py @@ -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 index 0000000..09e8427 --- /dev/null +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -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) diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py index d9882a6..8c8744d 100644 --- a/src/engine/SCons/Scanner/ScannerTests.py +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -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) - - diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py index d4ee523..2146ebe 100644 --- a/src/engine/SCons/Scanner/__init__.py +++ b/src/engine/SCons/Scanner/__init__.py @@ -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] diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 27ef3e1..c1e4d01 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -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 index 0000000..e78f76b --- /dev/null +++ b/src/engine/SCons/Tool/midl.py @@ -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') diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py index 5522b0a..4bf8f0e 100644 --- a/src/engine/SCons/Tool/mslink.py +++ b/src/engine/SCons/Tool/mslink.py @@ -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 diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py index eaaec3e..6f0c516 100644 --- a/src/engine/SCons/Tool/msvc.py +++ b/src/engine/SCons/Tool/msvc.py @@ -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: diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 3a9d3b7..a570a71 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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) diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index e0788da..5f0eaa8 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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 index 0000000..59fba57 --- /dev/null +++ b/test/midl.py @@ -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() |