http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Tool / msvs.py
index e17dcfd894ae473952b3f21dd0900fe3281e2ca2..304c35ea863bc78f9dd3eb40c106076837156ec1 100644 (file)
@@ -30,15 +30,19 @@ selection method.
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #
+from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.compat
+
 import base64
-import md5
-import os.path
+import hashlib
+import ntpath
+import os
+# compat layer imports "cPickle" for us if it's available.
 import pickle
 import re
-import string
 import sys
 
 import SCons.Builder
@@ -48,28 +52,18 @@ import SCons.Script.SConscript
 import SCons.Util
 import SCons.Warnings
 
+from MSCommon import msvc_exists, msvc_setup_env_once
+from SCons.Defaults import processDefines
+
 ##############################################################################
 # Below here are the classes and functions for generation of
 # DSP/DSW/SLN/VCPROJ files.
 ##############################################################################
 
-def _hexdigest(s):
-    """Return a string as a string of hex characters.
-    """
-    # NOTE:  This routine is a method in the Python 2.0 interface
-    # of the native md5 module, but we want SCons to operate all
-    # the way back to at least Python 1.5.2, which doesn't have it.
-    h = string.hexdigits
-    r = ''
-    for c in s:
-        i = ord(c)
-        r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
-    return r
-
 def xmlify(s):
-    s = string.replace(s, "&", "&") # do this first
-    s = string.replace(s, "'", "'")
-    s = string.replace(s, '"', """)
+    s = s.replace("&", "&") # do this first
+    s = s.replace("'", "'")
+    s = s.replace('"', """)
     return s
 
 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
@@ -79,7 +73,12 @@ def _generateGUID(slnfile, name):
     based on the MD5 signatures of the sln filename plus the name of
     the project.  It basically just needs to be unique, and not
     change with each invocation."""
-    solution = _hexdigest(md5.new(str(slnfile)+str(name)).digest()).upper()
+    m = hashlib.md5()
+    # Normalize the slnfile path to a Windows path (\ separators) so
+    # the generated file has a consistent GUID even if we generate
+    # it on a non-Windows platform.
+    m.update(ntpath.normpath(str(slnfile)) + str(name))
+    solution = m.hexdigest().upper()
     # convert most of the signature to GUID form (discard the rest)
     solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
     return solution
@@ -105,7 +104,7 @@ def msvs_parse_version(s):
 # which works regardless of how we were invoked.
 def getExecScriptMain(env, xml=None):
     scons_home = env.get('SCONS_HOME')
-    if not scons_home and os.environ.has_key('SCONS_LIB_DIR'):
+    if not scons_home and 'SCONS_LIB_DIR' in os.environ:
         scons_home = os.environ['SCONS_LIB_DIR']
     if scons_home:
         exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
@@ -150,7 +149,7 @@ def makeHierarchy(sources):
         if len(path):
             dict = hierarchy
             for part in path[:-1]:
-                if not dict.has_key(part):
+                if part not in dict:
                     dict[part] = {}
                 dict = dict[part]
             dict[path[-1]] = file
@@ -177,23 +176,21 @@ class _DSPGenerator:
         else:
             self.dspabs = get_abspath()
 
-        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 'variant' not in env:
+            raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
+                  "'Release') to create an MSVSProject.")
         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:
+        if 'buildtarget' not in env 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."
+                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):
@@ -205,17 +202,16 @@ class _DSPGenerator:
         if len(buildtarget) == 1:
             bt = buildtarget[0]
             buildtarget = []
-            for v in variants:
+            for _ in variants:
                 buildtarget.append(bt)
 
-        if not env.has_key('outdir') or env['outdir'] == None:
+        if 'outdir' not in env 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."
+                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):
@@ -230,14 +226,13 @@ class _DSPGenerator:
             for v in variants:
                 outdir.append(s)
 
-        if not env.has_key('runfile') or env['runfile'] == None:
+        if 'runfile' not in env or env['runfile'] == None:
             runfile = buildtarget[-1:]
         elif SCons.Util.is_String(env['runfile']):
             runfile = [env['runfile']]
         elif SCons.Util.is_List(env['runfile']):
             if len(env['runfile']) != len(variants):
-                raise SCons.Errors.InternalError, \
-                    "Sizes of 'runfile' and 'variant' lists must be the same."
+                raise SCons.Errors.InternalError("Sizes of 'runfile' and 'variant' lists must be the same.")
             runfile = []
             for s in env['runfile']:
                 if SCons.Util.is_String(s):
@@ -258,7 +253,7 @@ class _DSPGenerator:
 
         self.env = env
 
-        if self.env.has_key('name'):
+        if 'name' in self.env:
             self.name = self.env['name']
         else:
             self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
@@ -278,14 +273,14 @@ class _DSPGenerator:
         self.configs = {}
 
         self.nokeep = 0
-        if env.has_key('nokeep') and env['variant'] != 0:
+        if 'nokeep' in env and env['variant'] != 0:
             self.nokeep = 1
 
         if self.nokeep == 0 and os.path.exists(self.dspabs):
             self.Parse()
 
         for t in zip(sourcenames,self.srcargs):
-            if self.env.has_key(t[1]):
+            if t[1] in self.env:
                 if SCons.Util.is_List(self.env[t[1]]):
                     for i in self.env[t[1]]:
                         if not i in self.sources[t[0]]:
@@ -297,7 +292,7 @@ class _DSPGenerator:
         for n in sourcenames:
             self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
 
-        def AddConfig(variant, buildtarget, outdir, runfile, cmdargs):
+        def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
             config = Config()
             config.buildtarget = buildtarget
             config.outdir = outdir
@@ -316,7 +311,7 @@ class _DSPGenerator:
             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
 
         for i in range(len(variants)):
-            AddConfig(variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
+            AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
 
         self.platforms = []
         for key in self.configs.keys():
@@ -354,8 +349,7 @@ class _GenerateV6DSP(_DSPGenerator):
 
     def PrintHeader(self):
         # pick a default config
-        confkeys = self.configs.keys()
-        confkeys.sort()
+        confkeys = sorted(self.configs.keys())
 
         name = self.name
         confkey = confkeys[0]
@@ -375,8 +369,7 @@ class _GenerateV6DSP(_DSPGenerator):
                         '# PROP Scc_LocalPath ""\n\n')
 
         first = 1
-        confkeys = self.configs.keys()
-        confkeys.sort()
+        confkeys = sorted(self.configs.keys())
         for kind in confkeys:
             outdir = self.configs[kind].outdir
             buildtarget = self.configs[kind].buildtarget
@@ -386,7 +379,7 @@ 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')
+            env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
             if not env_has_buildtarget:
                 self.env['MSVSBUILDTARGET'] = buildtarget
 
@@ -394,6 +387,8 @@ class _GenerateV6DSP(_DSPGenerator):
             for base in ("BASE ",""):
                 self.file.write('# PROP %sUse_MFC 0\n'
                                 '# PROP %sUse_Debug_Libraries ' % (base, base))
+                # TODO(1.5):
+                #if kind.lower().find('debug') < 0:
                 if kind.lower().find('debug') < 0:
                     self.file.write('0\n')
                 else:
@@ -451,7 +446,9 @@ class _GenerateV6DSP(_DSPGenerator):
                 continue # skip empty groups
 
             self.file.write('# Begin Group "' + kind + '"\n\n')
-            typelist = categories[kind].replace('|',';')
+            # TODO(1.5)
+            #typelist = categories[kind].replace('|', ';')
+            typelist = categories[kind].replace('|', ';')
             self.file.write('# PROP Default_Filter "' + typelist + '"\n')
 
             for file in self.sources[kind]:
@@ -474,6 +471,8 @@ class _GenerateV6DSP(_DSPGenerator):
 
         line = dspfile.readline()
         while line:
+            # TODO(1.5):
+            #if line.find("# End Project") > -1:
             if line.find("# End Project") > -1:
                 break
             line = dspfile.readline()
@@ -518,7 +517,7 @@ class _GenerateV6DSP(_DSPGenerator):
         try:
             self.file = open(self.dspabs,'w')
         except IOError, detail:
-            raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
+            raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
         else:
             self.PrintHeader()
             self.PrintProject()
@@ -564,7 +563,7 @@ V8DSPHeader = """\
 
 V8DSPConfiguration = """\
 \t\t<Configuration
-\t\t\tName="%(variant)s|Win32"
+\t\t\tName="%(variant)s|%(platform)s"
 \t\t\tConfigurationType="0"
 \t\t\tUseOfMFC="0"
 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
@@ -575,8 +574,8 @@ V8DSPConfiguration = """\
 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
 \t\t\t\tCleanCommandLine="%(cleancmd)s"
 \t\t\t\tOutput="%(runfile)s"
-\t\t\t\tPreprocessorDefinitions=""
-\t\t\t\tIncludeSearchPath=""
+\t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
+\t\t\t\tIncludeSearchPath="%(includepath)s"
 \t\t\t\tForcedIncludes=""
 \t\t\t\tAssemblySearchPath=""
 \t\t\t\tForcedUsingAssemblies=""
@@ -643,8 +642,7 @@ class _GenerateV7DSP(_DSPGenerator):
     def PrintProject(self):
         self.file.write('\t<Configurations>\n')
 
-        confkeys = self.configs.keys()
-        confkeys.sort()
+        confkeys = sorted(self.configs.keys())
         for kind in confkeys:
             variant = self.configs[kind].variant
             platform = self.configs[kind].platform
@@ -653,7 +651,7 @@ class _GenerateV7DSP(_DSPGenerator):
             runfile     = self.configs[kind].runfile
             cmdargs = self.configs[kind].cmdargs
 
-            env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET')
+            env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
             if not env_has_buildtarget:
                 self.env['MSVSBUILDTARGET'] = buildtarget
 
@@ -666,6 +664,12 @@ class _GenerateV7DSP(_DSPGenerator):
             rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
             cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
 
+            # TODO(1.5)
+            #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', [])))
+            #includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
+            preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
+            includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
+
             if not env_has_buildtarget:
                 del self.env['MSVSBUILDTARGET']
 
@@ -690,6 +694,29 @@ class _GenerateV7DSP(_DSPGenerator):
             pdata = base64.encodestring(pdata)
             self.file.write(pdata + '-->\n')
 
+    def printSources(self, hierarchy, commonprefix):
+        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))
+                self.printSources(value, commonprefix)
+                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))
+
     def PrintSourceFiles(self):
         categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
                       'Header Files': 'h;hpp;hxx;hm;inl',
@@ -701,50 +728,33 @@ class _GenerateV7DSP(_DSPGenerator):
 
         cats = categories.keys()
         cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
-        cats = filter(lambda k, s=self: s.sources[k], cats)
+        cats = [k for k in cats if self.sources[k]]
         for kind in cats:
             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, commonprefix):
-                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, commonprefix)
-                        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:
-                s = map(os.path.normpath, sources)
-                cp = os.path.commonprefix(s)
+                s = list(map(os.path.normpath, sources))
+                # take the dirname because the prefix may include parts
+                # of the filenames (e.g. if you have 'dir\abcd' and
+                # 'dir\acde' then the cp will be 'dir\a' )
+                cp = os.path.dirname( os.path.commonprefix(s) )
                 if cp and s[0][len(cp)] == os.sep:
-                    sources = map(lambda s, l=len(cp): s[l:], sources)
+                    # +1 because the filename starts after the separator
+                    sources = [s[len(cp)+1:] for s in sources]
                     commonprefix = cp
+            elif len(sources) == 1:
+                commonprefix = os.path.dirname( sources[0] )
+                sources[0] = os.path.basename( sources[0] )
 
             hierarchy = makeHierarchy(sources)
-            printSources(hierarchy, commonprefix=commonprefix)
+            self.printSources(hierarchy, commonprefix=commonprefix)
 
             if len(cats)>1:
                 self.file.write('\t\t</Filter>\n')
@@ -766,6 +776,8 @@ class _GenerateV7DSP(_DSPGenerator):
 
         line = dspfile.readline()
         while line:
+            # TODO(1.5)
+            #if line.find('<!-- SCons Data:') > -1:
             if line.find('<!-- SCons Data:') > -1:
                 break
             line = dspfile.readline()
@@ -809,7 +821,7 @@ class _GenerateV7DSP(_DSPGenerator):
         try:
             self.file = open(self.dspabs,'w')
         except IOError, detail:
-            raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
+            raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
         else:
             self.PrintHeader()
             self.PrintProject()
@@ -821,20 +833,17 @@ class _DSWGenerator:
         self.dswfile = os.path.normpath(str(dswfile))
         self.env = env
 
-        if not env.has_key('projects'):
-            raise SCons.Errors.UserError, \
-                "You must specify a 'projects' argument to create an MSVSSolution."
+        if 'projects' not in env:
+            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."
+            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."
-        self.dspfiles = map(str, projects)
+            raise SCons.Errors.UserError("You must specify at least one project to create an MSVSSolution.")
+        self.dspfiles = list(map(str, projects))
 
-        if self.env.has_key('name'):
+        if 'name' in self.env:
             self.name = self.env['name']
         else:
             self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
@@ -859,7 +868,7 @@ class _GenerateV7DSW(_DSWGenerator):
         if self.version_num >= 8.0:
             self.versionstr = '9.00'
 
-        if env.has_key('slnguid') and env['slnguid']:
+        if 'slnguid' in env and env['slnguid']:
             self.slnguid = env['slnguid']
         else:
             self.slnguid = _generateGUID(dswfile, self.name)
@@ -867,13 +876,13 @@ class _GenerateV7DSW(_DSWGenerator):
         self.configs = {}
 
         self.nokeep = 0
-        if env.has_key('nokeep') and env['variant'] != 0:
+        if 'nokeep' in env and env['variant'] != 0:
             self.nokeep = 1
 
         if self.nokeep == 0 and os.path.exists(self.dswfile):
             self.Parse()
 
-        def AddConfig(variant):
+        def AddConfig(self, variant, dswfile=dswfile):
             config = Config()
 
             match = re.match('(.*)\|(.*)', variant)
@@ -887,15 +896,14 @@ class _GenerateV7DSW(_DSWGenerator):
             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."
+        if 'variant' not in env:
+            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'])
+            AddConfig(self, env['variant'])
         elif SCons.Util.is_List(env['variant']):
             for variant in env['variant']:
-                AddConfig(variant)
+                AddConfig(self, variant)
 
         self.platforms = []
         for key in self.configs.keys():
@@ -953,11 +961,11 @@ class _GenerateV7DSW(_DSWGenerator):
         self.file.write('Global\n')
 
         env = self.env
-        if env.has_key('MSVS_SCC_PROVIDER'):
+        if 'MSVS_SCC_PROVIDER' in env:
             dspfile_base = os.path.basename(self.dspfile)
             slnguid = self.slnguid
             scc_provider = env.get('MSVS_SCC_PROVIDER', '')
-            scc_provider = string.replace(scc_provider, ' ', r'\u0020')
+            scc_provider = scc_provider.replace(' ', r'\u0020')
             scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
             # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
             scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
@@ -983,8 +991,7 @@ class _GenerateV7DSW(_DSWGenerator):
         else:
             self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
 
-        confkeys = self.configs.keys()
-        confkeys.sort()
+        confkeys = sorted(self.configs.keys())
         cnt = 0
         for name in confkeys:
             variant = self.configs[name].variant
@@ -1004,7 +1011,6 @@ class _GenerateV7DSW(_DSWGenerator):
             self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
 
         for name in confkeys:
-            name = name
             variant = self.configs[name].variant
             platform = self.configs[name].platform
             if self.version_num >= 8.0:
@@ -1013,8 +1019,10 @@ class _GenerateV7DSW(_DSWGenerator):
                     self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
                                     '\t\t%s.%s|%s.Build.0 = %s|%s\n'  % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
             else:
-                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))
+                for p in self.dspfiles:
+                    guid = _generateGUID(p, '')
+                    self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
+                                    '\t\t%s.%s.Build.0 = %s|%s\n'  %(guid,variant,variant,platform,guid,variant,variant,platform))
 
         self.file.write('\tEndGlobalSection\n')
 
@@ -1037,7 +1045,7 @@ class _GenerateV7DSW(_DSWGenerator):
         try:
             self.file = open(self.dswfile,'w')
         except IOError, detail:
-            raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
+            raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
         else:
             self.PrintSolution()
             self.file.close()
@@ -1086,7 +1094,7 @@ class _GenerateV6DSW(_DSWGenerator):
         try:
             self.file = open(self.dswfile,'w')
         except IOError, detail:
-            raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
+            raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
         else:
             self.PrintWorkspace()
             self.file.close()
@@ -1096,7 +1104,7 @@ def GenerateDSP(dspfile, source, env):
     """Generates a Project file based on the version of MSVS that is being used"""
 
     version_num = 6.0
-    if env.has_key('MSVS_VERSION'):
+    if 'MSVS_VERSION' in env:
         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
     if version_num >= 7.0:
         g = _GenerateV7DSP(dspfile, source, env)
@@ -1109,7 +1117,7 @@ def GenerateDSW(dswfile, source, env):
     """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
 
     version_num = 6.0
-    if env.has_key('MSVS_VERSION'):
+    if 'MSVS_VERSION' in env:
         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
     if version_num >= 7.0:
         g = _GenerateV7DSW(dswfile, source, env)
@@ -1124,346 +1132,6 @@ def GenerateDSW(dswfile, source, env):
 # DSP/DSW/SLN/VCPROJ files.
 ##############################################################################
 
-def get_default_visualstudio_version(env):
-    """Returns the version set in the env, or the latest version
-    installed, if it can find it, or '6.0' if all else fails.  Also
-    updated the environment with what it found."""
-
-    version = '6.0'
-    versions = [version]
-
-    if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
-        env['MSVS'] = {}    
-
-        if env['MSVS'].has_key('VERSIONS'):
-            versions = env['MSVS']['VERSIONS']
-        elif SCons.Util.can_read_reg:
-            v = get_visualstudio_versions()
-            if v:
-                versions = v
-        if env.has_key('MSVS_VERSION'):
-            version = env['MSVS_VERSION']
-        else:
-            version = versions[0] #use highest version by default
-
-        env['MSVS_VERSION'] = version
-        env['MSVS']['VERSIONS'] = versions
-        env['MSVS']['VERSION'] = version
-    else:
-        version = env['MSVS']['VERSION']
-
-    return version
-
-def get_visualstudio_versions():
-    """
-    Get list of visualstudio versions from the Windows registry.
-    Returns a list of strings containing version numbers.  An empty list
-    is returned if we were unable to accees the register (for example,
-    we couldn't import the registry-access module) or the appropriate
-    registry keys weren't found.
-    """
-
-    if not SCons.Util.can_read_reg:
-        return []
-
-    HLM = SCons.Util.HKEY_LOCAL_MACHINE
-    KEYS = {
-        r'Software\Microsoft\VisualStudio'      : '',
-        r'Software\Microsoft\VCExpress'         : 'Exp',
-    }
-    L = []
-    for K, suite_suffix in KEYS.items():
-        try:
-            k = SCons.Util.RegOpenKeyEx(HLM, K)
-            i = 0
-            while 1:
-                try:
-                    p = SCons.Util.RegEnumKey(k,i)
-                except SCons.Util.RegError:
-                    break
-                i = i + 1
-                if not p[0] in '123456789' or p in L:
-                    continue
-                # Only add this version number if there is a valid
-                # registry structure (includes the "Setup" key),
-                # and at least some of the correct directories
-                # exist.  Sometimes VS uninstall leaves around
-                # some registry/filesystem turds that we don't
-                # want to trip over.  Also, some valid registry
-                # entries are MSDN entries, not MSVS ('7.1',
-                # notably), and we want to skip those too.
-                try:
-                    SCons.Util.RegOpenKeyEx(HLM, K + '\\' + p + '\\Setup')
-                except SCons.Util.RegError:
-                    continue
-
-                id = []
-                idk = SCons.Util.RegOpenKeyEx(HLM, K + '\\' + p)
-                # This is not always here -- it only exists if the
-                # user installed into a non-standard location (at
-                # least in VS6 it works that way -- VS7 seems to
-                # always write it)
-                try:
-                    id = SCons.Util.RegQueryValueEx(idk, 'InstallDir')
-                except SCons.Util.RegError:
-                    pass
-
-                # If the InstallDir key doesn't exist,
-                # then we check the default locations.
-                # Note: The IDE's executable is not devenv.exe for VS8 Express.
-                if not id or not id[0]:
-                    files_dir = SCons.Platform.win32.get_program_files_dir()
-                    version_num, suite = msvs_parse_version(p)
-                    if version_num < 7.0:
-                        vs = r'Microsoft Visual Studio\Common\MSDev98'
-                    elif version_num < 8.0:
-                        vs = r'Microsoft Visual Studio .NET\Common7\IDE'
-                    else:
-                        vs = r'Microsoft Visual Studio 8\Common7\IDE'
-                    id = [ os.path.join(files_dir, vs) ]
-                if os.path.exists(id[0]):
-                    L.append(p + suite_suffix)
-        except SCons.Util.RegError:
-            pass
-
-    if not L:
-        return []
-
-    # This is a hack to get around the fact that certain Visual Studio
-    # patches place a "6.1" version in the registry, which does not have
-    # any of the keys we need to find include paths, install directories,
-    # etc.  Therefore we ignore it if it is there, since it throws all
-    # other logic off.
-    try:
-        L.remove("6.1")
-    except ValueError:
-        pass
-
-    L.sort()
-    L.reverse()
-
-    return L
-
-def get_default_visualstudio8_suite(env):
-    """
-    Returns the Visual Studio 2005 suite identifier set in the env, or the
-    highest suite installed.
-    """
-    if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
-        env['MSVS'] = {}
-
-    if env.has_key('MSVS_SUITE'):
-        suite = env['MSVS_SUITE'].upper()
-        suites = [suite]
-    else:
-        suite = 'EXPRESS'
-        suites = [suite]
-        if SCons.Util.can_read_reg:
-            suites = get_visualstudio8_suites()
-            if suites:
-                suite = suites[0] #use best suite by default
-
-    env['MSVS_SUITE'] = suite
-    env['MSVS']['SUITES'] = suites
-    env['MSVS']['SUITE'] = suite
-
-    return suite
-
-def get_visualstudio8_suites():
-    """
-    Returns a sorted list of all installed Visual Studio 2005 suites found
-    in the registry. The highest version should be the first entry in the list.
-    """
-
-    suites = []
-
-    # Detect Standard, Professional and Team edition
-    try:
-        idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
-            r'Software\Microsoft\VisualStudio\8.0')
-        id = SCons.Util.RegQueryValueEx(idk, 'InstallDir')
-        editions = { 'PRO': r'Setup\VS\Pro' }       # ToDo: add standard and team editions
-        edition_name = 'STD'
-        for name, key_suffix in editions.items():
-            try:
-                idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
-                    r'Software\Microsoft\VisualStudio\8.0' + '\\' + key_suffix )
-                edition_name = name
-            except SCons.Util.RegError:
-                pass
-            suites.append(edition_name)
-    except SCons.Util.RegError:
-        pass
-
-    # Detect Expression edition
-    try:
-        idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
-            r'Software\Microsoft\VCExpress\8.0')
-        id = SCons.Util.RegQueryValueEx(idk, 'InstallDir')
-        suites.append('EXPRESS')
-    except SCons.Util.RegError:
-        pass
-
-    return suites
-
-def is_msvs_installed():
-    """
-    Check the registry for an installed visual studio.
-    """
-    try:
-        v = SCons.Tool.msvs.get_visualstudio_versions()
-        return v
-    except (SCons.Util.RegError, SCons.Errors.InternalError):
-        return 0
-
-def get_msvs_install_dirs(version = None):
-    """
-    Get installed locations for various msvc-related products, like the .NET SDK
-    and the Platform SDK.
-    """
-
-    if not SCons.Util.can_read_reg:
-        return {}
-
-    if not version:
-        versions = get_visualstudio_versions()
-        if versions:
-            version = versions[0] #use highest version by default
-        else:
-            return {}
-
-    version_num, suite = msvs_parse_version(version)
-
-    K = 'Software\\Microsoft\\VisualStudio\\' + str(version_num)
-    if (version_num >= 8.0):
-        try:
-            SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K )
-        except SCons.Util.RegError:
-            K = 'Software\\Microsoft\\VCExpress\\' + str(version_num)
-
-    # vc++ install dir
-    rv = {}
-    if (version_num < 7.0):
-        key = K + r'\Setup\Microsoft Visual C++\ProductDir'
-    else:
-        key = K + r'\Setup\VC\ProductDir'
-    try:
-        (rv['VCINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, key)
-    except SCons.Util.RegError:
-        pass
-
-    # visual studio install dir
-    if (version_num < 7.0):
-        try:
-            (rv['VSINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
-                                                             K + r'\Setup\Microsoft Visual Studio\ProductDir')
-        except SCons.Util.RegError:
-            pass
-
-        if not rv.has_key('VSINSTALLDIR') or not rv['VSINSTALLDIR']:
-            if rv.has_key('VCINSTALLDIR') and rv['VCINSTALLDIR']:
-                rv['VSINSTALLDIR'] = os.path.dirname(rv['VCINSTALLDIR'])
-            else:
-                rv['VSINSTALLDIR'] = os.path.join(SCons.Platform.win32.get_program_files_dir(),'Microsoft Visual Studio')
-    else:
-        try:
-            (rv['VSINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
-                                                             K + r'\Setup\VS\ProductDir')
-        except SCons.Util.RegError:
-            pass
-
-    # .NET framework install dir
-    try:
-        (rv['FRAMEWORKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
-            r'Software\Microsoft\.NETFramework\InstallRoot')
-    except SCons.Util.RegError:
-        pass
-
-    if rv.has_key('FRAMEWORKDIR'):
-        # try and enumerate the installed versions of the .NET framework.
-        contents = os.listdir(rv['FRAMEWORKDIR'])
-        l = re.compile('v[0-9]+.*')
-        versions = []
-        for entry in contents:
-            if l.match(entry):
-                versions.append(entry)
-
-        def versrt(a,b):
-            # since version numbers aren't really floats...
-            aa = a[1:]
-            bb = b[1:]
-            aal = aa.split('.')
-            bbl = bb.split('.')
-            c = int(bbl[0]) - int(aal[0])
-            if c == 0:
-                c = int(bbl[1]) - int(aal[1])
-                if c == 0:
-                    c = int(bbl[2]) - int(aal[2])
-            return c
-
-        versions.sort(versrt)
-
-        rv['FRAMEWORKVERSIONS'] = versions
-        # assume that the highest version is the latest version installed
-        rv['FRAMEWORKVERSION'] = versions[0]
-
-    # .NET framework SDK install dir
-    try:
-        if rv.has_key('FRAMEWORKVERSION') and rv['FRAMEWORKVERSION'][:4] == 'v1.1':
-            key = r'Software\Microsoft\.NETFramework\sdkInstallRootv1.1'
-        else:
-            key = r'Software\Microsoft\.NETFramework\sdkInstallRoot'
-
-        (rv['FRAMEWORKSDKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,key)
-
-    except SCons.Util.RegError:
-        pass
-
-    # MS Platform SDK dir
-    try:
-        (rv['PLATFORMSDKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
-            r'Software\Microsoft\MicrosoftSDK\Directories\Install Dir')
-    except SCons.Util.RegError:
-        pass
-
-    if rv.has_key('PLATFORMSDKDIR'):
-        # if we have a platform SDK, try and get some info on it.
-        vers = {}
-        try:
-            loc = r'Software\Microsoft\MicrosoftSDK\InstalledSDKs'
-            k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,loc)
-            i = 0
-            while 1:
-                try:
-                    key = SCons.Util.RegEnumKey(k,i)
-                    sdk = SCons.Util.RegOpenKeyEx(k,key)
-                    j = 0
-                    name = ''
-                    date = ''
-                    version = ''
-                    while 1:
-                        try:
-                            (vk,vv,t) = SCons.Util.RegEnumValue(sdk,j)
-                            if vk.lower() == 'keyword':
-                                name = vv
-                            if vk.lower() == 'propagation_date':
-                                date = vv
-                            if vk.lower() == 'version':
-                                version = vv
-                            j = j + 1
-                        except SCons.Util.RegError:
-                            break
-                    if name:
-                        vers[name] = (date, version)
-                    i = i + 1
-                except SCons.Util.RegError:
-                    break
-            rv['PLATFORMSDK_MODULES'] = vers
-        except SCons.Util.RegError:
-            pass
-
-    return rv
-
 def GetMSVSProjectSuffix(target, source, env, for_signature):
      return env['MSVS']['PROJECTSUFFIX']
 
@@ -1475,7 +1143,7 @@ def GenerateProject(target, source, env):
     builddspfile = target[0]
     dspfile = builddspfile.srcnode()
 
-    # this detects whether or not we're using a BuildDir
+    # this detects whether or not we're using a VariantDir
     if not dspfile is builddspfile:
         try:
             bdsp = open(str(builddspfile), "w+")
@@ -1525,7 +1193,7 @@ def projectEmitter(target, source, env):
         source = source + env.subst('$MSVSSCONSCOM', 1)
         source = source + env.subst('$MSVSENCODING', 1)
 
-        if env.has_key('buildtarget') and env['buildtarget'] != None:
+        if 'buildtarget' in env and env['buildtarget'] != None:
             if SCons.Util.is_String(env['buildtarget']):
                 source = source + ' "%s"' % env['buildtarget']
             elif SCons.Util.is_List(env['buildtarget']):
@@ -1534,14 +1202,12 @@ def projectEmitter(target, source, env):
                         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"
+                        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"
+                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 'outdir' in env and env['outdir'] != None:
             if SCons.Util.is_String(env['outdir']):
                 source = source + ' "%s"' % env['outdir']
             elif SCons.Util.is_List(env['outdir']):
@@ -1550,20 +1216,18 @@ def projectEmitter(target, source, env):
                         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"
+                        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"
+                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 'name' in env:
             if SCons.Util.is_String(env['name']):
                 source = source + ' "%s"' % env['name']
             else:
-                raise SCons.Errors.InternalError, "name must be a string"
+                raise SCons.Errors.InternalError("name must be a string")
 
-        if env.has_key('variant'):
+        if 'variant' in env:
             if SCons.Util.is_String(env['variant']):
                 source = source + ' "%s"' % env['variant']
             elif SCons.Util.is_List(env['variant']):
@@ -1571,14 +1235,14 @@ def projectEmitter(target, source, env):
                     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"
+                        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"
+                raise SCons.Errors.InternalError("variant must be a string or a list of strings")
         else:
-            raise SCons.Errors.InternalError, "variant must be specified"
+            raise SCons.Errors.InternalError("variant must be specified")
 
         for s in _DSPGenerator.srcargs:
-            if env.has_key(s):
+            if s in env:
                 if SCons.Util.is_String(env[s]):
                     source = source + ' "%s' % env[s]
                 elif SCons.Util.is_List(env[s]):
@@ -1586,9 +1250,9 @@ def projectEmitter(target, source, env):
                         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"
+                            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"
+                    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)]
@@ -1620,13 +1284,13 @@ def solutionEmitter(target, source, env):
     if not source:
         source = 'sln_inputs:'
 
-        if env.has_key('name'):
+        if 'name' in env:
             if SCons.Util.is_String(env['name']):
                 source = source + ' "%s"' % env['name']
             else:
-                raise SCons.Errors.InternalError, "name must be a string"
+                raise SCons.Errors.InternalError("name must be a string")
 
-        if env.has_key('variant'):
+        if 'variant' in env:
             if SCons.Util.is_String(env['variant']):
                 source = source + ' "%s"' % env['variant']
             elif SCons.Util.is_List(env['variant']):
@@ -1634,19 +1298,19 @@ def solutionEmitter(target, source, env):
                     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"
+                        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"
+                raise SCons.Errors.InternalError("variant must be a string or a list of strings")
         else:
-            raise SCons.Errors.InternalError, "variant must be specified"
+            raise SCons.Errors.InternalError("variant must be specified")
 
-        if env.has_key('slnguid'):
+        if 'slnguid' in env:
             if SCons.Util.is_String(env['slnguid']):
                 source = source + ' "%s"' % env['slnguid']
             else:
-                raise SCons.Errors.InternalError, "slnguid must be a string"
+                raise SCons.Errors.InternalError("slnguid must be a string")
 
-        if env.has_key('projects'):
+        if 'projects' in env:
             if SCons.Util.is_String(env['projects']):
                 source = source + ' "%s"' % env['projects']
             elif SCons.Util.is_List(env['projects']):
@@ -1702,24 +1366,20 @@ def generate(env):
     env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
     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['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
+    env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
+    env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
     env['MSVSENCODING'] = 'Windows-1252'
 
-    try:
-        version = get_default_visualstudio_version(env)
-        # keep a record of some of the MSVS info so the user can use it.
-        dirs = get_msvs_install_dirs(version)
-        env['MSVS'].update(dirs)
-    except (SCons.Util.RegError, SCons.Errors.InternalError):
-        # we don't care if we can't do this -- if we can't, it's
-        # because we don't have access to the registry, or because the
-        # tools aren't installed.  In either case, the user will have to
-        # find them on their own.
-        pass
+    # Set-up ms tools paths for default version
+    msvc_setup_env_once(env)
 
-    version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
+    if 'MSVS_VERSION' in env:
+        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
+    else:
+        (version_num, suite) = (7.0, None) # guess at a default
+    if 'MSVS' not in env:
+        env['MSVS'] = {}
     if (version_num < 7.0):
         env['MSVS']['PROJECTSUFFIX']  = '.dsp'
         env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
@@ -1734,19 +1394,10 @@ def generate(env):
     env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
 
 def exists(env):
-    try:
-        v = SCons.Tool.msvs.get_visualstudio_versions()
-    except (SCons.Util.RegError, SCons.Errors.InternalError):
-        pass
+    return msvc_exists()
 
-    if not v:
-        version_num = 6.0
-        if env.has_key('MSVS_VERSION'):
-            version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
-        if version_num >= 7.0:
-            return env.Detect('devenv')
-        else:
-            return env.Detect('msdev')
-    else:
-        # there's at least one version of MSVS installed.
-        return 1
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: