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