Toplevel directory cleanup
[senf.git] / tools / scons-1.2.0 / engine / SCons / Tool / msvs.py
1 """SCons.Tool.msvs
2
3 Tool-specific initialization for Microsoft Visual Studio project files.
4
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
7 selection method.
8
9 """
10
11 #
12 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
13 #
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
21 #
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #
33
34 __revision__ = "src/engine/SCons/Tool/msvs.py 3842 2008/12/20 22:59:52 scons"
35
36 import base64
37 import hashlib
38 import os.path
39 import pickle
40 import re
41 import string
42 import sys
43
44 import SCons.Builder
45 import SCons.Node.FS
46 import SCons.Platform.win32
47 import SCons.Script.SConscript
48 import SCons.Util
49 import SCons.Warnings
50
51 ##############################################################################
52 # Below here are the classes and functions for generation of
53 # DSP/DSW/SLN/VCPROJ files.
54 ##############################################################################
55
56 def _hexdigest(s):
57     """Return a string as a string of hex characters.
58     """
59     # NOTE:  This routine is a method in the Python 2.0 interface
60     # of the native md5 module, but we want SCons to operate all
61     # the way back to at least Python 1.5.2, which doesn't have it.
62     h = string.hexdigits
63     r = ''
64     for c in s:
65         i = ord(c)
66         r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
67     return r
68
69 def xmlify(s):
70     s = string.replace(s, "&", "&") # do this first
71     s = string.replace(s, "'", "'")
72     s = string.replace(s, '"', """)
73     return s
74
75 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
76
77 def _generateGUID(slnfile, name):
78     """This generates a dummy GUID for the sln file to use.  It is
79     based on the MD5 signatures of the sln filename plus the name of
80     the project.  It basically just needs to be unique, and not
81     change with each invocation."""
82     m = hashlib.md5()
83     m.update(str(slnfile) + str(name))
84     # TODO(1.5)
85     #solution = m.hexdigest().upper()
86     solution = string.upper(_hexdigest(m.digest()))
87     # convert most of the signature to GUID form (discard the rest)
88     solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
89     return solution
90
91 version_re = re.compile(r'(\d+\.\d+)(.*)')
92
93 def msvs_parse_version(s):
94     """
95     Split a Visual Studio version, which may in fact be something like
96     '7.0Exp', into is version number (returned as a float) and trailing
97     "suite" portion.
98     """
99     num, suite = version_re.match(s).groups()
100     return float(num), suite
101
102 # This is how we re-invoke SCons from inside MSVS Project files.
103 # The problem is that we might have been invoked as either scons.bat
104 # or scons.py.  If we were invoked directly as scons.py, then we could
105 # use sys.argv[0] to find the SCons "executable," but that doesn't work
106 # if we were invoked as scons.bat, which uses "python -c" to execute
107 # things and ends up with "-c" as sys.argv[0].  Consequently, we have
108 # the MSVS Project file invoke SCons the same way that scons.bat does,
109 # which works regardless of how we were invoked.
110 def getExecScriptMain(env, xml=None):
111     scons_home = env.get('SCONS_HOME')
112     if not scons_home and os.environ.has_key('SCONS_LIB_DIR'):
113         scons_home = os.environ['SCONS_LIB_DIR']
114     if scons_home:
115         exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
116     else:
117         version = SCons.__version__
118         exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%(version)s'), join(sys.prefix, 'scons-%(version)s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % locals()
119     if xml:
120         exec_script_main = xmlify(exec_script_main)
121     return exec_script_main
122
123 # The string for the Python executable we tell the Project file to use
124 # is either sys.executable or, if an external PYTHON_ROOT environment
125 # variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
126 # pluck the actual executable name from sys.executable).
127 try:
128     python_root = os.environ['PYTHON_ROOT']
129 except KeyError:
130     python_executable = sys.executable
131 else:
132     python_executable = os.path.join('$$(PYTHON_ROOT)',
133                                      os.path.split(sys.executable)[1])
134
135 class Config:
136     pass
137
138 def splitFully(path):
139     dir, base = os.path.split(path)
140     if dir and dir != '' and dir != path:
141         return splitFully(dir)+[base]
142     if base == '':
143         return []
144     return [base]
145
146 def makeHierarchy(sources):
147     '''Break a list of files into a hierarchy; for each value, if it is a string,
148        then it is a file.  If it is a dictionary, it is a folder.  The string is
149        the original path of the file.'''
150
151     hierarchy = {}
152     for file in sources:
153         path = splitFully(file)
154         if len(path):
155             dict = hierarchy
156             for part in path[:-1]:
157                 if not dict.has_key(part):
158                     dict[part] = {}
159                 dict = dict[part]
160             dict[path[-1]] = file
161         #else:
162         #    print 'Warning: failed to decompose path for '+str(file)
163     return hierarchy
164
165 class _DSPGenerator:
166     """ Base class for DSP generators """
167
168     srcargs = [
169         'srcs',
170         'incs',
171         'localincs',
172         'resources',
173         'misc']
174
175     def __init__(self, dspfile, source, env):
176         self.dspfile = str(dspfile)
177         try:
178             get_abspath = dspfile.get_abspath
179         except AttributeError:
180             self.dspabs = os.path.abspath(dspfile)
181         else:
182             self.dspabs = get_abspath()
183
184         if not env.has_key('variant'):
185             raise SCons.Errors.InternalError, \
186                   "You must specify a 'variant' argument (i.e. 'Debug' or " +\
187                   "'Release') to create an MSVSProject."
188         elif SCons.Util.is_String(env['variant']):
189             variants = [env['variant']]
190         elif SCons.Util.is_List(env['variant']):
191             variants = env['variant']
192
193         if not env.has_key('buildtarget') or env['buildtarget'] == None:
194             buildtarget = ['']
195         elif SCons.Util.is_String(env['buildtarget']):
196             buildtarget = [env['buildtarget']]
197         elif SCons.Util.is_List(env['buildtarget']):
198             if len(env['buildtarget']) != len(variants):
199                 raise SCons.Errors.InternalError, \
200                     "Sizes of 'buildtarget' and 'variant' lists must be the same."
201             buildtarget = []
202             for bt in env['buildtarget']:
203                 if SCons.Util.is_String(bt):
204                     buildtarget.append(bt)
205                 else:
206                     buildtarget.append(bt.get_abspath())
207         else:
208             buildtarget = [env['buildtarget'].get_abspath()]
209         if len(buildtarget) == 1:
210             bt = buildtarget[0]
211             buildtarget = []
212             for _ in variants:
213                 buildtarget.append(bt)
214
215         if not env.has_key('outdir') or env['outdir'] == None:
216             outdir = ['']
217         elif SCons.Util.is_String(env['outdir']):
218             outdir = [env['outdir']]
219         elif SCons.Util.is_List(env['outdir']):
220             if len(env['outdir']) != len(variants):
221                 raise SCons.Errors.InternalError, \
222                     "Sizes of 'outdir' and 'variant' lists must be the same."
223             outdir = []
224             for s in env['outdir']:
225                 if SCons.Util.is_String(s):
226                     outdir.append(s)
227                 else:
228                     outdir.append(s.get_abspath())
229         else:
230             outdir = [env['outdir'].get_abspath()]
231         if len(outdir) == 1:
232             s = outdir[0]
233             outdir = []
234             for v in variants:
235                 outdir.append(s)
236
237         if not env.has_key('runfile') or env['runfile'] == None:
238             runfile = buildtarget[-1:]
239         elif SCons.Util.is_String(env['runfile']):
240             runfile = [env['runfile']]
241         elif SCons.Util.is_List(env['runfile']):
242             if len(env['runfile']) != len(variants):
243                 raise SCons.Errors.InternalError, \
244                     "Sizes of 'runfile' and 'variant' lists must be the same."
245             runfile = []
246             for s in env['runfile']:
247                 if SCons.Util.is_String(s):
248                     runfile.append(s)
249                 else:
250                     runfile.append(s.get_abspath())
251         else:
252             runfile = [env['runfile'].get_abspath()]
253         if len(runfile) == 1:
254             s = runfile[0]
255             runfile = []
256             for v in variants:
257                 runfile.append(s)
258
259         self.sconscript = env['MSVSSCONSCRIPT']
260
261         cmdargs = env.get('cmdargs', '')
262
263         self.env = env
264
265         if self.env.has_key('name'):
266             self.name = self.env['name']
267         else:
268             self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
269         self.name = self.env.subst(self.name)
270
271         sourcenames = [
272             'Source Files',
273             'Header Files',
274             'Local Headers',
275             'Resource Files',
276             'Other Files']
277
278         self.sources = {}
279         for n in sourcenames:
280             self.sources[n] = []
281
282         self.configs = {}
283
284         self.nokeep = 0
285         if env.has_key('nokeep') and env['variant'] != 0:
286             self.nokeep = 1
287
288         if self.nokeep == 0 and os.path.exists(self.dspabs):
289             self.Parse()
290
291         for t in zip(sourcenames,self.srcargs):
292             if self.env.has_key(t[1]):
293                 if SCons.Util.is_List(self.env[t[1]]):
294                     for i in self.env[t[1]]:
295                         if not i in self.sources[t[0]]:
296                             self.sources[t[0]].append(i)
297                 else:
298                     if not self.env[t[1]] in self.sources[t[0]]:
299                         self.sources[t[0]].append(self.env[t[1]])
300
301         for n in sourcenames:
302             # TODO(1.5):
303             #self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
304             self.sources[n].sort(lambda a, b: cmp(string.lower(a), string.lower(b)))
305
306         def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
307             config = Config()
308             config.buildtarget = buildtarget
309             config.outdir = outdir
310             config.cmdargs = cmdargs
311             config.runfile = runfile
312
313             match = re.match('(.*)\|(.*)', variant)
314             if match:
315                 config.variant = match.group(1)
316                 config.platform = match.group(2)
317             else:
318                 config.variant = variant
319                 config.platform = 'Win32'
320
321             self.configs[variant] = config
322             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
323
324         for i in range(len(variants)):
325             AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
326
327         self.platforms = []
328         for key in self.configs.keys():
329             platform = self.configs[key].platform
330             if not platform in self.platforms:
331                 self.platforms.append(platform)
332
333     def Build(self):
334         pass
335
336 V6DSPHeader = """\
337 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
338 # Microsoft Developer Studio Generated Build File, Format Version 6.00
339 # ** DO NOT EDIT **
340
341 # TARGTYPE "Win32 (x86) External Target" 0x0106
342
343 CFG=%(name)s - Win32 %(confkey)s
344 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
345 !MESSAGE use the Export Makefile command and run
346 !MESSAGE 
347 !MESSAGE NMAKE /f "%(name)s.mak".
348 !MESSAGE 
349 !MESSAGE You can specify a configuration when running NMAKE
350 !MESSAGE by defining the macro CFG on the command line. For example:
351 !MESSAGE 
352 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
353 !MESSAGE 
354 !MESSAGE Possible choices for configuration are:
355 !MESSAGE 
356 """
357
358 class _GenerateV6DSP(_DSPGenerator):
359     """Generates a Project file for MSVS 6.0"""
360
361     def PrintHeader(self):
362         # pick a default config
363         confkeys = self.configs.keys()
364         confkeys.sort()
365
366         name = self.name
367         confkey = confkeys[0]
368
369         self.file.write(V6DSPHeader % locals())
370
371         for kind in confkeys:
372             self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
373
374         self.file.write('!MESSAGE \n\n')
375
376     def PrintProject(self):
377         name = self.name
378         self.file.write('# Begin Project\n'
379                         '# PROP AllowPerConfigDependencies 0\n'
380                         '# PROP Scc_ProjName ""\n'
381                         '# PROP Scc_LocalPath ""\n\n')
382
383         first = 1
384         confkeys = self.configs.keys()
385         confkeys.sort()
386         for kind in confkeys:
387             outdir = self.configs[kind].outdir
388             buildtarget = self.configs[kind].buildtarget
389             if first == 1:
390                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
391                 first = 0
392             else:
393                 self.file.write('\n!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
394
395             env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET')
396             if not env_has_buildtarget:
397                 self.env['MSVSBUILDTARGET'] = buildtarget
398
399             # have to write this twice, once with the BASE settings, and once without
400             for base in ("BASE ",""):
401                 self.file.write('# PROP %sUse_MFC 0\n'
402                                 '# PROP %sUse_Debug_Libraries ' % (base, base))
403                 # TODO(1.5):
404                 #if kind.lower().find('debug') < 0:
405                 if string.find(string.lower(kind), 'debug') < 0:
406                     self.file.write('0\n')
407                 else:
408                     self.file.write('1\n')
409                 self.file.write('# PROP %sOutput_Dir "%s"\n'
410                                 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
411                 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
412                 self.file.write('# PROP %sCmd_Line "%s"\n'
413                                 '# PROP %sRebuild_Opt "-c && %s"\n'
414                                 '# PROP %sTarget_File "%s"\n'
415                                 '# PROP %sBsc_Name ""\n'
416                                 '# PROP %sTarget_Dir ""\n'\
417                                 %(base,cmd,base,cmd,base,buildtarget,base,base))
418
419             if not env_has_buildtarget:
420                 del self.env['MSVSBUILDTARGET']
421
422         self.file.write('\n!ENDIF\n\n'
423                         '# Begin Target\n\n')
424         for kind in confkeys:
425             self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
426         self.file.write('\n')
427         first = 0
428         for kind in confkeys:
429             if first == 0:
430                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
431                 first = 1
432             else:
433                 self.file.write('!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
434         self.file.write('!ENDIF \n\n')
435         self.PrintSourceFiles()
436         self.file.write('# End Target\n'
437                         '# End Project\n')
438
439         if self.nokeep == 0:
440             # now we pickle some data and add it to the file -- MSDEV will ignore it.
441             pdata = pickle.dumps(self.configs,1)
442             pdata = base64.encodestring(pdata)
443             self.file.write(pdata + '\n')
444             pdata = pickle.dumps(self.sources,1)
445             pdata = base64.encodestring(pdata)
446             self.file.write(pdata + '\n')
447
448     def PrintSourceFiles(self):
449         categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
450                       'Header Files': 'h|hpp|hxx|hm|inl',
451                       'Local Headers': 'h|hpp|hxx|hm|inl',
452                       'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
453                       'Other Files': ''}
454
455         cats = categories.keys()
456         # TODO(1.5):
457         #cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
458         cats.sort(lambda a, b: cmp(string.lower(a), string.lower(b)))
459         for kind in cats:
460             if not self.sources[kind]:
461                 continue # skip empty groups
462
463             self.file.write('# Begin Group "' + kind + '"\n\n')
464             # TODO(1.5)
465             #typelist = categories[kind].replace('|', ';')
466             typelist = string.replace(categories[kind], '|', ';')
467             self.file.write('# PROP Default_Filter "' + typelist + '"\n')
468
469             for file in self.sources[kind]:
470                 file = os.path.normpath(file)
471                 self.file.write('# Begin Source File\n\n'
472                                 'SOURCE="' + file + '"\n'
473                                 '# End Source File\n')
474             self.file.write('# End Group\n')
475
476         # add the SConscript file outside of the groups
477         self.file.write('# Begin Source File\n\n'
478                         'SOURCE="' + str(self.sconscript) + '"\n'
479                         '# End Source File\n')
480
481     def Parse(self):
482         try:
483             dspfile = open(self.dspabs,'r')
484         except IOError:
485             return # doesn't exist yet, so can't add anything to configs.
486
487         line = dspfile.readline()
488         while line:
489             # TODO(1.5):
490             #if line.find("# End Project") > -1:
491             if string.find(line, "# End Project") > -1:
492                 break
493             line = dspfile.readline()
494
495         line = dspfile.readline()
496         datas = line
497         while line and line != '\n':
498             line = dspfile.readline()
499             datas = datas + line
500
501         # OK, we've found our little pickled cache of data.
502         try:
503             datas = base64.decodestring(datas)
504             data = pickle.loads(datas)
505         except KeyboardInterrupt:
506             raise
507         except:
508             return # unable to unpickle any data for some reason
509
510         self.configs.update(data)
511
512         data = None
513         line = dspfile.readline()
514         datas = line
515         while line and line != '\n':
516             line = dspfile.readline()
517             datas = datas + line
518
519         # OK, we've found our little pickled cache of data.
520         # it has a "# " in front of it, so we strip that.
521         try:
522             datas = base64.decodestring(datas)
523             data = pickle.loads(datas)
524         except KeyboardInterrupt:
525             raise
526         except:
527             return # unable to unpickle any data for some reason
528
529         self.sources.update(data)
530
531     def Build(self):
532         try:
533             self.file = open(self.dspabs,'w')
534         except IOError, detail:
535             raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
536         else:
537             self.PrintHeader()
538             self.PrintProject()
539             self.file.close()
540
541 V7DSPHeader = """\
542 <?xml version="1.0" encoding = "%(encoding)s"?>
543 <VisualStudioProject
544 \tProjectType="Visual C++"
545 \tVersion="%(versionstr)s"
546 \tName="%(name)s"
547 %(scc_attrs)s
548 \tKeyword="MakeFileProj">
549 """
550
551 V7DSPConfiguration = """\
552 \t\t<Configuration
553 \t\t\tName="%(variant)s|%(platform)s"
554 \t\t\tOutputDirectory="%(outdir)s"
555 \t\t\tIntermediateDirectory="%(outdir)s"
556 \t\t\tConfigurationType="0"
557 \t\t\tUseOfMFC="0"
558 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
559 \t\t\t<Tool
560 \t\t\t\tName="VCNMakeTool"
561 \t\t\t\tBuildCommandLine="%(buildcmd)s"
562 \t\t\t\tCleanCommandLine="%(cleancmd)s"
563 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
564 \t\t\t\tOutput="%(runfile)s"/>
565 \t\t</Configuration>
566 """
567
568 V8DSPHeader = """\
569 <?xml version="1.0" encoding="%(encoding)s"?>
570 <VisualStudioProject
571 \tProjectType="Visual C++"
572 \tVersion="%(versionstr)s"
573 \tName="%(name)s"
574 %(scc_attrs)s
575 \tRootNamespace="%(name)s"
576 \tKeyword="MakeFileProj">
577 """
578
579 V8DSPConfiguration = """\
580 \t\t<Configuration
581 \t\t\tName="%(variant)s|Win32"
582 \t\t\tConfigurationType="0"
583 \t\t\tUseOfMFC="0"
584 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
585 \t\t\t>
586 \t\t\t<Tool
587 \t\t\t\tName="VCNMakeTool"
588 \t\t\t\tBuildCommandLine="%(buildcmd)s"
589 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
590 \t\t\t\tCleanCommandLine="%(cleancmd)s"
591 \t\t\t\tOutput="%(runfile)s"
592 \t\t\t\tPreprocessorDefinitions=""
593 \t\t\t\tIncludeSearchPath=""
594 \t\t\t\tForcedIncludes=""
595 \t\t\t\tAssemblySearchPath=""
596 \t\t\t\tForcedUsingAssemblies=""
597 \t\t\t\tCompileAsManaged=""
598 \t\t\t/>
599 \t\t</Configuration>
600 """
601 class _GenerateV7DSP(_DSPGenerator):
602     """Generates a Project file for MSVS .NET"""
603
604     def __init__(self, dspfile, source, env):
605         _DSPGenerator.__init__(self, dspfile, source, env)
606         self.version = env['MSVS_VERSION']
607         self.version_num, self.suite = msvs_parse_version(self.version)
608         if self.version_num >= 8.0:
609             self.versionstr = '8.00'
610             self.dspheader = V8DSPHeader
611             self.dspconfiguration = V8DSPConfiguration
612         else:
613             if self.version_num >= 7.1:
614                 self.versionstr = '7.10'
615             else:
616                 self.versionstr = '7.00'
617             self.dspheader = V7DSPHeader
618             self.dspconfiguration = V7DSPConfiguration
619         self.file = None
620
621     def PrintHeader(self):
622         env = self.env
623         versionstr = self.versionstr
624         name = self.name
625         encoding = self.env.subst('$MSVSENCODING')
626         scc_provider = env.get('MSVS_SCC_PROVIDER', '')
627         scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
628         scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
629         scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
630         project_guid = env.get('MSVS_PROJECT_GUID', '')
631         if self.version_num >= 8.0 and not project_guid:
632             project_guid = _generateGUID(self.dspfile, '')
633         if scc_provider != '':
634             scc_attrs = ('\tProjectGUID="%s"\n'
635                          '\tSccProjectName="%s"\n'
636                          '\tSccAuxPath="%s"\n'
637                          '\tSccLocalPath="%s"\n'
638                          '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
639         else:
640             scc_attrs = ('\tProjectGUID="%s"\n'
641                          '\tSccProjectName="%s"\n'
642                          '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
643
644         self.file.write(self.dspheader % locals())
645
646         self.file.write('\t<Platforms>\n')
647         for platform in self.platforms:
648             self.file.write(
649                         '\t\t<Platform\n'
650                         '\t\t\tName="%s"/>\n' % platform)
651         self.file.write('\t</Platforms>\n')
652
653         if self.version_num >= 8.0:
654             self.file.write('\t<ToolFiles>\n'
655                             '\t</ToolFiles>\n')
656
657     def PrintProject(self):
658         self.file.write('\t<Configurations>\n')
659
660         confkeys = self.configs.keys()
661         confkeys.sort()
662         for kind in confkeys:
663             variant = self.configs[kind].variant
664             platform = self.configs[kind].platform
665             outdir = self.configs[kind].outdir
666             buildtarget = self.configs[kind].buildtarget
667             runfile     = self.configs[kind].runfile
668             cmdargs = self.configs[kind].cmdargs
669
670             env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET')
671             if not env_has_buildtarget:
672                 self.env['MSVSBUILDTARGET'] = buildtarget
673
674             starting = 'echo Starting SCons && '
675             if cmdargs:
676                 cmdargs = ' ' + cmdargs
677             else:
678                 cmdargs = ''
679             buildcmd    = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
680             rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
681             cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
682
683             if not env_has_buildtarget:
684                 del self.env['MSVSBUILDTARGET']
685
686             self.file.write(self.dspconfiguration % locals())
687
688         self.file.write('\t</Configurations>\n')
689
690         if self.version_num >= 7.1:
691             self.file.write('\t<References>\n'
692                             '\t</References>\n')
693
694         self.PrintSourceFiles()
695
696         self.file.write('</VisualStudioProject>\n')
697
698         if self.nokeep == 0:
699             # now we pickle some data and add it to the file -- MSDEV will ignore it.
700             pdata = pickle.dumps(self.configs,1)
701             pdata = base64.encodestring(pdata)
702             self.file.write('<!-- SCons Data:\n' + pdata + '\n')
703             pdata = pickle.dumps(self.sources,1)
704             pdata = base64.encodestring(pdata)
705             self.file.write(pdata + '-->\n')
706
707     def printSources(self, hierarchy, commonprefix):
708         sorteditems = hierarchy.items()
709         # TODO(1.5):
710         #sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
711         sorteditems.sort(lambda a, b: cmp(string.lower(a[0]), string.lower(b[0])))
712
713         # First folders, then files
714         for key, value in sorteditems:
715             if SCons.Util.is_Dict(value):
716                 self.file.write('\t\t\t<Filter\n'
717                                 '\t\t\t\tName="%s"\n'
718                                 '\t\t\t\tFilter="">\n' % (key))
719                 self.printSources(value, commonprefix)
720                 self.file.write('\t\t\t</Filter>\n')
721
722         for key, value in sorteditems:
723             if SCons.Util.is_String(value):
724                 file = value
725                 if commonprefix:
726                     file = os.path.join(commonprefix, value)
727                 file = os.path.normpath(file)
728                 self.file.write('\t\t\t<File\n'
729                                 '\t\t\t\tRelativePath="%s">\n'
730                                 '\t\t\t</File>\n' % (file))
731
732     def PrintSourceFiles(self):
733         categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
734                       'Header Files': 'h;hpp;hxx;hm;inl',
735                       'Local Headers': 'h;hpp;hxx;hm;inl',
736                       'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
737                       'Other Files': ''}
738
739         self.file.write('\t<Files>\n')
740
741         cats = categories.keys()
742         # TODO(1.5)
743         #cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
744         cats.sort(lambda a, b: cmp(string.lower(a), string.lower(b)))
745         cats = filter(lambda k, s=self: s.sources[k], cats)
746         for kind in cats:
747             if len(cats) > 1:
748                 self.file.write('\t\t<Filter\n'
749                                 '\t\t\tName="%s"\n'
750                                 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
751
752             sources = self.sources[kind]
753
754             # First remove any common prefix
755             commonprefix = None
756             if len(sources) > 1:
757                 s = map(os.path.normpath, sources)
758                 # take the dirname because the prefix may include parts
759                 # of the filenames (e.g. if you have 'dir\abcd' and
760                 # 'dir\acde' then the cp will be 'dir\a' )
761                 cp = os.path.dirname( os.path.commonprefix(s) )
762                 if cp and s[0][len(cp)] == os.sep:
763                     # +1 because the filename starts after the separator
764                     sources = map(lambda s, l=len(cp)+1: s[l:], sources)
765                     commonprefix = cp
766             elif len(sources) == 1:
767                 commonprefix = os.path.dirname( sources[0] )
768                 sources[0] = os.path.basename( sources[0] )
769
770             hierarchy = makeHierarchy(sources)
771             self.printSources(hierarchy, commonprefix=commonprefix)
772
773             if len(cats)>1:
774                 self.file.write('\t\t</Filter>\n')
775
776         # add the SConscript file outside of the groups
777         self.file.write('\t\t<File\n'
778                         '\t\t\tRelativePath="%s">\n'
779                         '\t\t</File>\n' % str(self.sconscript))
780
781         self.file.write('\t</Files>\n'
782                         '\t<Globals>\n'
783                         '\t</Globals>\n')
784
785     def Parse(self):
786         try:
787             dspfile = open(self.dspabs,'r')
788         except IOError:
789             return # doesn't exist yet, so can't add anything to configs.
790
791         line = dspfile.readline()
792         while line:
793             # TODO(1.5)
794             #if line.find('<!-- SCons Data:') > -1:
795             if string.find(line, '<!-- SCons Data:') > -1:
796                 break
797             line = dspfile.readline()
798
799         line = dspfile.readline()
800         datas = line
801         while line and line != '\n':
802             line = dspfile.readline()
803             datas = datas + line
804
805         # OK, we've found our little pickled cache of data.
806         try:
807             datas = base64.decodestring(datas)
808             data = pickle.loads(datas)
809         except KeyboardInterrupt:
810             raise
811         except:
812             return # unable to unpickle any data for some reason
813
814         self.configs.update(data)
815
816         data = None
817         line = dspfile.readline()
818         datas = line
819         while line and line != '\n':
820             line = dspfile.readline()
821             datas = datas + line
822
823         # OK, we've found our little pickled cache of data.
824         try:
825             datas = base64.decodestring(datas)
826             data = pickle.loads(datas)
827         except KeyboardInterrupt:
828             raise
829         except:
830             return # unable to unpickle any data for some reason
831
832         self.sources.update(data)
833
834     def Build(self):
835         try:
836             self.file = open(self.dspabs,'w')
837         except IOError, detail:
838             raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
839         else:
840             self.PrintHeader()
841             self.PrintProject()
842             self.file.close()
843
844 class _DSWGenerator:
845     """ Base class for DSW generators """
846     def __init__(self, dswfile, source, env):
847         self.dswfile = os.path.normpath(str(dswfile))
848         self.env = env
849
850         if not env.has_key('projects'):
851             raise SCons.Errors.UserError, \
852                 "You must specify a 'projects' argument to create an MSVSSolution."
853         projects = env['projects']
854         if not SCons.Util.is_List(projects):
855             raise SCons.Errors.InternalError, \
856                 "The 'projects' argument must be a list of nodes."
857         projects = SCons.Util.flatten(projects)
858         if len(projects) < 1:
859             raise SCons.Errors.UserError, \
860                 "You must specify at least one project to create an MSVSSolution."
861         self.dspfiles = map(str, projects)
862
863         if self.env.has_key('name'):
864             self.name = self.env['name']
865         else:
866             self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
867         self.name = self.env.subst(self.name)
868
869     def Build(self):
870         pass
871
872 class _GenerateV7DSW(_DSWGenerator):
873     """Generates a Solution file for MSVS .NET"""
874     def __init__(self, dswfile, source, env):
875         _DSWGenerator.__init__(self, dswfile, source, env)
876
877         self.file = None
878         self.version = self.env['MSVS_VERSION']
879         self.version_num, self.suite = msvs_parse_version(self.version)
880         self.versionstr = '7.00'
881         if self.version_num >= 8.0:
882             self.versionstr = '9.00'
883         elif self.version_num >= 7.1:
884             self.versionstr = '8.00'
885         if self.version_num >= 8.0:
886             self.versionstr = '9.00'
887
888         if env.has_key('slnguid') and env['slnguid']:
889             self.slnguid = env['slnguid']
890         else:
891             self.slnguid = _generateGUID(dswfile, self.name)
892
893         self.configs = {}
894
895         self.nokeep = 0
896         if env.has_key('nokeep') and env['variant'] != 0:
897             self.nokeep = 1
898
899         if self.nokeep == 0 and os.path.exists(self.dswfile):
900             self.Parse()
901
902         def AddConfig(self, variant, dswfile=dswfile):
903             config = Config()
904
905             match = re.match('(.*)\|(.*)', variant)
906             if match:
907                 config.variant = match.group(1)
908                 config.platform = match.group(2)
909             else:
910                 config.variant = variant
911                 config.platform = 'Win32'
912
913             self.configs[variant] = config
914             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
915
916         if not env.has_key('variant'):
917             raise SCons.Errors.InternalError, \
918                   "You must specify a 'variant' argument (i.e. 'Debug' or " +\
919                   "'Release') to create an MSVS Solution File."
920         elif SCons.Util.is_String(env['variant']):
921             AddConfig(self, env['variant'])
922         elif SCons.Util.is_List(env['variant']):
923             for variant in env['variant']:
924                 AddConfig(self, variant)
925
926         self.platforms = []
927         for key in self.configs.keys():
928             platform = self.configs[key].platform
929             if not platform in self.platforms:
930                 self.platforms.append(platform)
931
932     def Parse(self):
933         try:
934             dswfile = open(self.dswfile,'r')
935         except IOError:
936             return # doesn't exist yet, so can't add anything to configs.
937
938         line = dswfile.readline()
939         while line:
940             if line[:9] == "EndGlobal":
941                 break
942             line = dswfile.readline()
943
944         line = dswfile.readline()
945         datas = line
946         while line:
947             line = dswfile.readline()
948             datas = datas + line
949
950         # OK, we've found our little pickled cache of data.
951         try:
952             datas = base64.decodestring(datas)
953             data = pickle.loads(datas)
954         except KeyboardInterrupt:
955             raise
956         except:
957             return # unable to unpickle any data for some reason
958
959         self.configs.update(data)
960
961     def PrintSolution(self):
962         """Writes a solution file"""
963         self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
964         if self.version_num >= 8.0:
965             self.file.write('# Visual Studio 2005\n')
966         for p in self.dspfiles:
967             name = os.path.basename(p)
968             base, suffix = SCons.Util.splitext(name)
969             if suffix == '.vcproj':
970                 name = base
971             guid = _generateGUID(p, '')
972             self.file.write('Project("%s") = "%s", "%s", "%s"\n'
973                             % ( external_makefile_guid, name, p, guid ) )
974             if self.version_num >= 7.1 and self.version_num < 8.0:
975                 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
976                                 '\tEndProjectSection\n')
977             self.file.write('EndProject\n')
978
979         self.file.write('Global\n')
980
981         env = self.env
982         if env.has_key('MSVS_SCC_PROVIDER'):
983             dspfile_base = os.path.basename(self.dspfile)
984             slnguid = self.slnguid
985             scc_provider = env.get('MSVS_SCC_PROVIDER', '')
986             scc_provider = string.replace(scc_provider, ' ', r'\u0020')
987             scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
988             # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
989             scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
990             scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
991             # project_guid = env.get('MSVS_PROJECT_GUID', '')
992
993             self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
994                             '\t\tSccNumberOfProjects = 2\n'
995                             '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
996                             '\t\tSccLocalPath0 = %(scc_local_path)s\n'
997                             '\t\tCanCheckoutShared = true\n'
998                             '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
999                             '\t\tSccProjectName1 = %(scc_project_name)s\n'
1000                             '\t\tSccLocalPath1 = %(scc_local_path)s\n'
1001                             '\t\tSccProvider1 = %(scc_provider)s\n'
1002                             '\t\tCanCheckoutShared = true\n'
1003                             '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
1004                             '\t\tSolutionUniqueID = %(slnguid)s\n'
1005                             '\tEndGlobalSection\n' % locals())
1006
1007         if self.version_num >= 8.0:
1008             self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
1009         else:
1010             self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
1011
1012         confkeys = self.configs.keys()
1013         confkeys.sort()
1014         cnt = 0
1015         for name in confkeys:
1016             variant = self.configs[name].variant
1017             platform = self.configs[name].platform
1018             if self.version_num >= 8.0:
1019                 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1020             else:
1021                 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1022             cnt = cnt + 1
1023         self.file.write('\tEndGlobalSection\n')
1024         if self.version_num < 7.1:
1025             self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1026                             '\tEndGlobalSection\n')
1027         if self.version_num >= 8.0:
1028             self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1029         else:
1030             self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1031
1032         for name in confkeys:
1033             variant = self.configs[name].variant
1034             platform = self.configs[name].platform
1035             if self.version_num >= 8.0:
1036                 for p in self.dspfiles:
1037                     guid = _generateGUID(p, '')
1038                     self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1039                                     '\t\t%s.%s|%s.Build.0 = %s|%s\n'  % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1040             else:
1041                 for p in self.dspfiles:
1042                     guid = _generateGUID(p, '')
1043                     self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1044                                     '\t\t%s.%s.Build.0 = %s|%s\n'  %(guid,variant,variant,platform,guid,variant,variant,platform))
1045
1046         self.file.write('\tEndGlobalSection\n')
1047
1048         if self.version_num >= 8.0:
1049             self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1050                             '\t\tHideSolutionNode = FALSE\n'
1051                             '\tEndGlobalSection\n')
1052         else:
1053             self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1054                             '\tEndGlobalSection\n'
1055                             '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1056                             '\tEndGlobalSection\n')
1057         self.file.write('EndGlobal\n')
1058         if self.nokeep == 0:
1059             pdata = pickle.dumps(self.configs,1)
1060             pdata = base64.encodestring(pdata)
1061             self.file.write(pdata + '\n')
1062
1063     def Build(self):
1064         try:
1065             self.file = open(self.dswfile,'w')
1066         except IOError, detail:
1067             raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1068         else:
1069             self.PrintSolution()
1070             self.file.close()
1071
1072 V6DSWHeader = """\
1073 Microsoft Developer Studio Workspace File, Format Version 6.00
1074 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1075
1076 ###############################################################################
1077
1078 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1079
1080 Package=<5>
1081 {{{
1082 }}}
1083
1084 Package=<4>
1085 {{{
1086 }}}
1087
1088 ###############################################################################
1089
1090 Global:
1091
1092 Package=<5>
1093 {{{
1094 }}}
1095
1096 Package=<3>
1097 {{{
1098 }}}
1099
1100 ###############################################################################
1101 """
1102
1103 class _GenerateV6DSW(_DSWGenerator):
1104     """Generates a Workspace file for MSVS 6.0"""
1105
1106     def PrintWorkspace(self):
1107         """ writes a DSW file """
1108         name = self.name
1109         dspfile = self.dspfiles[0]
1110         self.file.write(V6DSWHeader % locals())
1111
1112     def Build(self):
1113         try:
1114             self.file = open(self.dswfile,'w')
1115         except IOError, detail:
1116             raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1117         else:
1118             self.PrintWorkspace()
1119             self.file.close()
1120
1121
1122 def GenerateDSP(dspfile, source, env):
1123     """Generates a Project file based on the version of MSVS that is being used"""
1124
1125     version_num = 6.0
1126     if env.has_key('MSVS_VERSION'):
1127         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1128     if version_num >= 7.0:
1129         g = _GenerateV7DSP(dspfile, source, env)
1130         g.Build()
1131     else:
1132         g = _GenerateV6DSP(dspfile, source, env)
1133         g.Build()
1134
1135 def GenerateDSW(dswfile, source, env):
1136     """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1137
1138     version_num = 6.0
1139     if env.has_key('MSVS_VERSION'):
1140         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1141     if version_num >= 7.0:
1142         g = _GenerateV7DSW(dswfile, source, env)
1143         g.Build()
1144     else:
1145         g = _GenerateV6DSW(dswfile, source, env)
1146         g.Build()
1147
1148
1149 ##############################################################################
1150 # Above here are the classes and functions for generation of
1151 # DSP/DSW/SLN/VCPROJ files.
1152 ##############################################################################
1153
1154 def get_default_visualstudio_version(env):
1155     """Returns the version set in the env, or the latest version
1156     installed, if it can find it, or '6.0' if all else fails.  Also
1157     updates the environment with what it found."""
1158
1159     versions = ['6.0']
1160
1161     if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
1162         v = get_visualstudio_versions()
1163         if v:
1164             versions = v
1165         env['MSVS'] = {'VERSIONS' : versions}
1166     else:
1167         versions = env['MSVS'].get('VERSIONS', versions)
1168
1169     if not env.has_key('MSVS_VERSION'):
1170         env['MSVS_VERSION'] = versions[0] #use highest version by default
1171
1172     env['MSVS']['VERSION'] = env['MSVS_VERSION']
1173
1174     return env['MSVS_VERSION']
1175
1176 def get_visualstudio_versions():
1177     """
1178     Get list of visualstudio versions from the Windows registry.
1179     Returns a list of strings containing version numbers.  An empty list
1180     is returned if we were unable to accees the register (for example,
1181     we couldn't import the registry-access module) or the appropriate
1182     registry keys weren't found.
1183     """
1184
1185     if not SCons.Util.can_read_reg:
1186         return []
1187
1188     HLM = SCons.Util.HKEY_LOCAL_MACHINE
1189     KEYS = {
1190         r'Software\Microsoft\VisualStudio'      : '',
1191         r'Software\Microsoft\VCExpress'         : 'Exp',
1192     }
1193     L = []
1194     for K, suite_suffix in KEYS.items():
1195         try:
1196             k = SCons.Util.RegOpenKeyEx(HLM, K)
1197             i = 0
1198             while 1:
1199                 try:
1200                     p = SCons.Util.RegEnumKey(k,i)
1201                 except SCons.Util.RegError:
1202                     break
1203                 i = i + 1
1204                 if not p[0] in '123456789' or p in L:
1205                     continue
1206                 # Only add this version number if there is a valid
1207                 # registry structure (includes the "Setup" key),
1208                 # and at least some of the correct directories
1209                 # exist.  Sometimes VS uninstall leaves around
1210                 # some registry/filesystem turds that we don't
1211                 # want to trip over.  Also, some valid registry
1212                 # entries are MSDN entries, not MSVS ('7.1',
1213                 # notably), and we want to skip those too.
1214                 try:
1215                     SCons.Util.RegOpenKeyEx(HLM, K + '\\' + p + '\\Setup')
1216                 except SCons.Util.RegError:
1217                     continue
1218
1219                 id = []
1220                 idk = SCons.Util.RegOpenKeyEx(HLM, K + '\\' + p)
1221                 # This is not always here -- it only exists if the
1222                 # user installed into a non-standard location (at
1223                 # least in VS6 it works that way -- VS7 seems to
1224                 # always write it)
1225                 try:
1226                     id = SCons.Util.RegQueryValueEx(idk, 'InstallDir')
1227                 except SCons.Util.RegError:
1228                     pass
1229
1230                 # If the InstallDir key doesn't exist,
1231                 # then we check the default locations.
1232                 # Note: The IDE's executable is not devenv.exe for VS8 Express.
1233                 if not id or not id[0]:
1234                     files_dir = SCons.Platform.win32.get_program_files_dir()
1235                     version_num, suite = msvs_parse_version(p)
1236                     if version_num < 7.0:
1237                         vs = r'Microsoft Visual Studio\Common\MSDev98'
1238                     elif version_num < 8.0:
1239                         vs = r'Microsoft Visual Studio .NET\Common7\IDE'
1240                     else:
1241                         vs = r'Microsoft Visual Studio 8\Common7\IDE'
1242                     id = [ os.path.join(files_dir, vs) ]
1243                 if os.path.exists(id[0]):
1244                     L.append(p + suite_suffix)
1245         except SCons.Util.RegError:
1246             pass
1247
1248     if not L:
1249         return []
1250
1251     # This is a hack to get around the fact that certain Visual Studio
1252     # patches place a "6.1" version in the registry, which does not have
1253     # any of the keys we need to find include paths, install directories,
1254     # etc.  Therefore we ignore it if it is there, since it throws all
1255     # other logic off.
1256     try:
1257         L.remove("6.1")
1258     except ValueError:
1259         pass
1260
1261     L.sort()
1262     L.reverse()
1263
1264     return L
1265
1266 def get_default_visualstudio8_suite(env):
1267     """
1268     Returns the Visual Studio 2005 suite identifier set in the env, or the
1269     highest suite installed.
1270     """
1271     if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']):
1272         env['MSVS'] = {}
1273
1274     if env.has_key('MSVS_SUITE'):
1275         # TODO(1.5)
1276         #suite = env['MSVS_SUITE'].upper()
1277         suite = string.upper(env['MSVS_SUITE'])
1278         suites = [suite]
1279     else:
1280         suite = 'EXPRESS'
1281         suites = [suite]
1282         if SCons.Util.can_read_reg:
1283             suites = get_visualstudio8_suites()
1284             if suites:
1285                 suite = suites[0] #use best suite by default
1286
1287     env['MSVS_SUITE'] = suite
1288     env['MSVS']['SUITES'] = suites
1289     env['MSVS']['SUITE'] = suite
1290
1291     return suite
1292
1293 def get_visualstudio8_suites():
1294     """
1295     Returns a sorted list of all installed Visual Studio 2005 suites found
1296     in the registry. The highest version should be the first entry in the list.
1297     """
1298
1299     suites = []
1300
1301     # Detect Standard, Professional and Team edition
1302     try:
1303         idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
1304             r'Software\Microsoft\VisualStudio\8.0')
1305         SCons.Util.RegQueryValueEx(idk, 'InstallDir')
1306         editions = { 'PRO': r'Setup\VS\Pro' }       # ToDo: add standard and team editions
1307         edition_name = 'STD'
1308         for name, key_suffix in editions.items():
1309             try:
1310                 idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
1311                     r'Software\Microsoft\VisualStudio\8.0' + '\\' + key_suffix )
1312                 edition_name = name
1313             except SCons.Util.RegError:
1314                 pass
1315             suites.append(edition_name)
1316     except SCons.Util.RegError:
1317         pass
1318
1319     # Detect Express edition
1320     try:
1321         idk = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
1322             r'Software\Microsoft\VCExpress\8.0')
1323         SCons.Util.RegQueryValueEx(idk, 'InstallDir')
1324         suites.append('EXPRESS')
1325     except SCons.Util.RegError:
1326         pass
1327
1328     return suites
1329
1330 def is_msvs_installed():
1331     """
1332     Check the registry for an installed visual studio.
1333     """
1334     try:
1335         v = SCons.Tool.msvs.get_visualstudio_versions()
1336         return v
1337     except (SCons.Util.RegError, SCons.Errors.InternalError):
1338         return 0
1339
1340 def get_msvs_install_dirs(version = None, vs8suite = None):
1341     """
1342     Get installed locations for various msvc-related products, like the .NET SDK
1343     and the Platform SDK.
1344     """
1345
1346     if not SCons.Util.can_read_reg:
1347         return {}
1348
1349     if not version:
1350         versions = get_visualstudio_versions()
1351         if versions:
1352             version = versions[0] #use highest version by default
1353         else:
1354             return {}
1355
1356     version_num, suite = msvs_parse_version(version)
1357
1358     K = 'Software\\Microsoft\\VisualStudio\\' + str(version_num)
1359     if (version_num >= 8.0):
1360         if vs8suite == None:
1361             # We've been given no guidance about which Visual Studio 8
1362             # suite to use, so attempt to autodetect.
1363             suites = get_visualstudio8_suites()
1364             if suites:
1365                 vs8suite = suites[0]
1366
1367         if vs8suite == 'EXPRESS':
1368             K = 'Software\\Microsoft\\VCExpress\\' + str(version_num)
1369
1370     # vc++ install dir
1371     rv = {}
1372     if (version_num < 7.0):
1373         key = K + r'\Setup\Microsoft Visual C++\ProductDir'
1374     else:
1375         key = K + r'\Setup\VC\ProductDir'
1376     try:
1377         (rv['VCINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, key)
1378     except SCons.Util.RegError:
1379         pass
1380
1381     # visual studio install dir
1382     if (version_num < 7.0):
1383         try:
1384             (rv['VSINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
1385                                                              K + r'\Setup\Microsoft Visual Studio\ProductDir')
1386         except SCons.Util.RegError:
1387             pass
1388
1389         if not rv.has_key('VSINSTALLDIR') or not rv['VSINSTALLDIR']:
1390             if rv.has_key('VCINSTALLDIR') and rv['VCINSTALLDIR']:
1391                 rv['VSINSTALLDIR'] = os.path.dirname(rv['VCINSTALLDIR'])
1392             else:
1393                 rv['VSINSTALLDIR'] = os.path.join(SCons.Platform.win32.get_program_files_dir(),'Microsoft Visual Studio')
1394     else:
1395         try:
1396             (rv['VSINSTALLDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
1397                                                              K + r'\Setup\VS\ProductDir')
1398         except SCons.Util.RegError:
1399             pass
1400
1401     # .NET framework install dir
1402     try:
1403         (rv['FRAMEWORKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
1404             r'Software\Microsoft\.NETFramework\InstallRoot')
1405     except SCons.Util.RegError:
1406         pass
1407
1408     if rv.has_key('FRAMEWORKDIR'):
1409         # try and enumerate the installed versions of the .NET framework.
1410         contents = os.listdir(rv['FRAMEWORKDIR'])
1411         l = re.compile('v[0-9]+.*')
1412         installed_framework_versions = filter(lambda e, l=l: l.match(e), contents)
1413
1414         def versrt(a,b):
1415             # since version numbers aren't really floats...
1416             aa = a[1:]
1417             bb = b[1:]
1418             aal = string.split(aa, '.')
1419             bbl = string.split(bb, '.')
1420             # sequence comparison in python is lexicographical
1421             # which is exactly what we want.
1422             # Note we sort backwards so the highest version is first.
1423             return cmp(bbl,aal)
1424
1425         installed_framework_versions.sort(versrt)
1426
1427         rv['FRAMEWORKVERSIONS'] = installed_framework_versions
1428
1429         # TODO: allow a specific framework version to be set
1430
1431         # Choose a default framework version based on the Visual
1432         # Studio version.
1433         DefaultFrameworkVersionMap = {
1434             '7.0'   : 'v1.0',
1435             '7.1'   : 'v1.1',
1436             '8.0'   : 'v2.0',
1437             # TODO: Does .NET 3.0 need to be worked into here somewhere?
1438         }
1439         try:
1440             default_framework_version = DefaultFrameworkVersionMap[version[:3]]
1441         except (KeyError, TypeError):
1442             pass
1443         else:
1444             # Look for the first installed directory in FRAMEWORKDIR that
1445             # begins with the framework version string that's appropriate
1446             # for the Visual Studio version we're using.
1447             for v in installed_framework_versions:
1448                 if v[:4] == default_framework_version:
1449                     rv['FRAMEWORKVERSION'] = v
1450                     break
1451
1452         # If the framework version couldn't be worked out by the previous
1453         # code then fall back to using the latest version of the .NET
1454         # framework
1455         if not rv.has_key('FRAMEWORKVERSION'):
1456             rv['FRAMEWORKVERSION'] = installed_framework_versions[0]
1457
1458     # .NET framework SDK install dir
1459     if rv.has_key('FRAMEWORKVERSION'):
1460         # The .NET SDK version used must match the .NET version used,
1461         # so we deliberately don't fall back to other .NET framework SDK
1462         # versions that might be present.
1463         ver = rv['FRAMEWORKVERSION'][:4]
1464         key = r'Software\Microsoft\.NETFramework\sdkInstallRoot' + ver
1465         try:
1466             (rv['FRAMEWORKSDKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
1467                 key)
1468         except SCons.Util.RegError:
1469             pass
1470
1471     # MS Platform SDK dir
1472     try:
1473         (rv['PLATFORMSDKDIR'], t) = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
1474             r'Software\Microsoft\MicrosoftSDK\Directories\Install Dir')
1475     except SCons.Util.RegError:
1476         pass
1477
1478     if rv.has_key('PLATFORMSDKDIR'):
1479         # if we have a platform SDK, try and get some info on it.
1480         vers = {}
1481         try:
1482             loc = r'Software\Microsoft\MicrosoftSDK\InstalledSDKs'
1483             k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,loc)
1484             i = 0
1485             while 1:
1486                 try:
1487                     key = SCons.Util.RegEnumKey(k,i)
1488                     sdk = SCons.Util.RegOpenKeyEx(k,key)
1489                     j = 0
1490                     name = ''
1491                     date = ''
1492                     version = ''
1493                     while 1:
1494                         try:
1495                             (vk,vv,t) = SCons.Util.RegEnumValue(sdk,j)
1496                             # TODO(1.5):
1497                             #if vk.lower() == 'keyword':
1498                             #    name = vv
1499                             #if vk.lower() == 'propagation_date':
1500                             #    date = vv
1501                             #if vk.lower() == 'version':
1502                             #    version = vv
1503                             if string.lower(vk) == 'keyword':
1504                                 name = vv
1505                             if string.lower(vk) == 'propagation_date':
1506                                 date = vv
1507                             if string.lower(vk) == 'version':
1508                                 version = vv
1509                             j = j + 1
1510                         except SCons.Util.RegError:
1511                             break
1512                     if name:
1513                         vers[name] = (date, version)
1514                     i = i + 1
1515                 except SCons.Util.RegError:
1516                     break
1517             rv['PLATFORMSDK_MODULES'] = vers
1518         except SCons.Util.RegError:
1519             pass
1520
1521     return rv
1522
1523 def GetMSVSProjectSuffix(target, source, env, for_signature):
1524      return env['MSVS']['PROJECTSUFFIX']
1525
1526 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1527      return env['MSVS']['SOLUTIONSUFFIX']
1528
1529 def GenerateProject(target, source, env):
1530     # generate the dsp file, according to the version of MSVS.
1531     builddspfile = target[0]
1532     dspfile = builddspfile.srcnode()
1533
1534     # this detects whether or not we're using a VariantDir
1535     if not dspfile is builddspfile:
1536         try:
1537             bdsp = open(str(builddspfile), "w+")
1538         except IOError, detail:
1539             print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1540             raise
1541
1542         bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1543
1544     GenerateDSP(dspfile, source, env)
1545
1546     if env.get('auto_build_solution', 1):
1547         builddswfile = target[1]
1548         dswfile = builddswfile.srcnode()
1549
1550         if not dswfile is builddswfile:
1551
1552             try:
1553                 bdsw = open(str(builddswfile), "w+")
1554             except IOError, detail:
1555                 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1556                 raise
1557
1558             bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1559
1560         GenerateDSW(dswfile, source, env)
1561
1562 def GenerateSolution(target, source, env):
1563     GenerateDSW(target[0], source, env)
1564
1565 def projectEmitter(target, source, env):
1566     """Sets up the DSP dependencies."""
1567
1568     # todo: Not sure what sets source to what user has passed as target,
1569     # but this is what happens. When that is fixed, we also won't have
1570     # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1571     if source[0] == target[0]:
1572         source = []
1573
1574     # make sure the suffix is correct for the version of MSVS we're running.
1575     (base, suff) = SCons.Util.splitext(str(target[0]))
1576     suff = env.subst('$MSVSPROJECTSUFFIX')
1577     target[0] = base + suff
1578
1579     if not source:
1580         source = 'prj_inputs:'
1581         source = source + env.subst('$MSVSSCONSCOM', 1)
1582         source = source + env.subst('$MSVSENCODING', 1)
1583
1584         if env.has_key('buildtarget') and env['buildtarget'] != None:
1585             if SCons.Util.is_String(env['buildtarget']):
1586                 source = source + ' "%s"' % env['buildtarget']
1587             elif SCons.Util.is_List(env['buildtarget']):
1588                 for bt in env['buildtarget']:
1589                     if SCons.Util.is_String(bt):
1590                         source = source + ' "%s"' % bt
1591                     else:
1592                         try: source = source + ' "%s"' % bt.get_abspath()
1593                         except AttributeError: raise SCons.Errors.InternalError, \
1594                             "buildtarget can be a string, a node, a list of strings or nodes, or None"
1595             else:
1596                 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1597                 except AttributeError: raise SCons.Errors.InternalError, \
1598                     "buildtarget can be a string, a node, a list of strings or nodes, or None"
1599
1600         if env.has_key('outdir') and env['outdir'] != None:
1601             if SCons.Util.is_String(env['outdir']):
1602                 source = source + ' "%s"' % env['outdir']
1603             elif SCons.Util.is_List(env['outdir']):
1604                 for s in env['outdir']:
1605                     if SCons.Util.is_String(s):
1606                         source = source + ' "%s"' % s
1607                     else:
1608                         try: source = source + ' "%s"' % s.get_abspath()
1609                         except AttributeError: raise SCons.Errors.InternalError, \
1610                             "outdir can be a string, a node, a list of strings or nodes, or None"
1611             else:
1612                 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1613                 except AttributeError: raise SCons.Errors.InternalError, \
1614                     "outdir can be a string, a node, a list of strings or nodes, or None"
1615
1616         if env.has_key('name'):
1617             if SCons.Util.is_String(env['name']):
1618                 source = source + ' "%s"' % env['name']
1619             else:
1620                 raise SCons.Errors.InternalError, "name must be a string"
1621
1622         if env.has_key('variant'):
1623             if SCons.Util.is_String(env['variant']):
1624                 source = source + ' "%s"' % env['variant']
1625             elif SCons.Util.is_List(env['variant']):
1626                 for variant in env['variant']:
1627                     if SCons.Util.is_String(variant):
1628                         source = source + ' "%s"' % variant
1629                     else:
1630                         raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1631             else:
1632                 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1633         else:
1634             raise SCons.Errors.InternalError, "variant must be specified"
1635
1636         for s in _DSPGenerator.srcargs:
1637             if env.has_key(s):
1638                 if SCons.Util.is_String(env[s]):
1639                     source = source + ' "%s' % env[s]
1640                 elif SCons.Util.is_List(env[s]):
1641                     for t in env[s]:
1642                         if SCons.Util.is_String(t):
1643                             source = source + ' "%s"' % t
1644                         else:
1645                             raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1646                 else:
1647                     raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1648
1649         source = source + ' "%s"' % str(target[0])
1650         source = [SCons.Node.Python.Value(source)]
1651
1652     targetlist = [target[0]]
1653     sourcelist = source
1654
1655     if env.get('auto_build_solution', 1):
1656         env['projects'] = targetlist
1657         t, s = solutionEmitter(target, target, env)
1658         targetlist = targetlist + t
1659
1660     return (targetlist, sourcelist)
1661
1662 def solutionEmitter(target, source, env):
1663     """Sets up the DSW dependencies."""
1664
1665     # todo: Not sure what sets source to what user has passed as target,
1666     # but this is what happens. When that is fixed, we also won't have
1667     # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1668     if source[0] == target[0]:
1669         source = []
1670
1671     # make sure the suffix is correct for the version of MSVS we're running.
1672     (base, suff) = SCons.Util.splitext(str(target[0]))
1673     suff = env.subst('$MSVSSOLUTIONSUFFIX')
1674     target[0] = base + suff
1675
1676     if not source:
1677         source = 'sln_inputs:'
1678
1679         if env.has_key('name'):
1680             if SCons.Util.is_String(env['name']):
1681                 source = source + ' "%s"' % env['name']
1682             else:
1683                 raise SCons.Errors.InternalError, "name must be a string"
1684
1685         if env.has_key('variant'):
1686             if SCons.Util.is_String(env['variant']):
1687                 source = source + ' "%s"' % env['variant']
1688             elif SCons.Util.is_List(env['variant']):
1689                 for variant in env['variant']:
1690                     if SCons.Util.is_String(variant):
1691                         source = source + ' "%s"' % variant
1692                     else:
1693                         raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1694             else:
1695                 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1696         else:
1697             raise SCons.Errors.InternalError, "variant must be specified"
1698
1699         if env.has_key('slnguid'):
1700             if SCons.Util.is_String(env['slnguid']):
1701                 source = source + ' "%s"' % env['slnguid']
1702             else:
1703                 raise SCons.Errors.InternalError, "slnguid must be a string"
1704
1705         if env.has_key('projects'):
1706             if SCons.Util.is_String(env['projects']):
1707                 source = source + ' "%s"' % env['projects']
1708             elif SCons.Util.is_List(env['projects']):
1709                 for t in env['projects']:
1710                     if SCons.Util.is_String(t):
1711                         source = source + ' "%s"' % t
1712
1713         source = source + ' "%s"' % str(target[0])
1714         source = [SCons.Node.Python.Value(source)]
1715
1716     return ([target[0]], source)
1717
1718 projectAction = SCons.Action.Action(GenerateProject, None)
1719
1720 solutionAction = SCons.Action.Action(GenerateSolution, None)
1721
1722 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1723                                        suffix = '$MSVSPROJECTSUFFIX',
1724                                        emitter = projectEmitter)
1725
1726 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1727                                         suffix = '$MSVSSOLUTIONSUFFIX',
1728                                         emitter = solutionEmitter)
1729
1730 default_MSVS_SConscript = None
1731
1732 def generate(env):
1733     """Add Builders and construction variables for Microsoft Visual
1734     Studio project files to an Environment."""
1735     try:
1736         env['BUILDERS']['MSVSProject']
1737     except KeyError:
1738         env['BUILDERS']['MSVSProject'] = projectBuilder
1739
1740     try:
1741         env['BUILDERS']['MSVSSolution']
1742     except KeyError:
1743         env['BUILDERS']['MSVSSolution'] = solutionBuilder
1744
1745     env['MSVSPROJECTCOM'] = projectAction
1746     env['MSVSSOLUTIONCOM'] = solutionAction
1747
1748     if SCons.Script.call_stack:
1749         # XXX Need to find a way to abstract this; the build engine
1750         # shouldn't depend on anything in SCons.Script.
1751         env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1752     else:
1753         global default_MSVS_SConscript
1754         if default_MSVS_SConscript is None:
1755             default_MSVS_SConscript = env.File('SConstruct')
1756         env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1757
1758     env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1759     env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1760     env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1761     env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1762     env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1763     env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1764     env['MSVSENCODING'] = 'Windows-1252'
1765
1766     try:
1767         version = get_default_visualstudio_version(env)
1768         # keep a record of some of the MSVS info so the user can use it.
1769         dirs = get_msvs_install_dirs(version)
1770         env['MSVS'].update(dirs)
1771     except (SCons.Util.RegError, SCons.Errors.InternalError):
1772         # we don't care if we can't do this -- if we can't, it's
1773         # because we don't have access to the registry, or because the
1774         # tools aren't installed.  In either case, the user will have to
1775         # find them on their own.
1776         pass
1777
1778     version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1779     if (version_num < 7.0):
1780         env['MSVS']['PROJECTSUFFIX']  = '.dsp'
1781         env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1782     else:
1783         env['MSVS']['PROJECTSUFFIX']  = '.vcproj'
1784         env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1785
1786     env['GET_MSVSPROJECTSUFFIX']  = GetMSVSProjectSuffix
1787     env['GET_MSVSSOLUTIONSUFFIX']  = GetMSVSSolutionSuffix
1788     env['MSVSPROJECTSUFFIX']  = '${GET_MSVSPROJECTSUFFIX}'
1789     env['MSVSSOLUTIONSUFFIX']  = '${GET_MSVSSOLUTIONSUFFIX}'
1790     env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1791
1792 def exists(env):
1793     if not env['PLATFORM'] in ('win32', 'cygwin'):
1794         return 0
1795
1796     try:
1797         v = SCons.Tool.msvs.get_visualstudio_versions()
1798     except (SCons.Util.RegError, SCons.Errors.InternalError):
1799         pass
1800
1801     if not v:
1802         version_num = 6.0
1803         if env.has_key('MSVS_VERSION'):
1804             version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1805         if version_num >= 7.0:
1806             # The executable is 'devenv' in Visual Studio Pro,
1807             # Team System and others.  Express Editions have different
1808             # executable names.  Right now we're only going to worry
1809             # about Visual C++ 2005 Express Edition.
1810             return env.Detect('devenv') or env.Detect('vcexpress')
1811         else:
1812             return env.Detect('msdev')
1813     else:
1814         # there's at least one version of MSVS installed.
1815         return 1