698648853cca8dd14cc62881721420979919756c
[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 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
34
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36
37 import SCons.compat
38
39 import base64
40 import hashlib
41 import ntpath
42 import os
43 # compat layer imports "cPickle" for us if it's available.
44 import pickle
45 import re
46 import sys
47
48 import SCons.Builder
49 import SCons.Node.FS
50 import SCons.Platform.win32
51 import SCons.Script.SConscript
52 import SCons.Util
53 import SCons.Warnings
54
55 from MSCommon import msvc_exists, msvc_setup_env_once
56 from SCons.Defaults import processDefines
57
58 ##############################################################################
59 # Below here are the classes and functions for generation of
60 # DSP/DSW/SLN/VCPROJ files.
61 ##############################################################################
62
63 def xmlify(s):
64     s = s.replace("&", "&") # do this first
65     s = s.replace("'", "'")
66     s = s.replace('"', """)
67     return s
68
69 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
70
71 def _generateGUID(slnfile, name):
72     """This generates a dummy GUID for the sln file to use.  It is
73     based on the MD5 signatures of the sln filename plus the name of
74     the project.  It basically just needs to be unique, and not
75     change with each invocation."""
76     m = hashlib.md5()
77     # Normalize the slnfile path to a Windows path (\ separators) so
78     # the generated file has a consistent GUID even if we generate
79     # it on a non-Windows platform.
80     m.update(ntpath.normpath(str(slnfile)) + str(name))
81     solution = m.hexdigest().upper()
82     # convert most of the signature to GUID form (discard the rest)
83     solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
84     return solution
85
86 version_re = re.compile(r'(\d+\.\d+)(.*)')
87
88 def msvs_parse_version(s):
89     """
90     Split a Visual Studio version, which may in fact be something like
91     '7.0Exp', into is version number (returned as a float) and trailing
92     "suite" portion.
93     """
94     num, suite = version_re.match(s).groups()
95     return float(num), suite
96
97 # This is how we re-invoke SCons from inside MSVS Project files.
98 # The problem is that we might have been invoked as either scons.bat
99 # or scons.py.  If we were invoked directly as scons.py, then we could
100 # use sys.argv[0] to find the SCons "executable," but that doesn't work
101 # if we were invoked as scons.bat, which uses "python -c" to execute
102 # things and ends up with "-c" as sys.argv[0].  Consequently, we have
103 # the MSVS Project file invoke SCons the same way that scons.bat does,
104 # which works regardless of how we were invoked.
105 def getExecScriptMain(env, xml=None):
106     scons_home = env.get('SCONS_HOME')
107     if not scons_home and 'SCONS_LIB_DIR' in os.environ:
108         scons_home = os.environ['SCONS_LIB_DIR']
109     if scons_home:
110         exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
111     else:
112         version = SCons.__version__
113         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()
114     if xml:
115         exec_script_main = xmlify(exec_script_main)
116     return exec_script_main
117
118 # The string for the Python executable we tell the Project file to use
119 # is either sys.executable or, if an external PYTHON_ROOT environment
120 # variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
121 # pluck the actual executable name from sys.executable).
122 try:
123     python_root = os.environ['PYTHON_ROOT']
124 except KeyError:
125     python_executable = sys.executable
126 else:
127     python_executable = os.path.join('$$(PYTHON_ROOT)',
128                                      os.path.split(sys.executable)[1])
129
130 class Config:
131     pass
132
133 def splitFully(path):
134     dir, base = os.path.split(path)
135     if dir and dir != '' and dir != path:
136         return splitFully(dir)+[base]
137     if base == '':
138         return []
139     return [base]
140
141 def makeHierarchy(sources):
142     '''Break a list of files into a hierarchy; for each value, if it is a string,
143        then it is a file.  If it is a dictionary, it is a folder.  The string is
144        the original path of the file.'''
145
146     hierarchy = {}
147     for file in sources:
148         path = splitFully(file)
149         if len(path):
150             dict = hierarchy
151             for part in path[:-1]:
152                 if part not in dict:
153                     dict[part] = {}
154                 dict = dict[part]
155             dict[path[-1]] = file
156         #else:
157         #    print 'Warning: failed to decompose path for '+str(file)
158     return hierarchy
159
160 class _DSPGenerator:
161     """ Base class for DSP generators """
162
163     srcargs = [
164         'srcs',
165         'incs',
166         'localincs',
167         'resources',
168         'misc']
169
170     def __init__(self, dspfile, source, env):
171         self.dspfile = str(dspfile)
172         try:
173             get_abspath = dspfile.get_abspath
174         except AttributeError:
175             self.dspabs = os.path.abspath(dspfile)
176         else:
177             self.dspabs = get_abspath()
178
179         if 'variant' not in env:
180             raise SCons.Errors.InternalError, \
181                   "You must specify a 'variant' argument (i.e. 'Debug' or " +\
182                   "'Release') to create an MSVSProject."
183         elif SCons.Util.is_String(env['variant']):
184             variants = [env['variant']]
185         elif SCons.Util.is_List(env['variant']):
186             variants = env['variant']
187
188         if 'buildtarget' not in env or env['buildtarget'] == None:
189             buildtarget = ['']
190         elif SCons.Util.is_String(env['buildtarget']):
191             buildtarget = [env['buildtarget']]
192         elif SCons.Util.is_List(env['buildtarget']):
193             if len(env['buildtarget']) != len(variants):
194                 raise SCons.Errors.InternalError, \
195                     "Sizes of 'buildtarget' and 'variant' lists must be the same."
196             buildtarget = []
197             for bt in env['buildtarget']:
198                 if SCons.Util.is_String(bt):
199                     buildtarget.append(bt)
200                 else:
201                     buildtarget.append(bt.get_abspath())
202         else:
203             buildtarget = [env['buildtarget'].get_abspath()]
204         if len(buildtarget) == 1:
205             bt = buildtarget[0]
206             buildtarget = []
207             for _ in variants:
208                 buildtarget.append(bt)
209
210         if 'outdir' not in env or env['outdir'] == None:
211             outdir = ['']
212         elif SCons.Util.is_String(env['outdir']):
213             outdir = [env['outdir']]
214         elif SCons.Util.is_List(env['outdir']):
215             if len(env['outdir']) != len(variants):
216                 raise SCons.Errors.InternalError, \
217                     "Sizes of 'outdir' and 'variant' lists must be the same."
218             outdir = []
219             for s in env['outdir']:
220                 if SCons.Util.is_String(s):
221                     outdir.append(s)
222                 else:
223                     outdir.append(s.get_abspath())
224         else:
225             outdir = [env['outdir'].get_abspath()]
226         if len(outdir) == 1:
227             s = outdir[0]
228             outdir = []
229             for v in variants:
230                 outdir.append(s)
231
232         if 'runfile' not in env or env['runfile'] == None:
233             runfile = buildtarget[-1:]
234         elif SCons.Util.is_String(env['runfile']):
235             runfile = [env['runfile']]
236         elif SCons.Util.is_List(env['runfile']):
237             if len(env['runfile']) != len(variants):
238                 raise SCons.Errors.InternalError, \
239                     "Sizes of 'runfile' and 'variant' lists must be the same."
240             runfile = []
241             for s in env['runfile']:
242                 if SCons.Util.is_String(s):
243                     runfile.append(s)
244                 else:
245                     runfile.append(s.get_abspath())
246         else:
247             runfile = [env['runfile'].get_abspath()]
248         if len(runfile) == 1:
249             s = runfile[0]
250             runfile = []
251             for v in variants:
252                 runfile.append(s)
253
254         self.sconscript = env['MSVSSCONSCRIPT']
255
256         cmdargs = env.get('cmdargs', '')
257
258         self.env = env
259
260         if 'name' in self.env:
261             self.name = self.env['name']
262         else:
263             self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
264         self.name = self.env.subst(self.name)
265
266         sourcenames = [
267             'Source Files',
268             'Header Files',
269             'Local Headers',
270             'Resource Files',
271             'Other Files']
272
273         self.sources = {}
274         for n in sourcenames:
275             self.sources[n] = []
276
277         self.configs = {}
278
279         self.nokeep = 0
280         if 'nokeep' in env and env['variant'] != 0:
281             self.nokeep = 1
282
283         if self.nokeep == 0 and os.path.exists(self.dspabs):
284             self.Parse()
285
286         for t in zip(sourcenames,self.srcargs):
287             if t[1] in self.env:
288                 if SCons.Util.is_List(self.env[t[1]]):
289                     for i in self.env[t[1]]:
290                         if not i in self.sources[t[0]]:
291                             self.sources[t[0]].append(i)
292                 else:
293                     if not self.env[t[1]] in self.sources[t[0]]:
294                         self.sources[t[0]].append(self.env[t[1]])
295
296         for n in sourcenames:
297             self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
298
299         def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
300             config = Config()
301             config.buildtarget = buildtarget
302             config.outdir = outdir
303             config.cmdargs = cmdargs
304             config.runfile = runfile
305
306             match = re.match('(.*)\|(.*)', variant)
307             if match:
308                 config.variant = match.group(1)
309                 config.platform = match.group(2)
310             else:
311                 config.variant = variant
312                 config.platform = 'Win32'
313
314             self.configs[variant] = config
315             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
316
317         for i in range(len(variants)):
318             AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
319
320         self.platforms = []
321         for key in self.configs.keys():
322             platform = self.configs[key].platform
323             if not platform in self.platforms:
324                 self.platforms.append(platform)
325
326     def Build(self):
327         pass
328
329 V6DSPHeader = """\
330 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
331 # Microsoft Developer Studio Generated Build File, Format Version 6.00
332 # ** DO NOT EDIT **
333
334 # TARGTYPE "Win32 (x86) External Target" 0x0106
335
336 CFG=%(name)s - Win32 %(confkey)s
337 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
338 !MESSAGE use the Export Makefile command and run
339 !MESSAGE 
340 !MESSAGE NMAKE /f "%(name)s.mak".
341 !MESSAGE 
342 !MESSAGE You can specify a configuration when running NMAKE
343 !MESSAGE by defining the macro CFG on the command line. For example:
344 !MESSAGE 
345 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
346 !MESSAGE 
347 !MESSAGE Possible choices for configuration are:
348 !MESSAGE 
349 """
350
351 class _GenerateV6DSP(_DSPGenerator):
352     """Generates a Project file for MSVS 6.0"""
353
354     def PrintHeader(self):
355         # pick a default config
356         confkeys = sorted(self.configs.keys())
357
358         name = self.name
359         confkey = confkeys[0]
360
361         self.file.write(V6DSPHeader % locals())
362
363         for kind in confkeys:
364             self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
365
366         self.file.write('!MESSAGE \n\n')
367
368     def PrintProject(self):
369         name = self.name
370         self.file.write('# Begin Project\n'
371                         '# PROP AllowPerConfigDependencies 0\n'
372                         '# PROP Scc_ProjName ""\n'
373                         '# PROP Scc_LocalPath ""\n\n')
374
375         first = 1
376         confkeys = sorted(self.configs.keys())
377         for kind in confkeys:
378             outdir = self.configs[kind].outdir
379             buildtarget = self.configs[kind].buildtarget
380             if first == 1:
381                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
382                 first = 0
383             else:
384                 self.file.write('\n!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
385
386             env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
387             if not env_has_buildtarget:
388                 self.env['MSVSBUILDTARGET'] = buildtarget
389
390             # have to write this twice, once with the BASE settings, and once without
391             for base in ("BASE ",""):
392                 self.file.write('# PROP %sUse_MFC 0\n'
393                                 '# PROP %sUse_Debug_Libraries ' % (base, base))
394                 # TODO(1.5):
395                 #if kind.lower().find('debug') < 0:
396                 if kind.lower().find('debug') < 0:
397                     self.file.write('0\n')
398                 else:
399                     self.file.write('1\n')
400                 self.file.write('# PROP %sOutput_Dir "%s"\n'
401                                 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
402                 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
403                 self.file.write('# PROP %sCmd_Line "%s"\n'
404                                 '# PROP %sRebuild_Opt "-c && %s"\n'
405                                 '# PROP %sTarget_File "%s"\n'
406                                 '# PROP %sBsc_Name ""\n'
407                                 '# PROP %sTarget_Dir ""\n'\
408                                 %(base,cmd,base,cmd,base,buildtarget,base,base))
409
410             if not env_has_buildtarget:
411                 del self.env['MSVSBUILDTARGET']
412
413         self.file.write('\n!ENDIF\n\n'
414                         '# Begin Target\n\n')
415         for kind in confkeys:
416             self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
417         self.file.write('\n')
418         first = 0
419         for kind in confkeys:
420             if first == 0:
421                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
422                 first = 1
423             else:
424                 self.file.write('!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
425         self.file.write('!ENDIF \n\n')
426         self.PrintSourceFiles()
427         self.file.write('# End Target\n'
428                         '# End Project\n')
429
430         if self.nokeep == 0:
431             # now we pickle some data and add it to the file -- MSDEV will ignore it.
432             pdata = pickle.dumps(self.configs,1)
433             pdata = base64.encodestring(pdata)
434             self.file.write(pdata + '\n')
435             pdata = pickle.dumps(self.sources,1)
436             pdata = base64.encodestring(pdata)
437             self.file.write(pdata + '\n')
438
439     def PrintSourceFiles(self):
440         categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
441                       'Header Files': 'h|hpp|hxx|hm|inl',
442                       'Local Headers': 'h|hpp|hxx|hm|inl',
443                       'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
444                       'Other Files': ''}
445
446         cats = categories.keys()
447         cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
448         for kind in cats:
449             if not self.sources[kind]:
450                 continue # skip empty groups
451
452             self.file.write('# Begin Group "' + kind + '"\n\n')
453             # TODO(1.5)
454             #typelist = categories[kind].replace('|', ';')
455             typelist = categories[kind].replace('|', ';')
456             self.file.write('# PROP Default_Filter "' + typelist + '"\n')
457
458             for file in self.sources[kind]:
459                 file = os.path.normpath(file)
460                 self.file.write('# Begin Source File\n\n'
461                                 'SOURCE="' + file + '"\n'
462                                 '# End Source File\n')
463             self.file.write('# End Group\n')
464
465         # add the SConscript file outside of the groups
466         self.file.write('# Begin Source File\n\n'
467                         'SOURCE="' + str(self.sconscript) + '"\n'
468                         '# End Source File\n')
469
470     def Parse(self):
471         try:
472             dspfile = open(self.dspabs,'r')
473         except IOError:
474             return # doesn't exist yet, so can't add anything to configs.
475
476         line = dspfile.readline()
477         while line:
478             # TODO(1.5):
479             #if line.find("# End Project") > -1:
480             if line.find("# End Project") > -1:
481                 break
482             line = dspfile.readline()
483
484         line = dspfile.readline()
485         datas = line
486         while line and line != '\n':
487             line = dspfile.readline()
488             datas = datas + line
489
490         # OK, we've found our little pickled cache of data.
491         try:
492             datas = base64.decodestring(datas)
493             data = pickle.loads(datas)
494         except KeyboardInterrupt:
495             raise
496         except:
497             return # unable to unpickle any data for some reason
498
499         self.configs.update(data)
500
501         data = None
502         line = dspfile.readline()
503         datas = line
504         while line and line != '\n':
505             line = dspfile.readline()
506             datas = datas + line
507
508         # OK, we've found our little pickled cache of data.
509         # it has a "# " in front of it, so we strip that.
510         try:
511             datas = base64.decodestring(datas)
512             data = pickle.loads(datas)
513         except KeyboardInterrupt:
514             raise
515         except:
516             return # unable to unpickle any data for some reason
517
518         self.sources.update(data)
519
520     def Build(self):
521         try:
522             self.file = open(self.dspabs,'w')
523         except IOError, detail:
524             raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
525         else:
526             self.PrintHeader()
527             self.PrintProject()
528             self.file.close()
529
530 V7DSPHeader = """\
531 <?xml version="1.0" encoding = "%(encoding)s"?>
532 <VisualStudioProject
533 \tProjectType="Visual C++"
534 \tVersion="%(versionstr)s"
535 \tName="%(name)s"
536 %(scc_attrs)s
537 \tKeyword="MakeFileProj">
538 """
539
540 V7DSPConfiguration = """\
541 \t\t<Configuration
542 \t\t\tName="%(variant)s|%(platform)s"
543 \t\t\tOutputDirectory="%(outdir)s"
544 \t\t\tIntermediateDirectory="%(outdir)s"
545 \t\t\tConfigurationType="0"
546 \t\t\tUseOfMFC="0"
547 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
548 \t\t\t<Tool
549 \t\t\t\tName="VCNMakeTool"
550 \t\t\t\tBuildCommandLine="%(buildcmd)s"
551 \t\t\t\tCleanCommandLine="%(cleancmd)s"
552 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
553 \t\t\t\tOutput="%(runfile)s"/>
554 \t\t</Configuration>
555 """
556
557 V8DSPHeader = """\
558 <?xml version="1.0" encoding="%(encoding)s"?>
559 <VisualStudioProject
560 \tProjectType="Visual C++"
561 \tVersion="%(versionstr)s"
562 \tName="%(name)s"
563 %(scc_attrs)s
564 \tRootNamespace="%(name)s"
565 \tKeyword="MakeFileProj">
566 """
567
568 V8DSPConfiguration = """\
569 \t\t<Configuration
570 \t\t\tName="%(variant)s|%(platform)s"
571 \t\t\tConfigurationType="0"
572 \t\t\tUseOfMFC="0"
573 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
574 \t\t\t>
575 \t\t\t<Tool
576 \t\t\t\tName="VCNMakeTool"
577 \t\t\t\tBuildCommandLine="%(buildcmd)s"
578 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
579 \t\t\t\tCleanCommandLine="%(cleancmd)s"
580 \t\t\t\tOutput="%(runfile)s"
581 \t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
582 \t\t\t\tIncludeSearchPath="%(includepath)s"
583 \t\t\t\tForcedIncludes=""
584 \t\t\t\tAssemblySearchPath=""
585 \t\t\t\tForcedUsingAssemblies=""
586 \t\t\t\tCompileAsManaged=""
587 \t\t\t/>
588 \t\t</Configuration>
589 """
590 class _GenerateV7DSP(_DSPGenerator):
591     """Generates a Project file for MSVS .NET"""
592
593     def __init__(self, dspfile, source, env):
594         _DSPGenerator.__init__(self, dspfile, source, env)
595         self.version = env['MSVS_VERSION']
596         self.version_num, self.suite = msvs_parse_version(self.version)
597         if self.version_num >= 8.0:
598             self.versionstr = '8.00'
599             self.dspheader = V8DSPHeader
600             self.dspconfiguration = V8DSPConfiguration
601         else:
602             if self.version_num >= 7.1:
603                 self.versionstr = '7.10'
604             else:
605                 self.versionstr = '7.00'
606             self.dspheader = V7DSPHeader
607             self.dspconfiguration = V7DSPConfiguration
608         self.file = None
609
610     def PrintHeader(self):
611         env = self.env
612         versionstr = self.versionstr
613         name = self.name
614         encoding = self.env.subst('$MSVSENCODING')
615         scc_provider = env.get('MSVS_SCC_PROVIDER', '')
616         scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
617         scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
618         scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
619         project_guid = env.get('MSVS_PROJECT_GUID', '')
620         if self.version_num >= 8.0 and not project_guid:
621             project_guid = _generateGUID(self.dspfile, '')
622         if scc_provider != '':
623             scc_attrs = ('\tProjectGUID="%s"\n'
624                          '\tSccProjectName="%s"\n'
625                          '\tSccAuxPath="%s"\n'
626                          '\tSccLocalPath="%s"\n'
627                          '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
628         else:
629             scc_attrs = ('\tProjectGUID="%s"\n'
630                          '\tSccProjectName="%s"\n'
631                          '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
632
633         self.file.write(self.dspheader % locals())
634
635         self.file.write('\t<Platforms>\n')
636         for platform in self.platforms:
637             self.file.write(
638                         '\t\t<Platform\n'
639                         '\t\t\tName="%s"/>\n' % platform)
640         self.file.write('\t</Platforms>\n')
641
642         if self.version_num >= 8.0:
643             self.file.write('\t<ToolFiles>\n'
644                             '\t</ToolFiles>\n')
645
646     def PrintProject(self):
647         self.file.write('\t<Configurations>\n')
648
649         confkeys = sorted(self.configs.keys())
650         for kind in confkeys:
651             variant = self.configs[kind].variant
652             platform = self.configs[kind].platform
653             outdir = self.configs[kind].outdir
654             buildtarget = self.configs[kind].buildtarget
655             runfile     = self.configs[kind].runfile
656             cmdargs = self.configs[kind].cmdargs
657
658             env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
659             if not env_has_buildtarget:
660                 self.env['MSVSBUILDTARGET'] = buildtarget
661
662             starting = 'echo Starting SCons && '
663             if cmdargs:
664                 cmdargs = ' ' + cmdargs
665             else:
666                 cmdargs = ''
667             buildcmd    = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
668             rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
669             cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
670
671             # TODO(1.5)
672             #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', [])))
673             #includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
674             preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
675             includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
676
677             if not env_has_buildtarget:
678                 del self.env['MSVSBUILDTARGET']
679
680             self.file.write(self.dspconfiguration % locals())
681
682         self.file.write('\t</Configurations>\n')
683
684         if self.version_num >= 7.1:
685             self.file.write('\t<References>\n'
686                             '\t</References>\n')
687
688         self.PrintSourceFiles()
689
690         self.file.write('</VisualStudioProject>\n')
691
692         if self.nokeep == 0:
693             # now we pickle some data and add it to the file -- MSDEV will ignore it.
694             pdata = pickle.dumps(self.configs,1)
695             pdata = base64.encodestring(pdata)
696             self.file.write('<!-- SCons Data:\n' + pdata + '\n')
697             pdata = pickle.dumps(self.sources,1)
698             pdata = base64.encodestring(pdata)
699             self.file.write(pdata + '-->\n')
700
701     def printSources(self, hierarchy, commonprefix):
702         sorteditems = hierarchy.items()
703         sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
704
705         # First folders, then files
706         for key, value in sorteditems:
707             if SCons.Util.is_Dict(value):
708                 self.file.write('\t\t\t<Filter\n'
709                                 '\t\t\t\tName="%s"\n'
710                                 '\t\t\t\tFilter="">\n' % (key))
711                 self.printSources(value, commonprefix)
712                 self.file.write('\t\t\t</Filter>\n')
713
714         for key, value in sorteditems:
715             if SCons.Util.is_String(value):
716                 file = value
717                 if commonprefix:
718                     file = os.path.join(commonprefix, value)
719                 file = os.path.normpath(file)
720                 self.file.write('\t\t\t<File\n'
721                                 '\t\t\t\tRelativePath="%s">\n'
722                                 '\t\t\t</File>\n' % (file))
723
724     def PrintSourceFiles(self):
725         categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
726                       'Header Files': 'h;hpp;hxx;hm;inl',
727                       'Local Headers': 'h;hpp;hxx;hm;inl',
728                       'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
729                       'Other Files': ''}
730
731         self.file.write('\t<Files>\n')
732
733         cats = categories.keys()
734         cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
735         cats = [k for k in cats if self.sources[k]]
736         for kind in cats:
737             if len(cats) > 1:
738                 self.file.write('\t\t<Filter\n'
739                                 '\t\t\tName="%s"\n'
740                                 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
741
742             sources = self.sources[kind]
743
744             # First remove any common prefix
745             commonprefix = None
746             if len(sources) > 1:
747                 s = list(map(os.path.normpath, sources))
748                 # take the dirname because the prefix may include parts
749                 # of the filenames (e.g. if you have 'dir\abcd' and
750                 # 'dir\acde' then the cp will be 'dir\a' )
751                 cp = os.path.dirname( os.path.commonprefix(s) )
752                 if cp and s[0][len(cp)] == os.sep:
753                     # +1 because the filename starts after the separator
754                     sources = [s[len(cp)+1:] for s in sources]
755                     commonprefix = cp
756             elif len(sources) == 1:
757                 commonprefix = os.path.dirname( sources[0] )
758                 sources[0] = os.path.basename( sources[0] )
759
760             hierarchy = makeHierarchy(sources)
761             self.printSources(hierarchy, commonprefix=commonprefix)
762
763             if len(cats)>1:
764                 self.file.write('\t\t</Filter>\n')
765
766         # add the SConscript file outside of the groups
767         self.file.write('\t\t<File\n'
768                         '\t\t\tRelativePath="%s">\n'
769                         '\t\t</File>\n' % str(self.sconscript))
770
771         self.file.write('\t</Files>\n'
772                         '\t<Globals>\n'
773                         '\t</Globals>\n')
774
775     def Parse(self):
776         try:
777             dspfile = open(self.dspabs,'r')
778         except IOError:
779             return # doesn't exist yet, so can't add anything to configs.
780
781         line = dspfile.readline()
782         while line:
783             # TODO(1.5)
784             #if line.find('<!-- SCons Data:') > -1:
785             if line.find('<!-- SCons Data:') > -1:
786                 break
787             line = dspfile.readline()
788
789         line = dspfile.readline()
790         datas = line
791         while line and line != '\n':
792             line = dspfile.readline()
793             datas = datas + line
794
795         # OK, we've found our little pickled cache of data.
796         try:
797             datas = base64.decodestring(datas)
798             data = pickle.loads(datas)
799         except KeyboardInterrupt:
800             raise
801         except:
802             return # unable to unpickle any data for some reason
803
804         self.configs.update(data)
805
806         data = None
807         line = dspfile.readline()
808         datas = line
809         while line and line != '\n':
810             line = dspfile.readline()
811             datas = datas + line
812
813         # OK, we've found our little pickled cache of data.
814         try:
815             datas = base64.decodestring(datas)
816             data = pickle.loads(datas)
817         except KeyboardInterrupt:
818             raise
819         except:
820             return # unable to unpickle any data for some reason
821
822         self.sources.update(data)
823
824     def Build(self):
825         try:
826             self.file = open(self.dspabs,'w')
827         except IOError, detail:
828             raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
829         else:
830             self.PrintHeader()
831             self.PrintProject()
832             self.file.close()
833
834 class _DSWGenerator:
835     """ Base class for DSW generators """
836     def __init__(self, dswfile, source, env):
837         self.dswfile = os.path.normpath(str(dswfile))
838         self.env = env
839
840         if 'projects' not in env:
841             raise SCons.Errors.UserError, \
842                 "You must specify a 'projects' argument to create an MSVSSolution."
843         projects = env['projects']
844         if not SCons.Util.is_List(projects):
845             raise SCons.Errors.InternalError, \
846                 "The 'projects' argument must be a list of nodes."
847         projects = SCons.Util.flatten(projects)
848         if len(projects) < 1:
849             raise SCons.Errors.UserError, \
850                 "You must specify at least one project to create an MSVSSolution."
851         self.dspfiles = list(map(str, projects))
852
853         if 'name' in self.env:
854             self.name = self.env['name']
855         else:
856             self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
857         self.name = self.env.subst(self.name)
858
859     def Build(self):
860         pass
861
862 class _GenerateV7DSW(_DSWGenerator):
863     """Generates a Solution file for MSVS .NET"""
864     def __init__(self, dswfile, source, env):
865         _DSWGenerator.__init__(self, dswfile, source, env)
866
867         self.file = None
868         self.version = self.env['MSVS_VERSION']
869         self.version_num, self.suite = msvs_parse_version(self.version)
870         self.versionstr = '7.00'
871         if self.version_num >= 8.0:
872             self.versionstr = '9.00'
873         elif self.version_num >= 7.1:
874             self.versionstr = '8.00'
875         if self.version_num >= 8.0:
876             self.versionstr = '9.00'
877
878         if 'slnguid' in env and env['slnguid']:
879             self.slnguid = env['slnguid']
880         else:
881             self.slnguid = _generateGUID(dswfile, self.name)
882
883         self.configs = {}
884
885         self.nokeep = 0
886         if 'nokeep' in env and env['variant'] != 0:
887             self.nokeep = 1
888
889         if self.nokeep == 0 and os.path.exists(self.dswfile):
890             self.Parse()
891
892         def AddConfig(self, variant, dswfile=dswfile):
893             config = Config()
894
895             match = re.match('(.*)\|(.*)', variant)
896             if match:
897                 config.variant = match.group(1)
898                 config.platform = match.group(2)
899             else:
900                 config.variant = variant
901                 config.platform = 'Win32'
902
903             self.configs[variant] = config
904             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
905
906         if 'variant' not in env:
907             raise SCons.Errors.InternalError, \
908                   "You must specify a 'variant' argument (i.e. 'Debug' or " +\
909                   "'Release') to create an MSVS Solution File."
910         elif SCons.Util.is_String(env['variant']):
911             AddConfig(self, env['variant'])
912         elif SCons.Util.is_List(env['variant']):
913             for variant in env['variant']:
914                 AddConfig(self, variant)
915
916         self.platforms = []
917         for key in self.configs.keys():
918             platform = self.configs[key].platform
919             if not platform in self.platforms:
920                 self.platforms.append(platform)
921
922     def Parse(self):
923         try:
924             dswfile = open(self.dswfile,'r')
925         except IOError:
926             return # doesn't exist yet, so can't add anything to configs.
927
928         line = dswfile.readline()
929         while line:
930             if line[:9] == "EndGlobal":
931                 break
932             line = dswfile.readline()
933
934         line = dswfile.readline()
935         datas = line
936         while line:
937             line = dswfile.readline()
938             datas = datas + line
939
940         # OK, we've found our little pickled cache of data.
941         try:
942             datas = base64.decodestring(datas)
943             data = pickle.loads(datas)
944         except KeyboardInterrupt:
945             raise
946         except:
947             return # unable to unpickle any data for some reason
948
949         self.configs.update(data)
950
951     def PrintSolution(self):
952         """Writes a solution file"""
953         self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
954         if self.version_num >= 8.0:
955             self.file.write('# Visual Studio 2005\n')
956         for p in self.dspfiles:
957             name = os.path.basename(p)
958             base, suffix = SCons.Util.splitext(name)
959             if suffix == '.vcproj':
960                 name = base
961             guid = _generateGUID(p, '')
962             self.file.write('Project("%s") = "%s", "%s", "%s"\n'
963                             % ( external_makefile_guid, name, p, guid ) )
964             if self.version_num >= 7.1 and self.version_num < 8.0:
965                 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
966                                 '\tEndProjectSection\n')
967             self.file.write('EndProject\n')
968
969         self.file.write('Global\n')
970
971         env = self.env
972         if 'MSVS_SCC_PROVIDER' in env:
973             dspfile_base = os.path.basename(self.dspfile)
974             slnguid = self.slnguid
975             scc_provider = env.get('MSVS_SCC_PROVIDER', '')
976             scc_provider = scc_provider.replace(' ', r'\u0020')
977             scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
978             # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
979             scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
980             scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
981             # project_guid = env.get('MSVS_PROJECT_GUID', '')
982
983             self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
984                             '\t\tSccNumberOfProjects = 2\n'
985                             '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
986                             '\t\tSccLocalPath0 = %(scc_local_path)s\n'
987                             '\t\tCanCheckoutShared = true\n'
988                             '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
989                             '\t\tSccProjectName1 = %(scc_project_name)s\n'
990                             '\t\tSccLocalPath1 = %(scc_local_path)s\n'
991                             '\t\tSccProvider1 = %(scc_provider)s\n'
992                             '\t\tCanCheckoutShared = true\n'
993                             '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
994                             '\t\tSolutionUniqueID = %(slnguid)s\n'
995                             '\tEndGlobalSection\n' % locals())
996
997         if self.version_num >= 8.0:
998             self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
999         else:
1000             self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
1001
1002         confkeys = sorted(self.configs.keys())
1003         cnt = 0
1004         for name in confkeys:
1005             variant = self.configs[name].variant
1006             platform = self.configs[name].platform
1007             if self.version_num >= 8.0:
1008                 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1009             else:
1010                 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1011             cnt = cnt + 1
1012         self.file.write('\tEndGlobalSection\n')
1013         if self.version_num < 7.1:
1014             self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1015                             '\tEndGlobalSection\n')
1016         if self.version_num >= 8.0:
1017             self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1018         else:
1019             self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1020
1021         for name in confkeys:
1022             variant = self.configs[name].variant
1023             platform = self.configs[name].platform
1024             if self.version_num >= 8.0:
1025                 for p in self.dspfiles:
1026                     guid = _generateGUID(p, '')
1027                     self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1028                                     '\t\t%s.%s|%s.Build.0 = %s|%s\n'  % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1029             else:
1030                 for p in self.dspfiles:
1031                     guid = _generateGUID(p, '')
1032                     self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1033                                     '\t\t%s.%s.Build.0 = %s|%s\n'  %(guid,variant,variant,platform,guid,variant,variant,platform))
1034
1035         self.file.write('\tEndGlobalSection\n')
1036
1037         if self.version_num >= 8.0:
1038             self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1039                             '\t\tHideSolutionNode = FALSE\n'
1040                             '\tEndGlobalSection\n')
1041         else:
1042             self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1043                             '\tEndGlobalSection\n'
1044                             '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1045                             '\tEndGlobalSection\n')
1046         self.file.write('EndGlobal\n')
1047         if self.nokeep == 0:
1048             pdata = pickle.dumps(self.configs,1)
1049             pdata = base64.encodestring(pdata)
1050             self.file.write(pdata + '\n')
1051
1052     def Build(self):
1053         try:
1054             self.file = open(self.dswfile,'w')
1055         except IOError, detail:
1056             raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1057         else:
1058             self.PrintSolution()
1059             self.file.close()
1060
1061 V6DSWHeader = """\
1062 Microsoft Developer Studio Workspace File, Format Version 6.00
1063 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1064
1065 ###############################################################################
1066
1067 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1068
1069 Package=<5>
1070 {{{
1071 }}}
1072
1073 Package=<4>
1074 {{{
1075 }}}
1076
1077 ###############################################################################
1078
1079 Global:
1080
1081 Package=<5>
1082 {{{
1083 }}}
1084
1085 Package=<3>
1086 {{{
1087 }}}
1088
1089 ###############################################################################
1090 """
1091
1092 class _GenerateV6DSW(_DSWGenerator):
1093     """Generates a Workspace file for MSVS 6.0"""
1094
1095     def PrintWorkspace(self):
1096         """ writes a DSW file """
1097         name = self.name
1098         dspfile = self.dspfiles[0]
1099         self.file.write(V6DSWHeader % locals())
1100
1101     def Build(self):
1102         try:
1103             self.file = open(self.dswfile,'w')
1104         except IOError, detail:
1105             raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1106         else:
1107             self.PrintWorkspace()
1108             self.file.close()
1109
1110
1111 def GenerateDSP(dspfile, source, env):
1112     """Generates a Project file based on the version of MSVS that is being used"""
1113
1114     version_num = 6.0
1115     if 'MSVS_VERSION' in env:
1116         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1117     if version_num >= 7.0:
1118         g = _GenerateV7DSP(dspfile, source, env)
1119         g.Build()
1120     else:
1121         g = _GenerateV6DSP(dspfile, source, env)
1122         g.Build()
1123
1124 def GenerateDSW(dswfile, source, env):
1125     """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1126
1127     version_num = 6.0
1128     if 'MSVS_VERSION' in env:
1129         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1130     if version_num >= 7.0:
1131         g = _GenerateV7DSW(dswfile, source, env)
1132         g.Build()
1133     else:
1134         g = _GenerateV6DSW(dswfile, source, env)
1135         g.Build()
1136
1137
1138 ##############################################################################
1139 # Above here are the classes and functions for generation of
1140 # DSP/DSW/SLN/VCPROJ files.
1141 ##############################################################################
1142
1143 def GetMSVSProjectSuffix(target, source, env, for_signature):
1144      return env['MSVS']['PROJECTSUFFIX']
1145
1146 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1147      return env['MSVS']['SOLUTIONSUFFIX']
1148
1149 def GenerateProject(target, source, env):
1150     # generate the dsp file, according to the version of MSVS.
1151     builddspfile = target[0]
1152     dspfile = builddspfile.srcnode()
1153
1154     # this detects whether or not we're using a VariantDir
1155     if not dspfile is builddspfile:
1156         try:
1157             bdsp = open(str(builddspfile), "w+")
1158         except IOError, detail:
1159             print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1160             raise
1161
1162         bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1163
1164     GenerateDSP(dspfile, source, env)
1165
1166     if env.get('auto_build_solution', 1):
1167         builddswfile = target[1]
1168         dswfile = builddswfile.srcnode()
1169
1170         if not dswfile is builddswfile:
1171
1172             try:
1173                 bdsw = open(str(builddswfile), "w+")
1174             except IOError, detail:
1175                 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1176                 raise
1177
1178             bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1179
1180         GenerateDSW(dswfile, source, env)
1181
1182 def GenerateSolution(target, source, env):
1183     GenerateDSW(target[0], source, env)
1184
1185 def projectEmitter(target, source, env):
1186     """Sets up the DSP dependencies."""
1187
1188     # todo: Not sure what sets source to what user has passed as target,
1189     # but this is what happens. When that is fixed, we also won't have
1190     # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1191     if source[0] == target[0]:
1192         source = []
1193
1194     # make sure the suffix is correct for the version of MSVS we're running.
1195     (base, suff) = SCons.Util.splitext(str(target[0]))
1196     suff = env.subst('$MSVSPROJECTSUFFIX')
1197     target[0] = base + suff
1198
1199     if not source:
1200         source = 'prj_inputs:'
1201         source = source + env.subst('$MSVSSCONSCOM', 1)
1202         source = source + env.subst('$MSVSENCODING', 1)
1203
1204         if 'buildtarget' in env and env['buildtarget'] != None:
1205             if SCons.Util.is_String(env['buildtarget']):
1206                 source = source + ' "%s"' % env['buildtarget']
1207             elif SCons.Util.is_List(env['buildtarget']):
1208                 for bt in env['buildtarget']:
1209                     if SCons.Util.is_String(bt):
1210                         source = source + ' "%s"' % bt
1211                     else:
1212                         try: source = source + ' "%s"' % bt.get_abspath()
1213                         except AttributeError: raise SCons.Errors.InternalError, \
1214                             "buildtarget can be a string, a node, a list of strings or nodes, or None"
1215             else:
1216                 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1217                 except AttributeError: raise SCons.Errors.InternalError, \
1218                     "buildtarget can be a string, a node, a list of strings or nodes, or None"
1219
1220         if 'outdir' in env and env['outdir'] != None:
1221             if SCons.Util.is_String(env['outdir']):
1222                 source = source + ' "%s"' % env['outdir']
1223             elif SCons.Util.is_List(env['outdir']):
1224                 for s in env['outdir']:
1225                     if SCons.Util.is_String(s):
1226                         source = source + ' "%s"' % s
1227                     else:
1228                         try: source = source + ' "%s"' % s.get_abspath()
1229                         except AttributeError: raise SCons.Errors.InternalError, \
1230                             "outdir can be a string, a node, a list of strings or nodes, or None"
1231             else:
1232                 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1233                 except AttributeError: raise SCons.Errors.InternalError, \
1234                     "outdir can be a string, a node, a list of strings or nodes, or None"
1235
1236         if 'name' in env:
1237             if SCons.Util.is_String(env['name']):
1238                 source = source + ' "%s"' % env['name']
1239             else:
1240                 raise SCons.Errors.InternalError, "name must be a string"
1241
1242         if 'variant' in env:
1243             if SCons.Util.is_String(env['variant']):
1244                 source = source + ' "%s"' % env['variant']
1245             elif SCons.Util.is_List(env['variant']):
1246                 for variant in env['variant']:
1247                     if SCons.Util.is_String(variant):
1248                         source = source + ' "%s"' % variant
1249                     else:
1250                         raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1251             else:
1252                 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1253         else:
1254             raise SCons.Errors.InternalError, "variant must be specified"
1255
1256         for s in _DSPGenerator.srcargs:
1257             if s in env:
1258                 if SCons.Util.is_String(env[s]):
1259                     source = source + ' "%s' % env[s]
1260                 elif SCons.Util.is_List(env[s]):
1261                     for t in env[s]:
1262                         if SCons.Util.is_String(t):
1263                             source = source + ' "%s"' % t
1264                         else:
1265                             raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1266                 else:
1267                     raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1268
1269         source = source + ' "%s"' % str(target[0])
1270         source = [SCons.Node.Python.Value(source)]
1271
1272     targetlist = [target[0]]
1273     sourcelist = source
1274
1275     if env.get('auto_build_solution', 1):
1276         env['projects'] = targetlist
1277         t, s = solutionEmitter(target, target, env)
1278         targetlist = targetlist + t
1279
1280     return (targetlist, sourcelist)
1281
1282 def solutionEmitter(target, source, env):
1283     """Sets up the DSW dependencies."""
1284
1285     # todo: Not sure what sets source to what user has passed as target,
1286     # but this is what happens. When that is fixed, we also won't have
1287     # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1288     if source[0] == target[0]:
1289         source = []
1290
1291     # make sure the suffix is correct for the version of MSVS we're running.
1292     (base, suff) = SCons.Util.splitext(str(target[0]))
1293     suff = env.subst('$MSVSSOLUTIONSUFFIX')
1294     target[0] = base + suff
1295
1296     if not source:
1297         source = 'sln_inputs:'
1298
1299         if 'name' in env:
1300             if SCons.Util.is_String(env['name']):
1301                 source = source + ' "%s"' % env['name']
1302             else:
1303                 raise SCons.Errors.InternalError, "name must be a string"
1304
1305         if 'variant' in env:
1306             if SCons.Util.is_String(env['variant']):
1307                 source = source + ' "%s"' % env['variant']
1308             elif SCons.Util.is_List(env['variant']):
1309                 for variant in env['variant']:
1310                     if SCons.Util.is_String(variant):
1311                         source = source + ' "%s"' % variant
1312                     else:
1313                         raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1314             else:
1315                 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1316         else:
1317             raise SCons.Errors.InternalError, "variant must be specified"
1318
1319         if 'slnguid' in env:
1320             if SCons.Util.is_String(env['slnguid']):
1321                 source = source + ' "%s"' % env['slnguid']
1322             else:
1323                 raise SCons.Errors.InternalError, "slnguid must be a string"
1324
1325         if 'projects' in env:
1326             if SCons.Util.is_String(env['projects']):
1327                 source = source + ' "%s"' % env['projects']
1328             elif SCons.Util.is_List(env['projects']):
1329                 for t in env['projects']:
1330                     if SCons.Util.is_String(t):
1331                         source = source + ' "%s"' % t
1332
1333         source = source + ' "%s"' % str(target[0])
1334         source = [SCons.Node.Python.Value(source)]
1335
1336     return ([target[0]], source)
1337
1338 projectAction = SCons.Action.Action(GenerateProject, None)
1339
1340 solutionAction = SCons.Action.Action(GenerateSolution, None)
1341
1342 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1343                                        suffix = '$MSVSPROJECTSUFFIX',
1344                                        emitter = projectEmitter)
1345
1346 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1347                                         suffix = '$MSVSSOLUTIONSUFFIX',
1348                                         emitter = solutionEmitter)
1349
1350 default_MSVS_SConscript = None
1351
1352 def generate(env):
1353     """Add Builders and construction variables for Microsoft Visual
1354     Studio project files to an Environment."""
1355     try:
1356         env['BUILDERS']['MSVSProject']
1357     except KeyError:
1358         env['BUILDERS']['MSVSProject'] = projectBuilder
1359
1360     try:
1361         env['BUILDERS']['MSVSSolution']
1362     except KeyError:
1363         env['BUILDERS']['MSVSSolution'] = solutionBuilder
1364
1365     env['MSVSPROJECTCOM'] = projectAction
1366     env['MSVSSOLUTIONCOM'] = solutionAction
1367
1368     if SCons.Script.call_stack:
1369         # XXX Need to find a way to abstract this; the build engine
1370         # shouldn't depend on anything in SCons.Script.
1371         env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1372     else:
1373         global default_MSVS_SConscript
1374         if default_MSVS_SConscript is None:
1375             default_MSVS_SConscript = env.File('SConstruct')
1376         env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1377
1378     env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1379     env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1380     env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1381     env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1382     env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1383     env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1384     env['MSVSENCODING'] = 'Windows-1252'
1385
1386     # Set-up ms tools paths for default version
1387     msvc_setup_env_once(env)
1388
1389     if 'MSVS_VERSION' in env:
1390         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1391     else:
1392         (version_num, suite) = (7.0, None) # guess at a default
1393     if 'MSVS' not in env:
1394         env['MSVS'] = {}
1395     if (version_num < 7.0):
1396         env['MSVS']['PROJECTSUFFIX']  = '.dsp'
1397         env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1398     else:
1399         env['MSVS']['PROJECTSUFFIX']  = '.vcproj'
1400         env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1401
1402     env['GET_MSVSPROJECTSUFFIX']  = GetMSVSProjectSuffix
1403     env['GET_MSVSSOLUTIONSUFFIX']  = GetMSVSSolutionSuffix
1404     env['MSVSPROJECTSUFFIX']  = '${GET_MSVSPROJECTSUFFIX}'
1405     env['MSVSSOLUTIONSUFFIX']  = '${GET_MSVSSOLUTIONSUFFIX}'
1406     env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1407
1408 def exists(env):
1409     return msvc_exists()
1410
1411 # Local Variables:
1412 # tab-width:4
1413 # indent-tabs-mode:nil
1414 # End:
1415 # vim: set expandtab tabstop=4 shiftwidth=4: