MSVC.py improvements: new MSVSSolution() Builder, new variables to contro generation...
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 8 Oct 2005 15:31:13 +0000 (15:31 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Sat, 8 Oct 2005 15:31:13 +0000 (15:31 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1363 fdb21ef1-2011-0410-befe-b5e4ea1792b1

bin/restore.sh
doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Tool/msvs.py
src/engine/SCons/Tool/msvs.xml
test/MSVS/vs-6.0-files.py
test/MSVS/vs-7.0-files.py
test/MSVS/vs-7.1-files.py

index d5dc6c180cc77ed902e53bd661f7d4ec31d9e742..d23d1d0c0ba9fb5148fbe99f32eeb5bc7caec9d5 100644 (file)
@@ -14,7 +14,7 @@ fi
 for i in `find $DIRS -name '*.py'`; do
 ed $i <<EOF
 /^__revision__ = /s/= .*/= "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"/p
-/Copyright (c) 2001.*SCons Foundation/s/.*/__COPYRIGHT__/p
+/Copyright (c) 2001.*SCons Foundation.*/s//__COPYRIGHT__/p
 w
 q
 EOF
@@ -23,7 +23,7 @@ done
 for i in `find $DIRS -name '*.txt'`; do
 ed $i <<EOF
 /# [^ ]* 0.96.[CD][0-9]* [0-9\/]* [0-9:]* knight$/s/.*/# __FILE__ __REVISION__ __DATE__ __DEVELOPER__/p
-/Copyright (c) 2001.*SCons Foundation/s/.*/__COPYRIGHT__/p
+/Copyright (c) 2001.*SCons Foundation.*/s//__COPYRIGHT__/p
 w
 q
 EOF
index a0b4694081db2e5edbe6ef716141c44898452f54..d8fe258abc6d6c0dc606f939cf5889ca38636d24 100644 (file)
@@ -31,7 +31,7 @@
 .fi
 .RE
 ..
-.TH SCONS 1 "January 2005"
+.TH SCONS 1 "October 2005"
 .SH NAME
 scons \- a software construction tool
 .SH SYNOPSIS
@@ -1739,55 +1739,101 @@ env.Moc('foo.cpp') # generates foo.moc
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .IP MSVSProject()
 .IP env.MSVSProject()
-Builds Microsoft Visual Studio project files.
+Builds a Microsoft Visual Studio project file,
+and by default builds a solution file as well.
+
 This builds a Visual Studio project file, based on the version of
 Visual Studio that is configured (either the latest installed version,
-or the version set by 
+or the version specified by 
 .B MSVS_VERSION
-in the Environment constructor).
-For VS 6, it will generate 
+in the construction environment).
+For Visual Studio 6, it will generate a
 .B .dsp
-and
-.B .dsw
-files, for VS 7, it will
-generate
+file.
+For Visual Studio 7 (.NET), it will
+generate a
 .B .vcproj
-and
+file.
+
+By default,
+this also generates a solution file
+for the specified project,
+a
+.B .dsw
+file for Visual Studio 6
+or a
 .B .sln
-files.
+file for Visual Studio 7 (.NET).
+This behavior may be disabled by specifying
+.B auto_build_solution=0
+when you call
+.BR MSVSProject (),
+in which case you presumably want to
+build the solution file(s)
+by calling the
+.BR MSVSSolution ()
+Builder (see below).
 
 It takes several lists of filenames to be placed into the project
-file, currently these are limited to 
-.B srcs, incs, localincs, resources,
+file.
+These are currently limited to
+.BR srcs ,
+.BR incs ,
+.BR localincs ,
+.BR resources ,
 and
-.B misc.
-These are pretty self explanatory, but it
-should be noted that the 'srcs' list is NOT added to the $SOURCES
-environment variable.  This is because it represents a list of files
-to be added to the project file, not the source used to build the
-project file (in this case, the 'source' is the SConscript file used
-to call MSVSProject).
-
-In addition to these values (which are all optional, although not
-specifying any of them results in an empty project file), the
-following values must be specified:
-
-target: The name of the target .dsp or .vcproj file.  The correct
-suffix for the version of Visual Studio must be used, but the value
-
-env['MSVSPROJECTSUFFIX']
-
+.BR misc .
+These are pretty self-explanatory, but it should be noted that these
+lists are added to the $SOURCES construction variable as strings,
+NOT as SCons File Nodes.  This is because they represent file
+names to be added to the project file, not the source files used to
+build the project file.
+
+In addition to the above lists of values (which are all optional,
+although not specifying any of them results in an empty project file),
+the following values may be specified:
+
+.BR target :
+The name of the target
+.B .dsp
+or
+.B .vcproj
+file.
+The correct
+suffix for the version of Visual Studio must be used, but the
+.B env['MSVSPROJECTSUFFIX']
+construction variable
 will be defined to the correct value (see example below).
 
-variant: The name of this particular variant.  These are typically
-things like "Debug" or "Release", but really can be anything you want.
-Multiple calls to MSVSProject with different variants are allowed: all
-variants will be added to the project file with their appropriate
+.BR variant :
+The name of this particular variant.
+For Visual Studio 7 projects,
+this can also be a list of variant names.
+These are typically things like "Debug" or "Release", but really
+can be anything you want.
+For Visual Studio 7 projects,
+they may also specify a target platform
+separated from the variant name by a
+.B |
+(vertical pipe)
+character:
+.BR Debug|Xbox .
+The default target platform is Win32.
+Multiple calls to
+.BR MSVSProject ()
+with different variants are allowed;
+all variants will be added to the project file with their appropriate
 build targets and sources.
 
-buildtarget: A list of SCons.Node.FS objects which is returned from
-the command which builds the target.  This is used to tell SCons what
-to build when the 'build' button is pressed inside of the IDE.
+.BR buildtarget :
+An optional string, node, or list of strings or nodes
+(one per build variant), to tell the Visual Studio debugger
+what output target to use in what build variant.
+The number of
+.B buildtarget
+entries must match the number of
+.B variant
+entries.
 
 Example Usage:
 
@@ -1811,6 +1857,59 @@ Example Usage:
                           variant = 'Release')
 .EE
 
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.IP MSVSSolution()
+.IP env.MSVSSolution()
+Builds a Microsoft Visual Studio solution file.
+
+This builds a Visual Studio solution file,
+based on the version of Visual Studio that is configured
+(either the latest installed version,
+or the version specified by 
+.B MSVS_VERSION
+in the construction environment).
+For Visual Studio 6, it will generate a
+.B .dsw
+file.
+For Visual Studio 7 (.NET), it will
+generate a
+.B .sln
+file.
+
+The following values must be specified:
+
+.BR target :
+The name of the target .dsw or .sln file.  The correct
+suffix for the version of Visual Studio must be used, but the value
+.B env['MSVSSOLUTIONSUFFIX']
+will be defined to the correct value (see example below).
+
+.BR variant :
+The name of this particular variant, or a list of variant
+names (the latter is only supported for MSVS 7 solutions). These are
+typically things like "Debug" or "Release", but really can be anything
+you want. For MSVS 7 they may also specify target platform, like this
+"Debug|Xbox". Default platform is Win32.
+
+.BR projects :
+A list of project file names, or Project nodes returned by calls to the
+.BR MSVSProject ()
+Builder,
+to be placed into the solution file.
+(NOTE: Currently only one project is supported per solution.)
+It should be noted that these file names are NOT added to the $SOURCES
+environment variable in form of files, but rather as strings.   This
+is because they represent file names to be added to the solution file,
+not the source files used to build the solution file.
+
+Example Usage:
+
+.ES
+        local.MSVSSolution(target = 'Bar' + env['MSVSSOLUTIONSUFFIX'],
+                          projects = ['bar' + env['MSVSPROJECTSUFFIX']],
+                          variant = 'Release')
+.EE
+
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .IP Object()
 .IP env.Object()
@@ -6448,17 +6547,36 @@ environment variables are set explictly.
 Sets the preferred version of MSVS to use.
 
 SCons will (by default) select the latest version of MSVS
-installed on your machine.  So, if you have version 6 and version 7
-(MSVS .NET) installed, it will prefer version 7.  You can override this by
+installed on your machine.
+So, if you have version 6 and version 7 (MSVS .NET) installed,
+it will prefer version 7.
+You can override this by
 specifying the 
 .B MSVS_VERSION
 variable in the Environment initialization, setting it to the
 appropriate version ('6.0' or '7.0', for example).
 If the given version isn't installed, tool initialization will fail.
 
+.IP MSVSBUILDCOM
+The build command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with any specified
+build targets.
+
+.IP MSVSCLEANCOM
+The clean command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with the -c option
+to remove any specified targets.
+
+.IP MSVSENCODING
+The encoding string placed in
+a generated Microsoft Visual Studio project file.
+The default is encoding
+.BR Windows-1252 .
+
 .IP MSVSPROJECTCOM
-The action used to generate Microsoft Visual Studio
-project and solution files.
+The action used to generate Microsoft Visual Studio project files.
 
 .IP MSVSPROJECTSUFFIX
 The suffix used for Microsoft Visual Studio project (DSP) files.
@@ -6469,6 +6587,45 @@ and
 .B .dsp
 when using earlier versions of Visual Studio.
 
+.IP MSVSREBUILDCOM
+The rebuild command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with any specified
+rebuild targets.
+
+.IP MSVSSCONS
+The SCons used in generated Microsoft Visual Studio project files.
+The default is the version of SCons being
+used to generate the project file.
+
+.IP MSVSSCONSFLAGS
+The SCons flags used in generated Microsoft Visual Studio
+project files.
+
+.IP MSVSSCONSCOM
+The default SCons command used in generated Microsoft Visual Studio
+project files.
+
+.IP MSVSSCONSCRIPT
+The sconscript file
+(that is,
+.B SConstruct
+or
+.B SConscript
+file)
+that will be invoked by Visual Studio
+project files
+(through the
+.B MSVSSCONSCOM
+variable).
+The default is the same sconscript file
+that contains the call to
+.BR MSVSProject ()
+to build the project file.
+
+.IP MSVSSOLUTIONCOM
+The action used to generate Microsoft Visual Studio solution files.
+
 .IP MSVSSOLUTIONSUFFIX
 The suffix used for Microsoft Visual Studio solution (DSW) files.
 The default value is
index 68d1b48aeae196411341e18708d6761be9752e36..0d1cf5f2a1fac7116d820535b96363debba41ae4 100644 (file)
@@ -46,6 +46,11 @@ RELEASE 0.97 - XXX
 
   - Allow SConscript files to modify BUILD_TARGETS.
 
+  - Add a separate MSVSSolution() Builder, with support for the
+    following new construction variables: $MSVSBUILDCOM, $MSVSCLEANCOM,
+    $MSVSENCODING, $MSVSREBUILDCOM, $MSVSSCONS, $MSVSSCONSCOM,
+    $MSVSSCONSFLAGS, $MSVSSCONSCRIPT and $MSVSSOLUTIONCOM.
+
   From Timothee Besset:
 
   - Add support for Objective C/C++ .m and .mm file suffixes (for
index fb4f1b3a5e6effab78f61fdfc013e5edd71e810d..280c456407e37d8814748502aa603b1a4c43d262 100644 (file)
@@ -40,12 +40,11 @@ import pickle
 import re
 import string
 import sys
-import types
 
 import SCons.Builder
 import SCons.Node.FS
 import SCons.Platform.win32
-import SCons.Script
+import SCons.Script.SConscript
 import SCons.Util
 import SCons.Warnings
 
@@ -86,7 +85,6 @@ def _generateGUID(slnfile, name):
 # the MSVS Project file invoke SCons the same way that scons.bat does,
 # which works regardless of how we were invoked.
 exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-__VERSION__'), join(sys.prefix, 'scons-__VERSION__'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()"
-exec_script_main_xml = string.replace(exec_script_main, "'", "&apos;")
 
 # The string for the Python executable we tell the Project file to use
 # is either sys.executable or, if an external PYTHON_ROOT environment
@@ -97,52 +95,109 @@ try:
 except KeyError:
     python_executable = sys.executable
 else:
-    python_executable = os.path.join('$(PYTHON_ROOT)',
+    python_executable = os.path.join('$$(PYTHON_ROOT)',
                                      os.path.split(sys.executable)[1])
 
 class Config:
     pass
 
+def splitFully(path):
+    dir, base = os.path.split(path)
+    if dir and dir != '' and dir != path:
+        return splitFully(dir)+[base]
+    if base == '':
+        return []
+    return [base]
+
+def makeHierarchy(sources):
+    '''Break a list of files into a hierarchy; for each value, if it is a string,
+       then it is a file.  If it is a dictionary, it is a folder.  The string is
+       the original path of the file.'''
+
+    hierarchy = {}
+    for file in sources:
+        path = splitFully(file)
+        if len(path):
+            dict = hierarchy
+            for part in path[:-1]:
+                if not dict.has_key(part):
+                    dict[part] = {}
+                dict = dict[part]
+            dict[path[-1]] = file
+        #else:
+        #    print 'Warning: failed to decompose path for '+str(file)
+    return hierarchy    
+
 class _DSPGenerator:
     """ Base class for DSP generators """
+
+    srcargs = [
+        'srcs',
+        'incs',
+        'localincs',
+        'resources',
+        'misc']
+
     def __init__(self, dspfile, source, env):
-        if type(dspfile) == types.StringType:
+        if SCons.Util.is_String(dspfile):
             self.dspfile = os.path.abspath(dspfile)
         else:
             self.dspfile = dspfile.get_abspath()
 
-        try:
-            self.conspath = source[0].attributes.sconstruct.get_abspath()
-        except KeyError:
-            raise SCons.Errors.InternalError, \
-                  "Unable to determine where the SConstruct is"
-
-        self.config = Config()
-        if env.has_key('variant'):
-            self.config.variant = env['variant'].capitalize()
-        else:
+        if not env.has_key('variant'):
             raise SCons.Errors.InternalError, \
                   "You must specify a 'variant' argument (i.e. 'Debug' or " +\
                   "'Release') to create an MSVSProject."
-
-        if env.has_key('buildtarget'):
-            if type(env['buildtarget']) == types.StringType:
-                self.config.buildtarget = os.path.abspath(env['buildtarget'])
-            elif type(env['buildtarget']) == types.ListType:
-                self.config.buildtarget = env['buildtarget'][0].get_abspath()
-            else:
-                self.config.buildtarget = env['buildtarget'].get_abspath()
+        elif SCons.Util.is_String(env['variant']):
+            variants = [env['variant']]
+        elif SCons.Util.is_List(env['variant']):
+            variants = env['variant']
+
+        if not env.has_key('buildtarget') or env['buildtarget'] == None:
+            buildtarget = ['']
+        elif SCons.Util.is_String(env['buildtarget']):
+            buildtarget = [env['buildtarget']]
+        elif SCons.Util.is_List(env['buildtarget']):
+            if len(env['buildtarget']) != len(variants):
+                raise SCons.Errors.InternalError, \
+                    "Sizes of 'buildtarget' and 'variant' lists must be the same."
+            buildtarget = []
+            for bt in env['buildtarget']:
+                if SCons.Util.is_String(bt):
+                    buildtarget.append(bt)
+                else:
+                    buildtarget.append(bt.get_abspath())
         else:
-            raise SCons.Errors.InternalError, \
-                  "You must specify a target 'buildtarget' file argument (such as the target" +\
-                  " executable) to create an MSVSProject."
-
-        self.config.outdir = os.path.dirname(self.config.buildtarget)
-
-        if type(source[0]) == types.StringType:
-            self.source = os.path.abspath(source[0])
+            buildtarget = [env['buildtarget'].get_abspath()]
+        if len(buildtarget) == 1:
+            bt = buildtarget[0]
+            buildtarget = []
+            for v in variants:
+                buildtarget.append(bt)
+
+        if not env.has_key('outdir') or env['outdir'] == None:
+            outdir = ['']
+        elif SCons.Util.is_String(env['outdir']):
+            outdir = [env['outdir']]
+        elif SCons.Util.is_List(env['outdir']):
+            if len(env['outdir']) != len(variants):
+                raise SCons.Errors.InternalError, \
+                    "Sizes of 'outdir' and 'variant' lists must be the same."
+            outdir = []
+            for s in env['outdir']:
+                if SCons.Util.is_String(s):
+                    outdir.append(s)
+                else:
+                    outdir.append(s.get_abspath())
         else:
-            self.source = source[0].get_abspath()
+            outdir = [env['outdir'].get_abspath()]
+        if len(outdir) == 1:
+            s = outdir[0]
+            outdir = []
+            for v in variants:
+                outdir.append(s)
+
+       self.sconscript = env['MSVSSCONSCRIPT']
 
         self.env = env
 
@@ -151,34 +206,29 @@ class _DSPGenerator:
         else:
             self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
 
-        print "Adding '" + self.name + ' - ' + self.config.variant + "' to Visual Studio Project '" + str(dspfile) + "'"
-
         sourcenames = [
-            ' Source Files',
+            'Source Files',
             'Header Files',
             'Local Headers',
             'Resource Files',
             'Other Files']
 
-        srcargs = [
-            'srcs',
-            'incs',
-            'localincs',
-            'resources',
-            'misc']
-
         self.sources = {}
         for n in sourcenames:
             self.sources[n] = []
 
         self.configs = {}
 
-        if os.path.exists(self.dspfile):
+        self.nokeep = 0
+        if env.has_key('nokeep') and env['variant'] != 0:
+            self.nokeep = 1
+
+        if self.nokeep == 0 and os.path.exists(self.dspfile):
             self.Parse()
 
-        for t in zip(sourcenames,srcargs):
+        for t in zip(sourcenames,self.srcargs):
             if self.env.has_key(t[1]):
-                if type(self.env[t[1]]) == types.ListType:
+                if SCons.Util.is_List(self.env[t[1]]):
                     for i in self.env[t[1]]:
                         if not i in self.sources[t[0]]:
                             self.sources[t[0]].append(i)
@@ -187,9 +237,32 @@ class _DSPGenerator:
                         self.sources[t[0]].append(self.env[t[1]])
 
         for n in sourcenames:
-            self.sources[n].sort()
+            self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
+
+        def AddConfig(variant, buildtarget, outdir):
+            config = Config()
+            config.buildtarget = buildtarget
+            config.outdir = outdir
 
-        self.configs[self.config.variant] = self.config
+            match = re.match('(.*)\|(.*)', variant)
+            if match:
+                config.variant = match.group(1)
+                config.platform = match.group(2)
+            else:
+                config.variant = variant
+                config.platform = 'Win32';
+
+            self.configs[variant] = config
+            print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
+
+        for i in range(len(variants)):
+            AddConfig(variants[i], buildtarget[i], outdir[i])
+
+        self.platforms = []
+        for key in self.configs.keys():
+            platform = self.configs[key].platform
+            if not platform in self.platforms:
+                self.platforms.append(platform)
 
     def Build(self):
         pass
@@ -253,6 +326,10 @@ class _GenerateV6DSP(_DSPGenerator):
             else:
                 self.file.write('\n!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
 
+            env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET')
+            if not env_has_buildtarget:
+                self.env['MSVSBUILDTARGET'] = buildtarget
+
             # have to write this twice, once with the BASE settings, and once without
             for base in ("BASE ",""):
                 self.file.write('# PROP %sUse_MFC 0\n'
@@ -263,16 +340,17 @@ class _GenerateV6DSP(_DSPGenerator):
                     self.file.write('1\n')
                 self.file.write('# PROP %sOutput_Dir "%s"\n'
                                 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
-                (d,c) = os.path.split(str(self.conspath))
-                cmd = 'echo Starting SCons && "%s" -c "%s" -C %s -f %s %s'
-                cmd = cmd % (python_executable, exec_script_main, d, c, buildtarget)
-                self.file.write('# PROP %sCmd_Line "%s"\n'
+                cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
+                self.file.write('# PROP %sCmd_Line "%s"\n' 
                                 '# PROP %sRebuild_Opt "-c && %s"\n'
                                 '# PROP %sTarget_File "%s"\n'
                                 '# PROP %sBsc_Name ""\n'
                                 '# PROP %sTarget_Dir ""\n'\
                                 %(base,cmd,base,cmd,base,buildtarget,base,base))
 
+            if not env_has_buildtarget:
+                del self.env['MSVSBUILDTARGET']
+
         self.file.write('\n!ENDIF\n\n'
                         '# Begin Target\n\n')
         for kind in confkeys:
@@ -290,23 +368,24 @@ class _GenerateV6DSP(_DSPGenerator):
         self.file.write('# End Target\n'
                         '# End Project\n')
 
-        # now we pickle some data and add it to the file -- MSDEV will ignore it.
-        pdata = pickle.dumps(self.configs,1)
-        pdata = base64.encodestring(pdata)
-        self.file.write(pdata + '\n')
-        pdata = pickle.dumps(self.sources,1)
-        pdata = base64.encodestring(pdata)
-        self.file.write(pdata + '\n')
+        if self.nokeep == 0:
+            # now we pickle some data and add it to the file -- MSDEV will ignore it.
+            pdata = pickle.dumps(self.configs,1)
+            pdata = base64.encodestring(pdata)
+            self.file.write(pdata + '\n')
+            pdata = pickle.dumps(self.sources,1)
+            pdata = base64.encodestring(pdata)
+            self.file.write(pdata + '\n')
 
     def PrintSourceFiles(self):
-        categories = {' Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
+        categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
                       'Header Files': 'h|hpp|hxx|hm|inl',
                       'Local Headers': 'h|hpp|hxx|hm|inl',
                       'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
                       'Other Files': ''}
 
         cats = categories.keys()
-        cats.sort()
+        cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
         for kind in cats:
             if not self.sources[kind]:
                 continue # skip empty groups
@@ -322,9 +401,9 @@ class _GenerateV6DSP(_DSPGenerator):
                                 '# End Source File\n')
             self.file.write('# End Group\n')
 
-        # add the Conscript file outside of the groups
+        # add the SConscript file outside of the groups
         self.file.write('# Begin Source File\n\n'
-                        'SOURCE="' + str(self.source) + '"\n'
+                        'SOURCE="' + str(self.sconscript) + '"\n'
                         '# End Source File\n')
 
     def Parse(self):
@@ -386,7 +465,7 @@ class _GenerateV6DSP(_DSPGenerator):
             self.file.close()
 
 V7DSPHeader = """\
-<?xml version="1.0" encoding = "Windows-1252"?>
+<?xml version="1.0" encoding = "%(encoding)s"?>
 <VisualStudioProject
 \tProjectType="Visual C++"
 \tVersion="%(versionstr)s"
@@ -394,15 +473,11 @@ V7DSPHeader = """\
 \tSccProjectName=""
 \tSccLocalPath=""
 \tKeyword="MakeFileProj">
-\t<Platforms>
-\t\t<Platform
-\t\t\tName="Win32"/>
-\t</Platforms>
 """
 
 V7DSPConfiguration = """\
 \t\t<Configuration
-\t\t\tName="%(capitalized_kind)s|Win32"
+\t\t\tName="%(variant)s|Win32"
 \t\t\tOutputDirectory="%(outdir)s"
 \t\t\tIntermediateDirectory="%(outdir)s"
 \t\t\tConfigurationType="0"
@@ -410,9 +485,9 @@ V7DSPConfiguration = """\
 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
 \t\t\t<Tool
 \t\t\t\tName="VCNMakeTool"
-\t\t\t\tBuildCommandLine="%(cmd)s"
+\t\t\t\tBuildCommandLine="%(buildcmd)s"
 \t\t\t\tCleanCommandLine="%(cleancmd)s"
-\t\t\t\tRebuildCommandLine="%(cmd)s"
+\t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
 \t\t\t\tOutput="%(buildtarget)s"/>
 \t\t</Configuration>
 """
@@ -428,7 +503,18 @@ class _GenerateV7DSP(_DSPGenerator):
             self.versionstr = '7.10'
 
     def PrintHeader(self):
-        self.file.write(V7DSPHeader % self.__dict__)
+        versionstr = self.versionstr
+        name = self.name
+        encoding = self.env.subst('$MSVSENCODING')
+
+        self.file.write(V7DSPHeader % locals())
+
+        self.file.write('\t<Platforms>\n')
+        for platform in self.platforms:
+            self.file.write(
+                        '\t\t<Platform\n'
+                        '\t\t\tName="%s"/>\n' % platform)
+        self.file.write('\t</Platforms>\n')
 
     def PrintProject(self):
         self.file.write('\t<Configurations>\n')
@@ -436,39 +522,52 @@ class _GenerateV7DSP(_DSPGenerator):
         confkeys = self.configs.keys()
         confkeys.sort()
         for kind in confkeys:
-            capitalized_kind = kind.capitalize()
+            variant = self.configs[kind].variant
+            platform = self.configs[kind].platform
             outdir = self.configs[kind].outdir
             buildtarget = self.configs[kind].buildtarget
 
-            (d,c) = os.path.split(str(self.conspath))
-            fmt = 'echo Starting SCons &amp;&amp; &quot;%s&quot; -c &quot;%s&quot; -C %s -f %s%s %s'
-            cmd         = fmt % (python_executable, exec_script_main_xml,
-                                 d, c, '', buildtarget)
-            cleancmd    = fmt % (python_executable, exec_script_main_xml,
-                                 d, c, ' -c', buildtarget)
+            def xmlify(cmd):
+                cmd = string.replace(cmd, "&", "&amp;") # do this first
+                cmd = string.replace(cmd, "'", "&apos;")
+                cmd = string.replace(cmd, '"', "&quot;")
+                return cmd
+
+            env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET')
+            if not env_has_buildtarget:
+                self.env['MSVSBUILDTARGET'] = buildtarget
+
+            starting = 'echo Starting SCons && '
+            buildcmd    = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1))
+            rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1))
+            cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1))
+
+            if not env_has_buildtarget:
+                del self.env['MSVSBUILDTARGET']
 
             self.file.write(V7DSPConfiguration % locals())
 
         self.file.write('\t</Configurations>\n')
 
         if self.version >= 7.1:
-            self.file.write('\t<References>\n'
+            self.file.write('\t<References>\n' 
                             '\t</References>\n')
 
         self.PrintSourceFiles()
 
         self.file.write('</VisualStudioProject>\n')
 
-        # now we pickle some data and add it to the file -- MSDEV will ignore it.
-        pdata = pickle.dumps(self.configs,1)
-        pdata = base64.encodestring(pdata)
-        self.file.write('<!-- SCons Data:\n' + pdata + '\n')
-        pdata = pickle.dumps(self.sources,1)
-        pdata = base64.encodestring(pdata)
-        self.file.write(pdata + '-->\n')
+        if self.nokeep == 0:
+            # now we pickle some data and add it to the file -- MSDEV will ignore it.
+            pdata = pickle.dumps(self.configs,1)
+            pdata = base64.encodestring(pdata)
+            self.file.write('<!-- SCons Data:\n' + pdata + '\n')
+            pdata = pickle.dumps(self.sources,1)
+            pdata = base64.encodestring(pdata)
+            self.file.write(pdata + '-->\n')
 
     def PrintSourceFiles(self):
-        categories = {' Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
+        categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
                       'Header Files': 'h;hpp;hxx;hm;inl',
                       'Local Headers': 'h;hpp;hxx;hm;inl',
                       'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
@@ -477,30 +576,61 @@ class _GenerateV7DSP(_DSPGenerator):
         self.file.write('\t<Files>\n')
 
         cats = categories.keys()
-        cats.sort()
+        cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
+        cats = filter(lambda k, s=self: s.sources[k], cats)
         for kind in cats:
-            if not self.sources[kind]:
-                continue # skip empty groups
-
-            self.file.write('\t\t<Filter\n'
-                            '\t\t\tName="%s"\n'
-                            '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
-
-            for file in self.sources[kind]:
-                file = os.path.normpath(file)
-                self.file.write('\t\t\t<File\n'
-                                '\t\t\t\tRelativePath="%s">\n'
-                                '\t\t\t</File>\n' % file)
-
-            self.file.write('\t\t</Filter>\n')
-
-        # add the Conscript file outside of the groups
+            if len(cats) > 1:
+                self.file.write('\t\t<Filter\n'
+                                '\t\t\tName="%s"\n'
+                                '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
+
+
+            def printSources(hierarchy):
+                sorteditems = hierarchy.items()
+                sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
+
+                # First folders, then files
+                for key, value in sorteditems:
+                    if SCons.Util.is_Dict(value):
+                        self.file.write('\t\t\t<Filter\n'
+                                        '\t\t\t\tName="%s"\n'
+                                        '\t\t\t\tFilter="">\n' % (key))
+                        printSources(value)
+                        self.file.write('\t\t\t</Filter>\n')
+
+                for key, value in sorteditems:
+                    if SCons.Util.is_String(value):
+                        file = value
+                        if commonprefix:
+                            file = os.path.join(commonprefix, value)
+                        file = os.path.normpath(file)
+                        self.file.write('\t\t\t<File\n'
+                                        '\t\t\t\tRelativePath="%s">\n'
+                                        '\t\t\t</File>\n' % (file))
+
+            sources = self.sources[kind]
+
+            # First remove any common prefix
+            commonprefix = None
+            if len(sources) > 1:
+                commonprefix = os.path.commonprefix(sources)
+                prefixlen = len(commonprefix)
+                if prefixlen:
+                    sources = map(lambda s, p=prefixlen: s[p:], sources)
+
+            hierarchy = makeHierarchy(sources)
+            printSources(hierarchy)
+
+            if len(cats)>1:
+                self.file.write('\t\t</Filter>\n')
+
+        # add the SConscript file outside of the groups
         self.file.write('\t\t<File\n'
                         '\t\t\tRelativePath="%s">\n'
-                        '\t\t</File>\n'
-                        '\t</Files>\n' % str(self.source))
+                        '\t\t</File>\n' % str(self.sconscript))
 
-        self.file.write('\t<Globals>\n'
+        self.file.write('\t</Files>\n'
+                        '\t<Globals>\n'
                         '\t</Globals>\n')
 
     def Parse(self):
@@ -562,11 +692,26 @@ class _GenerateV7DSP(_DSPGenerator):
 
 class _DSWGenerator:
     """ Base class for DSW generators """
-    def __init__(self, dswfile, dspfile, source, env):
+    def __init__(self, dswfile, source, env):
         self.dswfile = os.path.normpath(str(dswfile))
-        self.dspfile = os.path.abspath(str(dspfile))
         self.env = env
 
+        if not env.has_key('projects'):
+            raise SCons.Errors.UserError, \
+                "You must specify a 'projects' argument to create an MSVSSolution."
+        projects = env['projects']
+        if not SCons.Util.is_List(projects):
+            raise SCons.Errors.InternalError, \
+                "The 'projects' argument must be a list of nodes."
+        projects = SCons.Util.flatten(projects)
+        if len(projects) < 1:
+            raise SCons.Errors.UserError, \
+                "You must specify at least one project to create an MSVSSolution."
+        if len(projects) > 1:
+            raise SCons.Errors.UserError, \
+                "Currently you can specify at most one project to create an MSVSSolution."
+        self.dspfile = str(projects[0])
+
         if self.env.has_key('name'):
             self.name = self.env['name']
         else:
@@ -577,8 +722,8 @@ class _DSWGenerator:
 
 class _GenerateV7DSW(_DSWGenerator):
     """Generates a Solution file for MSVS .NET"""
-    def __init__(self, dswfile, dspfile, source, env):
-        _DSWGenerator.__init__(self, dswfile, dspfile, source, env)
+    def __init__(self, dswfile, source, env):
+        _DSWGenerator.__init__(self, dswfile, source, env)
 
         self.version = float(self.env['MSVS_VERSION'])
         self.versionstr = '7.00'
@@ -590,20 +735,44 @@ class _GenerateV7DSW(_DSWGenerator):
         else:
             self.slnguid = _generateGUID(dswfile, self.name)
 
-        self.config = Config()
-        if env.has_key('variant'):
-            self.config.variant = env['variant'].capitalize()
-        else:
-            raise SCons.Errors.InternalError, \
-                  "You must specify a 'variant' argument (i.e. 'Debug' or " +\
-                  "'Release') to create an MSVS Solution File."
-
         self.configs = {}
 
-        if os.path.exists(self.dswfile):
+        self.nokeep = 0
+        if env.has_key('nokeep') and env['variant'] != 0:
+            self.nokeep = 1
+
+        if self.nokeep == 0 and os.path.exists(self.dswfile):
             self.Parse()
 
-        self.configs[self.config.variant] = self.config
+        def AddConfig(variant):
+            config = Config()
+
+            match = re.match('(.*)\|(.*)', variant)
+            if match:
+                config.variant = match.group(1)
+                config.platform = match.group(2)
+            else:
+                config.variant = variant
+                config.platform = 'Win32';
+
+            self.configs[variant] = config
+            print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
+
+        if not env.has_key('variant'):
+            raise SCons.Errors.InternalError, \
+                  "You must specify a 'variant' argument (i.e. 'Debug' or " +\
+                  "'Release') to create an MSVS Solution File."
+        elif SCons.Util.is_String(env['variant']):
+            AddConfig(env['variant'])
+        elif SCons.Util.is_List(env['variant']):
+            for variant in env['variant']:
+                AddConfig(variant)
+
+        self.platforms = []
+        for key in self.configs.keys():
+            platform = self.configs[key].platform
+            if not platform in self.platforms:
+                self.platforms.append(platform)
 
     def Parse(self):
         try:
@@ -650,7 +819,8 @@ class _GenerateV7DSW(_DSWGenerator):
         confkeys.sort()
         cnt = 0
         for name in confkeys:
-            self.file.write('\t\tConfigName.%d = %s\n' % (cnt, name.capitalize()))
+            variant = self.configs[name].variant
+            self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
             cnt = cnt + 1
         self.file.write('\tEndGlobalSection\n')
         if self.version < 7.1:
@@ -658,18 +828,21 @@ class _GenerateV7DSW(_DSWGenerator):
                             '\tEndGlobalSection\n')
         self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
         for name in confkeys:
-            name = name.capitalize()
-            self.file.write('\t\t%s.%s.ActiveCfg = %s|Win32\n'
-                            '\t\t%s.%s.Build.0 = %s|Win32\n'  %(self.slnguid,name,name,self.slnguid,name,name))
+            name = name
+            variant = self.configs[name].variant
+            platform = self.configs[name].platform
+            self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
+                            '\t\t%s.%s.Build.0 = %s|%s\n'  %(self.slnguid,variant,variant,platform,self.slnguid,variant,variant,platform))
         self.file.write('\tEndGlobalSection\n'
                         '\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
                         '\tEndGlobalSection\n'
                         '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
                         '\tEndGlobalSection\n'
                         'EndGlobal\n')
-        pdata = pickle.dumps(self.configs,1)
-        pdata = base64.encodestring(pdata)
-        self.file.write(pdata + '\n')
+        if self.nokeep == 0:
+            pdata = pickle.dumps(self.configs,1)
+            pdata = base64.encodestring(pdata)
+            self.file.write(pdata + '\n')
 
     def Build(self):
         try:
@@ -716,7 +889,9 @@ class _GenerateV6DSW(_DSWGenerator):
 
     def PrintWorkspace(self):
         """ writes a DSW file """
-        self.file.write(V6DSWHeader % self.__dict__)
+        name = self.name
+        dspfile = self.dspfile
+        self.file.write(V6DSWHeader % locals())
 
     def Build(self):
         try:
@@ -738,14 +913,14 @@ def GenerateDSP(dspfile, source, env):
         g = _GenerateV6DSP(dspfile, source, env)
         g.Build()
 
-def GenerateDSW(dswfile, dspfile, source, env):
+def GenerateDSW(dswfile, source, env):
     """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
 
     if env.has_key('MSVS_VERSION') and float(env['MSVS_VERSION']) >= 7.0:
-        g = _GenerateV7DSW(dswfile, dspfile, source, env)
+        g = _GenerateV7DSW(dswfile, source, env)
         g.Build()
     else:
-        g = _GenerateV6DSW(dswfile, dspfile, source, env)
+        g = _GenerateV6DSW(dswfile, source, env)
         g.Build()
 
 
@@ -761,7 +936,7 @@ def get_default_visualstudio_version(env):
 
     version = '6.0'
     versions = [version]
-    if not env.has_key('MSVS') or type(env['MSVS']) != types.DictType:
+    if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
         env['MSVS'] = {}
 
     if env.has_key('MSVS_VERSION'):
@@ -1022,19 +1197,10 @@ def GetMSVSSolutionSuffix(target, source, env, for_signature):
 def GenerateProject(target, source, env):
     # generate the dsp file, according to the version of MSVS.
     builddspfile = target[0]
-    builddswfile = target[1]
-    dswfile = builddswfile.srcnode()
     dspfile = builddspfile.srcnode()
 
-#     print "SConscript    :",str(source[0])
-#     print "DSW file      :",dswfile
-#     print "DSP file      :",dspfile
-#     print "Build DSW file:",builddswfile
-#     print "Build DSP file:",builddspfile
-
     # this detects whether or not we're using a BuildDir
-    if os.path.abspath(os.path.normcase(str(dspfile))) != \
-           os.path.abspath(os.path.normcase(str(builddspfile))):
+    if not dspfile is builddspfile:
         try:
             bdsp = open(str(builddspfile), "w+")
         except IOError, detail:
@@ -1043,20 +1209,33 @@ def GenerateProject(target, source, env):
 
         bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
 
-        try:
-            bdsw = open(str(builddswfile), "w+")
-        except IOError, detail:
-            print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
-            raise
+    GenerateDSP(dspfile, source, env)
 
-        bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
+    if env.get('auto_build_solution', 1):
+        builddswfile = target[1]
+        dswfile = builddswfile.srcnode()
 
-    GenerateDSP(dspfile, source, env)
-    GenerateDSW(dswfile, dspfile, source, env)
+        if not dswfile is builddswfile:
+
+            try:
+                bdsw = open(str(builddswfile), "w+")
+            except IOError, detail:
+                print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
+                raise
+
+            bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
+
+        GenerateDSW(dswfile, source, env)
+
+def GenerateSolution(target, source, env):
+    GenerateDSW(target[0], source, env)
 
 def projectEmitter(target, source, env):
-    """Sets up the DSP and DSW dependencies for an SConscript file."""
+    """Sets up the DSP dependencies."""
 
+    # todo: Not sure what sets source to what user has passed as target,
+    # but this is what happens. When that is fixed, we also won't have
+    # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
     if source[0] == target[0]:
         source = []
 
@@ -1065,36 +1244,157 @@ def projectEmitter(target, source, env):
     suff = env.subst('$MSVSPROJECTSUFFIX')
     target[0] = base + suff
 
-    dspfile = env.File(target[0]).srcnode()
-    dswfile = env.File(SCons.Util.splitext(str(dspfile))[0] + env.subst('$MSVSSOLUTIONSUFFIX'))
+    if not source:
+        source = 'prj_inputs:'
+        source = source + env.subst('$MSVSSCONSCOM', 1)
+        source = source + env.subst('$MSVSENCODING', 1)
+
+        if env.has_key('buildtarget') and env['buildtarget'] != None:
+            if SCons.Util.is_String(env['buildtarget']):
+                source = source + ' "%s"' % env['buildtarget']
+            elif SCons.Util.is_List(env['buildtarget']):
+                for bt in env['buildtarget']:
+                    if SCons.Util.is_String(bt):
+                        source = source + ' "%s"' % bt
+                    else:
+                        try: source = source + ' "%s"' % bt.get_abspath()
+                        except AttributeError: raise SCons.Errors.InternalError, \
+                            "buildtarget can be a string, a node, a list of strings or nodes, or None"
+            else:
+                try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
+                except AttributeError: raise SCons.Errors.InternalError, \
+                    "buildtarget can be a string, a node, a list of strings or nodes, or None"
+
+        if env.has_key('outdir') and env['outdir'] != None:
+            if SCons.Util.is_String(env['outdir']):
+                source = source + ' "%s"' % env['outdir']
+            elif SCons.Util.is_List(env['outdir']):
+                for s in env['outdir']:
+                    if SCons.Util.is_String(s):
+                        source = source + ' "%s"' % s
+                    else:
+                        try: source = source + ' "%s"' % s.get_abspath()
+                        except AttributeError: raise SCons.Errors.InternalError, \
+                            "outdir can be a string, a node, a list of strings or nodes, or None"
+            else:
+                try: source = source + ' "%s"' % env['outdir'].get_abspath()
+                except AttributeError: raise SCons.Errors.InternalError, \
+                    "outdir can be a string, a node, a list of strings or nodes, or None"
+
+        if env.has_key('name'):
+            if SCons.Util.is_String(env['name']):
+                source = source + ' "%s"' % env['name']
+            else:
+                raise SCons.Errors.InternalError, "name must be a string"
+
+        if env.has_key('variant'):
+            if SCons.Util.is_String(env['variant']):
+                source = source + ' "%s"' % env['variant']
+            elif SCons.Util.is_List(env['variant']):
+                for variant in env['variant']:
+                    if SCons.Util.is_String(variant):
+                        source = source + ' "%s"' % variant
+                    else:
+                        raise SCons.Errors.InternalError, "name must be a string or a list of strings"
+            else:
+                raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
+        else:
+            raise SCons.Errors.InternalError, "variant must be specified"
+
+        for s in _DSPGenerator.srcargs:
+            if env.has_key(s):
+                if SCons.Util.is_String(env[s]):
+                    source = source + ' "%s' % env[s]
+                elif SCons.Util.is_List(env[s]):
+                    for t in env[s]:
+                        if SCons.Util.is_String(t):
+                            source = source + ' "%s"' % t
+                        else:
+                            raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
+                else:
+                    raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
+
+        source = source + ' "%s"' % str(target[0])
+        source = [SCons.Node.Python.Value(source)]
+
+    targetlist = [target[0]]
+    sourcelist = source
+
+    if env.get('auto_build_solution', 1):
+        env['projects'] = targetlist
+        t, s = solutionEmitter(target, target, env)
+        targetlist = targetlist + t
+
+    return (targetlist, sourcelist)
+
+def solutionEmitter(target, source, env):
+    """Sets up the DSW dependencies."""
+
+    # todo: Not sure what sets source to what user has passed as target,
+    # but this is what happens. When that is fixed, we also won't have
+    # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
+    if source[0] == target[0]:
+        source = []
+
+    # make sure the suffix is correct for the version of MSVS we're running.
+    (base, suff) = SCons.Util.splitext(str(target[0]))
+    suff = env.subst('$MSVSSOLUTIONSUFFIX')
+    target[0] = base + suff
 
-    # XXX Need to find a way to abstract this; the build engine
-    # shouldn't depend on anything in SCons.Script.
-    stack = SCons.Script.call_stack
     if not source:
-        source = [stack[-1].sconscript.srcnode()]
-    source[0].attributes.sconstruct = stack[0].sconscript
+        source = 'sln_inputs:'
 
-    bdswpath = SCons.Util.splitext(str(target[0]))[0] + env.subst('$MSVSSOLUTIONSUFFIX')
-    bdswfile = env.File(bdswpath)
+        if env.has_key('name'):
+            if SCons.Util.is_String(env['name']):
+                source = source + ' "%s"' % env['name']
+            else:
+                raise SCons.Errors.InternalError, "name must be a string"
 
-    # only make these side effects if they're
-    # not the same file.
-    if os.path.abspath(os.path.normcase(str(dspfile))) != \
-           os.path.abspath(os.path.normcase(str(target[0]))):
-        env.SideEffect(dspfile, target[0])
-        env.Precious(dspfile)
-        # dswfile isn't precious -- it can be blown away and rewritten each time.
-        env.SideEffect(dswfile, target[0])
+        if env.has_key('variant'):
+            if SCons.Util.is_String(env['variant']):
+                source = source + ' "%s"' % env['variant']
+            elif SCons.Util.is_List(env['variant']):
+                for variant in env['variant']:
+                    if SCons.Util.is_String(variant):
+                        source = source + ' "%s"' % variant
+                    else:
+                        raise SCons.Errors.InternalError, "name must be a string or a list of strings"
+            else:
+                raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
+        else:
+            raise SCons.Errors.InternalError, "variant must be specified"
 
-    return ([target[0],bdswfile], source)
+        if env.has_key('slnguid'):
+            if SCons.Util.is_String(env['slnguid']):
+                source = source + ' "%s"' % env['slnguid']
+            else:
+                raise SCons.Errors.InternalError, "slnguid must be a string"
+
+        if env.has_key('projects'):
+            if SCons.Util.is_String(env['projects']):
+                source = source + ' "%s"' % env['projects']
+            elif SCons.Util.is_List(env['projects']):
+                for t in env['projects']:
+                    if SCons.Util.is_String(t):
+                        source = source + ' "%s"' % t
+
+        source = source + ' "%s"' % str(target[0])
+        source = [SCons.Node.Python.Value(source)]
+
+    return ([target[0]], source)
 
-projectGeneratorAction = SCons.Action.Action(GenerateProject, None)
+projectAction = SCons.Action.Action(GenerateProject, None)
+
+solutionAction = SCons.Action.Action(GenerateSolution, None)
 
 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
                                        suffix = '$MSVSPROJECTSUFFIX',
                                        emitter = projectEmitter)
 
+solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
+                                        suffix = '$MSVSSOLUTIONSUFFIX',
+                                        emitter = solutionEmitter)
+
 def generate(env):
     """Add Builders and construction variables for Microsoft Visual
     Studio project files to an Environment."""
@@ -1103,7 +1403,28 @@ def generate(env):
     except KeyError:
         env['BUILDERS']['MSVSProject'] = projectBuilder
 
-    env['MSVSPROJECTCOM'] = projectGeneratorAction
+    try:
+        env['BUILDERS']['MSVSSolution']
+    except KeyError:
+        env['BUILDERS']['MSVSSolution'] = solutionBuilder
+
+    env['MSVSPROJECTCOM'] = projectAction
+    env['MSVSSOLUTIONCOM'] = solutionAction
+
+    if SCons.Script.call_stack:
+        # XXX Need to find a way to abstract this; the build engine
+        # shouldn't depend on anything in SCons.Script.
+        env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
+    else:
+        env['MSVSSCONSCRIPT'] = env.File('SConstruct')
+
+    env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, exec_script_main)
+    env['MSVSSCONSFLAGS'] = '-C ${MSVSSCONSCRIPT.dir.abspath} -f ${MSVSSCONSCRIPT.name}'
+    env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
+    env['MSVSBUILDCOM'] = '$MSVSSCONSCOM $MSVSBUILDTARGET'
+    env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM $MSVSBUILDTARGET'
+    env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c $MSVSBUILDTARGET'
+    env['MSVSENCODING'] = 'Windows-1252'
 
     try:
         version = get_default_visualstudio_version(env)
@@ -1143,4 +1464,3 @@ def exists(env):
     else:
         # there's at least one version of MSVS installed.
         return 1
-
index 4103ce1eb77f2dbac780d86b91e2b4f45a1b9c64..647acd562d0334fe44c936f42f7070b12640c77e 100644 (file)
@@ -7,62 +7,100 @@ XXX
 
 <builder name ="MSVSProject">
 <summary>
-Builds Microsoft Visual Studio project files.
+Builds a Microsoft Visual Studio project file,
+and by default builds a solution file as well.
+
 This builds a Visual Studio project file, based on the version of
 Visual Studio that is configured (either the latest installed version,
-or the version set by 
+or the version specified by 
 &cv-MSVS_VERSION;
 in the Environment constructor).
-For VS 6, it will generate 
+For Visual Studio 6, it will generate a
 <filename>.dsp</filename>
-and
+file.
+For Visual Studio 7 (.NET), it will generate a
+<filename>.dsp</filename>
+file.
+
+By default,
+this also generates a solution file
+for the specified project,
+a
 <filename>.dsw</filename>
-files, for VS 7, it will
-generate
-<filename>.vcproj</filename>
-and
+file for Visual Studio 6
+or a
 <filename>.sln</filename>
-files.
+file for Visual Studio 7 (.NET).
+This behavior may be disabled by specifying
+<literal>auto_build_solution=0</literal>
+when you call
+&b-MSVSProject;,
+in which case you presumably want to
+build the solution file(s)
+by calling the
+&b-MSVSSolution;
+Builder (see below).
 
 It takes several lists of filenames to be placed into the project
-file, currently these are limited to 
+file.
+These are currently these are limited to 
 <literal>srcs</literal>,
 <literal>incs</literal>,
 <literal>localincs</literal>,
 <literal>resources</literal>,
 and
 <literal>misc</literal>.
-These are pretty self explanatory, but it
-should be noted that the <literal>srcs</literal> list
-is NOT added to the &cv-SOURCES;
-construction variable.  This is because it represents a list of files
-to be added to the project file, not the source used to build the
-project file (in this case, the "source" is the &SConscript; file used
-to call MSVSProject).
-
-In addition to these values (which are all optional, although not
-specifying any of them results in an empty project file), the
-following values must be specified:
-
-target: The name of the target
+These are pretty self-explanatory, but it should be noted that these
+lists are added to the &cv-SOURCES; construction variable as strings,
+NOT as SCons File Nodes.  This is because they represent file
+names to be added to the project file, not the source files used to
+build the project file.
+
+In addition to the above lists of values (which are all optional,
+although not specifying any of them results in an empty project file),
+the following values may be specified:
+
+<literal>target</literal>: The name of the target
 <filename>.dsp</filename>
 or
 <filename>.vcproj</filename>
-file.  The correct
-suffix for the version of Visual Studio must be used, but the
+file.
+The correct
+suffix for the version of Visual Studio must be used,
+but the
 &cv-MSVSPROJECTSUFFIX;
-construction value
+construction variable
 will be defined to the correct value (see example below).
 
-variant: The name of this particular variant.  These are typically
-things like "Debug" or "Release", but really can be anything you want.
-Multiple calls to MSVSProject with different variants are allowed: all
-variants will be added to the project file with their appropriate
+<literal>variant</literal>:
+The name of this particular variant.
+For Visual Studio 7 projects,
+this can also be a list of variant names.
+These are typically things like "Debug" or "Release", but really
+can be anything you want.
+For Visual Studio 7 projects,
+they may also specify a target platform
+separated from the variant name by a
+<literal>|</literal>
+(vertical pipe)
+character:
+<literal>Debug|Xbox</literal>.
+The default target platform is Win32.
+Multiple calls to
+&b-MSVSProject;
+ with different variants are allowed;
+all variants will be added to the project file with their appropriate
 build targets and sources.
 
-buildtarget: A list of SCons.Node.FS objects which is returned from
-the command which builds the target.  This is used to tell SCons what
-to build when the 'build' button is pressed inside of the IDE.
+<literal>buildtarget</literal>:
+An optional string, node, or list of strings or nodes
+(one per build variant), to tell the Visual Studio debugger
+what output target to use in what build variant.
+The number of
+<literal>buildtarget</literal>
+entries must match the number of
+<literal>variant</literal>
+entries.
 
 Example usage:
 
@@ -88,6 +126,60 @@ local.MSVSProject(target = 'Bar' + env['MSVSPROJECTSUFFIX'],
 </summary>
 </builder>
 
+<builder name ="MSVSSolution">
+<summary>
+Builds a Microsoft Visual Studio solution file.
+
+This builds a Visual Studio solution file,
+based on the version of Visual Studio that is configured
+(either the latest installed version,
+or the version specified by 
+&cv-MSVS_VERSION;
+in the construction environment).
+For Visual Studio 6, it will generate a
+<filename>dsw</filename>
+file.
+For Visual Studio 7 (.NET), it will
+generate a
+<filename>sln</filename>
+file.
+
+The following values must be specified:
+
+<literal>target</literal>:
+The name of the target .dsw or .sln file.  The correct
+suffix for the version of Visual Studio must be used, but the value
+&cv-MSVSSOLUTIONSUFFIX;
+will be defined to the correct value (see example below).
+
+<literal>variant</literal>:
+The name of this particular variant, or a list of variant
+names (the latter is only supported for MSVS 7 solutions). These are
+typically things like "Debug" or "Release", but really can be anything
+you want. For MSVS 7 they may also specify target platform, like this
+"Debug|Xbox". Default platform is Win32.
+
+<literal>projects</literal>:
+A list of project file names, or Project nodes returned by calls to the
+&b-MSVSProject;
+Builder,
+to be placed into the solution file.
+(NOTE: Currently only one project is supported per solution.)
+It should be noted that these file names are NOT added to the $SOURCES
+environment variable in form of files, but rather as strings.   This
+is because they represent file names to be added to the solution file,
+not the source files used to build the solution file.
+
+Example Usage:
+
+<example>
+local.MSVSSolution(target = 'Bar' + env['MSVSSOLUTIONSUFFIX'],
+                  projects = ['bar' + env['MSVSPROJECTSUFFIX']],
+                  variant = 'Release')
+</example>
+</summary>
+</builder>
+
 <cvar name="MSVS">
 <summary>
 When the Microsoft Visual Studio tools are initialized, they set up
@@ -224,8 +316,10 @@ environment variables are set explictly.
 Sets the preferred version of MSVS to use.
 
 SCons will (by default) select the latest version of MSVS
-installed on your machine.  So, if you have version 6 and version 7
-(MSVS .NET) installed, it will prefer version 7.  You can override this by
+installed on your machine.
+So, if you have version 6 and version 7 (MSVS .NET) installed,
+it will prefer version 7.
+You can override this by
 specifying the 
 <envar>MSVS_VERSION</envar>
 variable in the Environment initialization, setting it to the
@@ -234,10 +328,36 @@ If the given version isn't installed, tool initialization will fail.
 </summary>
 </cvar>
 
+<cvar name="MSVSBUILDCOM">
+<summary>
+The build command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with any specified
+build targets.
+</summary>
+</cvar>
+
+<cvar name="MSVSCLEANCOM">
+<summary>
+The clean command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with the -c option
+to remove any specified targets.
+</summary>
+</cvar>
+
+<cvar name="MSVSENCODING">
+<summary>
+The encoding string placed in
+a generated Microsoft Visual Studio project file.
+The default is encoding
+<literal>Windows-1252</literal>.
+</summary>
+</cvar>
+
 <cvar name="MSVSPROJECTCOM">
 <summary>
-The action used to generate Microsoft Visual Studio
-project and solution files.
+The action used to generate Microsoft Visual Studio project files.
 </summary>
 </cvar>
 
@@ -253,6 +373,63 @@ when using earlier versions of Visual Studio.
 </summary>
 </cvar>
 
+<cvar name="MSVSREBUILDCOM">
+<summary>
+The rebuild command line placed in
+a generated Microsoft Visual Studio project file.
+The default is to have Visual Studio invoke SCons with any specified
+rebuild targets.
+</summary>
+</cvar>
+
+<cvar name="MSVSSCONS">
+<summary>
+The SCons used in generated Microsoft Visual Studio project files.
+The default is the version of SCons being
+used to generate the project file.
+</summary>
+</cvar>
+
+<cvar name="MSVSSCONSFLAGS">
+<summary>
+The SCons flags used in generated Microsoft Visual Studio
+project files.
+</summary>
+</cvar>
+
+<cvar name="MSVSSCONSCOM">
+<summary>
+The default SCons command used in generated Microsoft Visual Studio
+project files.
+</summary>
+</cvar>
+
+<cvar name="MSVSSCONSCRIPT">
+<summary>
+The sconscript file
+(that is,
+&SConstruct;
+or
+&SConscript;
+file)
+that will be invoked by Visual Studio
+project files
+(through the
+&cv-MSVSSCONSCOM;
+variable).
+The default is the same sconscript file
+that contains the call to
+&b-MSVSProject;
+to build the project file.
+</summary>
+</cvar>
+
+<cvar name="MSVSSOLUTIONCOM">
+<summary>
+The action used to generate Microsoft Visual Studio solution files.
+</summary>
+</cvar>
+
 <cvar name="MSVSSOLUTIONSUFFIX">
 <summary>
 The suffix used for Microsoft Visual Studio solution (DSW) files.
index 8792cb0952d73cec9278007b8e3660c617292d89..e3f29942046a4c83b5095407d4f55dfc38ec2499 100644 (file)
@@ -75,20 +75,20 @@ CFG=Test - Win32 Release
 
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir "<WORKPATH>"
-# PROP BASE Intermediate_Dir "<WORKPATH>"
-# PROP BASE Cmd_Line "echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-# PROP BASE Rebuild_Opt "-c && echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-# PROP BASE Target_File "<WORKPATH>\Test.exe"
+# PROP BASE Output_Dir ""
+# PROP BASE Intermediate_Dir ""
+# PROP BASE Cmd_Line "echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct Test.exe"
+# PROP BASE Rebuild_Opt "-c && echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct Test.exe"
+# PROP BASE Target_File "Test.exe"
 # PROP BASE Bsc_Name ""
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 0
-# PROP Output_Dir "<WORKPATH>"
-# PROP Intermediate_Dir "<WORKPATH>"
-# PROP Cmd_Line "echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-# PROP Rebuild_Opt "-c && echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-# PROP Target_File "<WORKPATH>\Test.exe"
+# PROP Output_Dir ""
+# PROP Intermediate_Dir ""
+# PROP Cmd_Line "echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct Test.exe"
+# PROP Rebuild_Opt "-c && echo Starting SCons && "<PYTHON>" -c "<SCONS_SCRIPT_MAIN>" -C <WORKPATH> -f SConstruct Test.exe"
+# PROP Target_File "Test.exe"
 # PROP Bsc_Name ""
 # PROP Target_Dir ""
 
@@ -102,14 +102,6 @@ CFG=Test - Win32 Release
 
 !ENDIF 
 
-# Begin Group " Source Files"
-
-# PROP Default_Filter "cpp;c;cxx;l;y;def;odl;idl;hpj;bat"
-# Begin Source File
-
-SOURCE="test.c"
-# End Source File
-# End Group
 # Begin Group "Header Files"
 
 # PROP Default_Filter "h;hpp;hxx;hm;inl"
@@ -142,6 +134,14 @@ SOURCE="readme.txt"
 SOURCE="test.rc"
 # End Source File
 # End Group
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;l;y;def;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="test.c"
+# End Source File
+# End Group
 # Begin Source File
 
 SOURCE="<SCONSCRIPT>"
@@ -156,7 +156,7 @@ Microsoft Developer Studio Workspace File, Format Version 6.00
 
 ###############################################################################
 
-Project: "Test"="<WORKPATH>\Test.dsp" - Package Owner=<4>
+Project: "Test"="Test.dsp" - Package Owner=<4>
 
 Package=<5>
 {{{
@@ -212,15 +212,13 @@ test.run(chdir='work1', arguments="Test.dsp")
 
 test.must_exist(test.workpath('work1', 'Test.dsp'))
 dsp = test.read(['work1', 'Test.dsp'], 'r')
-expect = test.msvs_substitute(expected_dspfile, '6.0', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_dspfile, '6.0', 'work1', 'SConstruct')
 # don't compare the pickled data
 assert dsp[:len(expect)] == expect, test.diff_substr(expect, dsp)
 
 test.must_exist(test.workpath('work1', 'Test.dsw'))
 dsw = test.read(['work1', 'Test.dsw'], 'r')
-expect = test.msvs_substitute(expected_dswfile, '6.0', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_dswfile, '6.0', 'work1', 'SConstruct')
 assert dsw == expect, test.diff_substr(expect, dsw)
 
 test.run(chdir='work1', arguments='-c .')
@@ -250,6 +248,17 @@ test.write(['work2', 'src', 'SConscript'], SConscript_contents)
 
 test.run(chdir='work2', arguments=".")
 
+dsp = test.read(['work2', 'src', 'Test.dsp'], 'r')
+expect = test.msvs_substitute(expected_dspfile, '6.0', 'work2', 'SConstruct')
+# don't compare the pickled data
+assert dsp[:len(expect)] == expect, test.diff_substr(expect, dsp)
+
+test.must_exist(test.workpath('work2', 'src', 'Test.dsw'))
+dsw = test.read(['work2', 'src', 'Test.dsw'], 'r')
+expect = test.msvs_substitute(expected_dswfile, '6.0',
+                              os.path.join('work2', 'src'))
+assert dsw == expect, test.diff_substr(expect, dsw)
+
 test.must_match(['work2', 'build', 'Test.dsp'], """\
 This is just a placeholder file.
 The real project file is here:
@@ -257,12 +266,6 @@ The real project file is here:
 """ % test.workpath('work2', 'src', 'Test.dsp'),
                 mode='r')
 
-dsp = test.read(['work2', 'src', 'Test.dsp'], 'r')
-expect = test.msvs_substitute(expected_dspfile, '6.0', 'work2',
-                             test.workpath('work2', 'src', 'SConscript'))
-# don't compare the pickled data
-assert dsp[:len(expect)] == expect, test.diff_substr(expect, dsp)
-
 test.must_match(['work2', 'build', 'Test.dsw'], """\
 This is just a placeholder file.
 The real workspace file is here:
@@ -270,11 +273,67 @@ The real workspace file is here:
 """ % test.workpath('work2', 'src', 'Test.dsw'),
                 mode='r')
 
-test.must_exist(test.workpath('work2', 'src', 'Test.dsw'))
-dsw = test.read(['work2', 'src', 'Test.dsw'], 'r')
-expect = test.msvs_substitute(expected_dswfile, '6.0', 'work2\\src')
+
+
+test.subdir('work3')
+
+test.write(['work3', 'SConstruct'], """\
+env=Environment(MSVS_VERSION = '6.0')
+
+testsrc = ['test.c']
+testincs = ['sdk.h']
+testlocalincs = ['test.h']
+testresources = ['test.rc']
+testmisc = ['readme.txt']
+
+p = env.MSVSProject(target = 'Test.dsp',
+                    srcs = testsrc,
+                    incs = testincs,
+                    localincs = testlocalincs,
+                    resources = testresources,
+                    misc = testmisc,
+                    buildtarget = 'Test.exe',
+                    variant = 'Release',
+                    auto_build_solution = 0)
+
+env.MSVSSolution(target = 'Test.dsw',
+                 slnguid = '{SLNGUID}',
+                 projects = [p],
+                 variant = 'Release')
+""")
+
+test.run(chdir='work3', arguments=".")
+
+test.must_exist(test.workpath('work3', 'Test.dsp'))
+dsp = test.read(['work3', 'Test.dsp'], 'r')
+expect = test.msvs_substitute(expected_dspfile, '6.0', 'work3', 'SConstruct')
+# don't compare the pickled data
+assert dsp[:len(expect)] == expect, test.diff_substr(expect, dsp)
+
+test.must_exist(test.workpath('work3', 'Test.dsw'))
+dsw = test.read(['work3', 'Test.dsw'], 'r')
+expect = test.msvs_substitute(expected_dswfile, '6.0', 'work3', 'SConstruct')
 assert dsw == expect, test.diff_substr(expect, dsw)
 
+test.run(chdir='work3', arguments='-c .')
+
+test.must_not_exist(test.workpath('work3', 'Test.dsp'))
+test.must_not_exist(test.workpath('work3', 'Test.dsw'))
+
+test.run(chdir='work3', arguments='.')
+
+test.must_exist(test.workpath('work3', 'Test.dsp'))
+test.must_exist(test.workpath('work3', 'Test.dsw'))
+
+test.run(chdir='work3', arguments='-c Test.dsw')
+
+test.must_exist(test.workpath('work3', 'Test.dsp'))
+test.must_not_exist(test.workpath('work3', 'Test.dsw'))
+
+test.run(chdir='work3', arguments='-c Test.dsp')
+
+test.must_not_exist(test.workpath('work3', 'Test.dsp'))
+
 
 
 test.pass_test()
index 9144537860b3af49d45c8591a4308458af65c66d..409501d6ec5a4510f1747c5f10e76f6e1adbe099 100644 (file)
@@ -79,28 +79,21 @@ expected_vcprojfile = """\
 \t<Configurations>
 \t\t<Configuration
 \t\t\tName="Release|Win32"
-\t\t\tOutputDirectory="<WORKPATH>"
-\t\t\tIntermediateDirectory="<WORKPATH>"
+\t\t\tOutputDirectory=""
+\t\t\tIntermediateDirectory=""
 \t\t\tConfigurationType="0"
 \t\t\tUseOfMFC="0"
 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
 \t\t\t<Tool
 \t\t\t\tName="VCNMakeTool"
-\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct -c <WORKPATH>\Test.exe"
-\t\t\t\tRebuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-\t\t\t\tOutput="<WORKPATH>\Test.exe"/>
+\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct Test.exe"
+\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct -c Test.exe"
+\t\t\t\tRebuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct Test.exe"
+\t\t\t\tOutput="Test.exe"/>
 \t\t</Configuration>
 \t</Configurations>
 \t<Files>
 \t\t<Filter
-\t\t\tName=" Source Files"
-\t\t\tFilter="cpp;c;cxx;l;y;def;odl;idl;hpj;bat">
-\t\t\t<File
-\t\t\t\tRelativePath="test.cpp">
-\t\t\t</File>
-\t\t</Filter>
-\t\t<Filter
 \t\t\tName="Header Files"
 \t\t\tFilter="h;hpp;hxx;hm;inl">
 \t\t\t<File
@@ -128,6 +121,13 @@ expected_vcprojfile = """\
 \t\t\t\tRelativePath="test.rc">
 \t\t\t</File>
 \t\t</Filter>
+\t\t<Filter
+\t\t\tName="Source Files"
+\t\t\tFilter="cpp;c;cxx;l;y;def;odl;idl;hpj;bat">
+\t\t\t<File
+\t\t\t\tRelativePath="test.cpp">
+\t\t\t</File>
+\t\t</Filter>
 \t\t<File
 \t\t\tRelativePath="<SCONSCRIPT>">
 \t\t</File>
@@ -169,15 +169,13 @@ test.run(chdir='work1', arguments="Test.vcproj")
 
 test.must_exist(test.workpath('work1', 'Test.vcproj'))
 vcproj = test.read(['work1', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work1', 'SConstruct')
 # don't compare the pickled data
 assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
 
 test.must_exist(test.workpath('work1', 'Test.sln'))
 sln = test.read(['work1', 'Test.sln'], 'r')
-expect = test.msvs_substitute(expected_slnfile, '7.0', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_slnfile, '7.0', 'work1', 'SConstruct')
 # don't compare the pickled data
 assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
 
@@ -208,8 +206,7 @@ python = os.path.join('$(PYTHON_ROOT)', os.path.split(sys.executable)[1])
 
 test.must_exist(test.workpath('work1', 'Test.vcproj'))
 vcproj = test.read(['work1', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work1',
-                             test.workpath('work1', 'SConstruct'),
+expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work1', 'SConstruct',
                              python=python)
 # don't compare the pickled data
 assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
@@ -228,6 +225,18 @@ test.write(['work2', 'src', 'SConscript'], SConscript_contents)
 
 test.run(chdir='work2', arguments=".")
 
+vcproj = test.read(['work2', 'src', 'Test.vcproj'], 'r')
+expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work2', 'SConstruct')
+# don't compare the pickled data
+assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
+
+test.must_exist(test.workpath('work2', 'src', 'Test.sln'))
+sln = test.read(['work2', 'src', 'Test.sln'], 'r')
+expect = test.msvs_substitute(expected_slnfile, '7.0',
+                              os.path.join('work2', 'src'))
+# don't compare the pickled data
+assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
+
 test.must_match(['work2', 'build', 'Test.vcproj'], """\
 This is just a placeholder file.
 The real project file is here:
@@ -235,12 +244,6 @@ The real project file is here:
 """ % test.workpath('work2', 'src', 'Test.vcproj'),
                 mode='r')
 
-vcproj = test.read(['work2', 'src', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work2',
-                             test.workpath('work2', 'src', 'SConscript'))
-# don't compare the pickled data
-assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
-
 test.must_match(['work2', 'build', 'Test.sln'], """\
 This is just a placeholder file.
 The real workspace file is here:
@@ -248,12 +251,68 @@ The real workspace file is here:
 """ % test.workpath('work2', 'src', 'Test.sln'),
                 mode='r')
 
-test.must_exist(test.workpath('work2', 'src', 'Test.sln'))
-sln = test.read(['work2', 'src', 'Test.sln'], 'r')
-expect = test.msvs_substitute(expected_slnfile, '7.0', 'work2\\src')
+
+
+test.subdir('work3')
+
+test.write(['work3', 'SConstruct'], """\
+env=Environment(MSVS_VERSION = '7.0')
+
+testsrc = ['test.cpp']
+testincs = ['sdk.h']
+testlocalincs = ['test.h']
+testresources = ['test.rc']
+testmisc = ['readme.txt']
+
+p = env.MSVSProject(target = 'Test.vcproj',
+                    srcs = testsrc,
+                    incs = testincs,
+                    localincs = testlocalincs,
+                    resources = testresources,
+                    misc = testmisc,
+                    buildtarget = 'Test.exe',
+                    variant = 'Release',
+                    auto_build_solution = 0)
+
+env.MSVSSolution(target = 'Test.sln',
+                 slnguid = '{SLNGUID}',
+                 projects = [p],
+                 variant = 'Release')
+""")
+
+test.run(chdir='work3', arguments=".")
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+vcproj = test.read(['work3', 'Test.vcproj'], 'r')
+expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work3', 'SConstruct')
+# don't compare the pickled data
+assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
+
+test.must_exist(test.workpath('work3', 'Test.sln'))
+sln = test.read(['work3', 'Test.sln'], 'r')
+expect = test.msvs_substitute(expected_slnfile, '7.0', 'work3', 'SConstruct')
 # don't compare the pickled data
 assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
 
+test.run(chdir='work3', arguments='-c .')
+
+test.must_not_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_not_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='.')
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='-c Test.sln')
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_not_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='-c Test.vcproj')
+
+test.must_not_exist(test.workpath('work3', 'Test.vcproj'))
+
 
 
 test.pass_test()
index 5b5799b9e3796d338f3ae012468a06b6de6b5b30..9cbab12886de296b303a311a3547eddcb8c5560d 100644 (file)
@@ -79,30 +79,23 @@ expected_vcprojfile = """\
 \t<Configurations>
 \t\t<Configuration
 \t\t\tName="Release|Win32"
-\t\t\tOutputDirectory="<WORKPATH>"
-\t\t\tIntermediateDirectory="<WORKPATH>"
+\t\t\tOutputDirectory=""
+\t\t\tIntermediateDirectory=""
 \t\t\tConfigurationType="0"
 \t\t\tUseOfMFC="0"
 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
 \t\t\t<Tool
 \t\t\t\tName="VCNMakeTool"
-\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct -c <WORKPATH>\Test.exe"
-\t\t\t\tRebuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct <WORKPATH>\Test.exe"
-\t\t\t\tOutput="<WORKPATH>\Test.exe"/>
+\t\t\t\tBuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct Test.exe"
+\t\t\t\tCleanCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct -c Test.exe"
+\t\t\t\tRebuildCommandLine="echo Starting SCons &amp;&amp; &quot;<PYTHON>&quot; -c &quot;<SCONS_SCRIPT_MAIN_XML>&quot; -C <WORKPATH> -f SConstruct Test.exe"
+\t\t\t\tOutput="Test.exe"/>
 \t\t</Configuration>
 \t</Configurations>
 \t<References>
 \t</References>
 \t<Files>
 \t\t<Filter
-\t\t\tName=" Source Files"
-\t\t\tFilter="cpp;c;cxx;l;y;def;odl;idl;hpj;bat">
-\t\t\t<File
-\t\t\t\tRelativePath="test.cpp">
-\t\t\t</File>
-\t\t</Filter>
-\t\t<Filter
 \t\t\tName="Header Files"
 \t\t\tFilter="h;hpp;hxx;hm;inl">
 \t\t\t<File
@@ -130,6 +123,13 @@ expected_vcprojfile = """\
 \t\t\t\tRelativePath="test.rc">
 \t\t\t</File>
 \t\t</Filter>
+\t\t<Filter
+\t\t\tName="Source Files"
+\t\t\tFilter="cpp;c;cxx;l;y;def;odl;idl;hpj;bat">
+\t\t\t<File
+\t\t\t\tRelativePath="test.cpp">
+\t\t\t</File>
+\t\t</Filter>
 \t\t<File
 \t\t\tRelativePath="<SCONSCRIPT>">
 \t\t</File>
@@ -171,15 +171,13 @@ test.run(chdir='work1', arguments="Test.vcproj")
 
 test.must_exist(test.workpath('work1', 'Test.vcproj'))
 vcproj = test.read(['work1', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.1', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_vcprojfile, '7.1', 'work1', 'SConstruct')
 # don't compare the pickled data
 assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
 
 test.must_exist(test.workpath('work1', 'Test.sln'))
 sln = test.read(['work1', 'Test.sln'], 'r')
-expect = test.msvs_substitute(expected_slnfile, '7.1', 'work1',
-                             test.workpath('work1', 'SConstruct'))
+expect = test.msvs_substitute(expected_slnfile, '7.1', 'work1', 'SConstruct')
 # don't compare the pickled data
 assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
 
@@ -210,8 +208,7 @@ python = os.path.join('$(PYTHON_ROOT)', os.path.split(sys.executable)[1])
 
 test.must_exist(test.workpath('work1', 'Test.vcproj'))
 vcproj = test.read(['work1', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.1', 'work1',
-                             test.workpath('work1', 'SConstruct'),
+expect = test.msvs_substitute(expected_vcprojfile, '7.1', 'work1', 'SConstruct',
                              python=python)
 # don't compare the pickled data
 assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
@@ -230,6 +227,18 @@ test.write(['work2', 'src', 'SConscript'], SConscript_contents)
 
 test.run(chdir='work2', arguments=".")
 
+vcproj = test.read(['work2', 'src', 'Test.vcproj'], 'r')
+expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work2', 'SConstruct')
+# don't compare the pickled data
+assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
+
+test.must_exist(test.workpath('work2', 'src', 'Test.sln'))
+sln = test.read(['work2', 'src', 'Test.sln'], 'r')
+expect = test.msvs_substitute(expected_slnfile, '7.0',
+                              os.path.join('work2', 'src'))
+# don't compare the pickled data
+assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
+
 test.must_match(['work2', 'build', 'Test.vcproj'], """\
 This is just a placeholder file.
 The real project file is here:
@@ -237,12 +246,6 @@ The real project file is here:
 """ % test.workpath('work2', 'src', 'Test.vcproj'),
                 mode='r')
 
-vcproj = test.read(['work2', 'src', 'Test.vcproj'], 'r')
-expect = test.msvs_substitute(expected_vcprojfile, '7.0', 'work2',
-                             test.workpath('work2', 'src', 'SConscript'))
-# don't compare the pickled data
-assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
-
 test.must_match(['work2', 'build', 'Test.sln'], """\
 This is just a placeholder file.
 The real workspace file is here:
@@ -250,12 +253,68 @@ The real workspace file is here:
 """ % test.workpath('work2', 'src', 'Test.sln'),
                 mode='r')
 
-test.must_exist(test.workpath('work2', 'src', 'Test.sln'))
-sln = test.read(['work2', 'src', 'Test.sln'], 'r')
-expect = test.msvs_substitute(expected_slnfile, '7.0', 'work2\\src')
+
+
+test.subdir('work3')
+
+test.write(['work3', 'SConstruct'], """\
+env=Environment(MSVS_VERSION = '7.1')
+
+testsrc = ['test.cpp']
+testincs = ['sdk.h']
+testlocalincs = ['test.h']
+testresources = ['test.rc']
+testmisc = ['readme.txt']
+
+p = env.MSVSProject(target = 'Test.vcproj',
+                    srcs = testsrc,
+                    incs = testincs,
+                    localincs = testlocalincs,
+                    resources = testresources,
+                    misc = testmisc,
+                    buildtarget = 'Test.exe',
+                    variant = 'Release',
+                    auto_build_solution = 0)
+
+env.MSVSSolution(target = 'Test.sln',
+                 slnguid = '{SLNGUID}',
+                 projects = [p],
+                 variant = 'Release')
+""")
+
+test.run(chdir='work3', arguments=".")
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+vcproj = test.read(['work3', 'Test.vcproj'], 'r')
+expect = test.msvs_substitute(expected_vcprojfile, '7.1', 'work3', 'SConstruct')
+# don't compare the pickled data
+assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)
+
+test.must_exist(test.workpath('work3', 'Test.sln'))
+sln = test.read(['work3', 'Test.sln'], 'r')
+expect = test.msvs_substitute(expected_slnfile, '7.1', 'work3', 'SConstruct')
 # don't compare the pickled data
 assert sln[:len(expect)] == expect, test.diff_substr(expect, sln)
 
+test.run(chdir='work3', arguments='-c .')
+
+test.must_not_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_not_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='.')
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='-c Test.sln')
+
+test.must_exist(test.workpath('work3', 'Test.vcproj'))
+test.must_not_exist(test.workpath('work3', 'Test.sln'))
+
+test.run(chdir='work3', arguments='-c Test.vcproj')
+
+test.must_not_exist(test.workpath('work3', 'Test.vcproj'))
+
 
 
 test.pass_test()