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