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