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             self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
294
295         def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
296             config = Config()
297             config.buildtarget = buildtarget
298             config.outdir = outdir
299             config.cmdargs = cmdargs
300             config.runfile = runfile
301
302             match = re.match('(.*)\|(.*)', variant)
303             if match:
304                 config.variant = match.group(1)
305                 config.platform = match.group(2)
306             else:
307                 config.variant = variant
308                 config.platform = 'Win32'
309
310             self.configs[variant] = config
311             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
312
313         for i in range(len(variants)):
314             AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
315
316         self.platforms = []
317         for key in self.configs.keys():
318             platform = self.configs[key].platform
319             if not platform in self.platforms:
320                 self.platforms.append(platform)
321
322     def Build(self):
323         pass
324
325 V6DSPHeader = """\
326 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
327 # Microsoft Developer Studio Generated Build File, Format Version 6.00
328 # ** DO NOT EDIT **
329
330 # TARGTYPE "Win32 (x86) External Target" 0x0106
331
332 CFG=%(name)s - Win32 %(confkey)s
333 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
334 !MESSAGE use the Export Makefile command and run
335 !MESSAGE 
336 !MESSAGE NMAKE /f "%(name)s.mak".
337 !MESSAGE 
338 !MESSAGE You can specify a configuration when running NMAKE
339 !MESSAGE by defining the macro CFG on the command line. For example:
340 !MESSAGE 
341 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
342 !MESSAGE 
343 !MESSAGE Possible choices for configuration are:
344 !MESSAGE 
345 """
346
347 class _GenerateV6DSP(_DSPGenerator):
348     """Generates a Project file for MSVS 6.0"""
349
350     def PrintHeader(self):
351         # pick a default config
352         confkeys = sorted(self.configs.keys())
353
354         name = self.name
355         confkey = confkeys[0]
356
357         self.file.write(V6DSPHeader % locals())
358
359         for kind in confkeys:
360             self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
361
362         self.file.write('!MESSAGE \n\n')
363
364     def PrintProject(self):
365         name = self.name
366         self.file.write('# Begin Project\n'
367                         '# PROP AllowPerConfigDependencies 0\n'
368                         '# PROP Scc_ProjName ""\n'
369                         '# PROP Scc_LocalPath ""\n\n')
370
371         first = 1
372         confkeys = sorted(self.configs.keys())
373         for kind in confkeys:
374             outdir = self.configs[kind].outdir
375             buildtarget = self.configs[kind].buildtarget
376             if first == 1:
377                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
378                 first = 0
379             else:
380                 self.file.write('\n!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
381
382             env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
383             if not env_has_buildtarget:
384                 self.env['MSVSBUILDTARGET'] = buildtarget
385
386             # have to write this twice, once with the BASE settings, and once without
387             for base in ("BASE ",""):
388                 self.file.write('# PROP %sUse_MFC 0\n'
389                                 '# PROP %sUse_Debug_Libraries ' % (base, base))
390                 # TODO(1.5):
391                 #if kind.lower().find('debug') < 0:
392                 if kind.lower().find('debug') < 0:
393                     self.file.write('0\n')
394                 else:
395                     self.file.write('1\n')
396                 self.file.write('# PROP %sOutput_Dir "%s"\n'
397                                 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
398                 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
399                 self.file.write('# PROP %sCmd_Line "%s"\n'
400                                 '# PROP %sRebuild_Opt "-c && %s"\n'
401                                 '# PROP %sTarget_File "%s"\n'
402                                 '# PROP %sBsc_Name ""\n'
403                                 '# PROP %sTarget_Dir ""\n'\
404                                 %(base,cmd,base,cmd,base,buildtarget,base,base))
405
406             if not env_has_buildtarget:
407                 del self.env['MSVSBUILDTARGET']
408
409         self.file.write('\n!ENDIF\n\n'
410                         '# Begin Target\n\n')
411         for kind in confkeys:
412             self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
413         self.file.write('\n')
414         first = 0
415         for kind in confkeys:
416             if first == 0:
417                 self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
418                 first = 1
419             else:
420                 self.file.write('!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
421         self.file.write('!ENDIF \n\n')
422         self.PrintSourceFiles()
423         self.file.write('# End Target\n'
424                         '# End Project\n')
425
426         if self.nokeep == 0:
427             # now we pickle some data and add it to the file -- MSDEV will ignore it.
428             pdata = pickle.dumps(self.configs,1)
429             pdata = base64.encodestring(pdata)
430             self.file.write(pdata + '\n')
431             pdata = pickle.dumps(self.sources,1)
432             pdata = base64.encodestring(pdata)
433             self.file.write(pdata + '\n')
434
435     def PrintSourceFiles(self):
436         categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
437                       'Header Files': 'h|hpp|hxx|hm|inl',
438                       'Local Headers': 'h|hpp|hxx|hm|inl',
439                       'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
440                       'Other Files': ''}
441
442         cats = categories.keys()
443         cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
444         for kind in cats:
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 = hierarchy.items()
699         sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
700
701         # First folders, then files
702         for key, value in sorteditems:
703             if SCons.Util.is_Dict(value):
704                 self.file.write('\t\t\t<Filter\n'
705                                 '\t\t\t\tName="%s"\n'
706                                 '\t\t\t\tFilter="">\n' % (key))
707                 self.printSources(value, commonprefix)
708                 self.file.write('\t\t\t</Filter>\n')
709
710         for key, value in sorteditems:
711             if SCons.Util.is_String(value):
712                 file = value
713                 if commonprefix:
714                     file = os.path.join(commonprefix, value)
715                 file = os.path.normpath(file)
716                 self.file.write('\t\t\t<File\n'
717                                 '\t\t\t\tRelativePath="%s">\n'
718                                 '\t\t\t</File>\n' % (file))
719
720     def PrintSourceFiles(self):
721         categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
722                       'Header Files': 'h;hpp;hxx;hm;inl',
723                       'Local Headers': 'h;hpp;hxx;hm;inl',
724                       'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
725                       'Other Files': ''}
726
727         self.file.write('\t<Files>\n')
728
729         cats = categories.keys()
730         cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
731         cats = [k for k in cats if self.sources[k]]
732         for kind in cats:
733             if len(cats) > 1:
734                 self.file.write('\t\t<Filter\n'
735                                 '\t\t\tName="%s"\n'
736                                 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
737
738             sources = self.sources[kind]
739
740             # First remove any common prefix
741             commonprefix = None
742             if len(sources) > 1:
743                 s = list(map(os.path.normpath, sources))
744                 # take the dirname because the prefix may include parts
745                 # of the filenames (e.g. if you have 'dir\abcd' and
746                 # 'dir\acde' then the cp will be 'dir\a' )
747                 cp = os.path.dirname( os.path.commonprefix(s) )
748                 if cp and s[0][len(cp)] == os.sep:
749                     # +1 because the filename starts after the separator
750                     sources = [s[len(cp)+1:] for s in sources]
751                     commonprefix = cp
752             elif len(sources) == 1:
753                 commonprefix = os.path.dirname( sources[0] )
754                 sources[0] = os.path.basename( sources[0] )
755
756             hierarchy = makeHierarchy(sources)
757             self.printSources(hierarchy, commonprefix=commonprefix)
758
759             if len(cats)>1:
760                 self.file.write('\t\t</Filter>\n')
761
762         # add the SConscript file outside of the groups
763         self.file.write('\t\t<File\n'
764                         '\t\t\tRelativePath="%s">\n'
765                         '\t\t</File>\n' % str(self.sconscript))
766
767         self.file.write('\t</Files>\n'
768                         '\t<Globals>\n'
769                         '\t</Globals>\n')
770
771     def Parse(self):
772         try:
773             dspfile = open(self.dspabs,'r')
774         except IOError:
775             return # doesn't exist yet, so can't add anything to configs.
776
777         line = dspfile.readline()
778         while line:
779             # TODO(1.5)
780             #if line.find('<!-- SCons Data:') > -1:
781             if line.find('<!-- SCons Data:') > -1:
782                 break
783             line = dspfile.readline()
784
785         line = dspfile.readline()
786         datas = line
787         while line and line != '\n':
788             line = dspfile.readline()
789             datas = datas + line
790
791         # OK, we've found our little pickled cache of data.
792         try:
793             datas = base64.decodestring(datas)
794             data = pickle.loads(datas)
795         except KeyboardInterrupt:
796             raise
797         except:
798             return # unable to unpickle any data for some reason
799
800         self.configs.update(data)
801
802         data = None
803         line = dspfile.readline()
804         datas = line
805         while line and line != '\n':
806             line = dspfile.readline()
807             datas = datas + line
808
809         # OK, we've found our little pickled cache of data.
810         try:
811             datas = base64.decodestring(datas)
812             data = pickle.loads(datas)
813         except KeyboardInterrupt:
814             raise
815         except:
816             return # unable to unpickle any data for some reason
817
818         self.sources.update(data)
819
820     def Build(self):
821         try:
822             self.file = open(self.dspabs,'w')
823         except IOError, detail:
824             raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
825         else:
826             self.PrintHeader()
827             self.PrintProject()
828             self.file.close()
829
830 class _DSWGenerator:
831     """ Base class for DSW generators """
832     def __init__(self, dswfile, source, env):
833         self.dswfile = os.path.normpath(str(dswfile))
834         self.env = env
835
836         if 'projects' not in env:
837             raise SCons.Errors.UserError("You must specify a 'projects' argument to create an MSVSSolution.")
838         projects = env['projects']
839         if not SCons.Util.is_List(projects):
840             raise SCons.Errors.InternalError("The 'projects' argument must be a list of nodes.")
841         projects = SCons.Util.flatten(projects)
842         if len(projects) < 1:
843             raise SCons.Errors.UserError("You must specify at least one project to create an MSVSSolution.")
844         self.dspfiles = list(map(str, projects))
845
846         if 'name' in self.env:
847             self.name = self.env['name']
848         else:
849             self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
850         self.name = self.env.subst(self.name)
851
852     def Build(self):
853         pass
854
855 class _GenerateV7DSW(_DSWGenerator):
856     """Generates a Solution file for MSVS .NET"""
857     def __init__(self, dswfile, source, env):
858         _DSWGenerator.__init__(self, dswfile, source, env)
859
860         self.file = None
861         self.version = self.env['MSVS_VERSION']
862         self.version_num, self.suite = msvs_parse_version(self.version)
863         self.versionstr = '7.00'
864         if self.version_num >= 8.0:
865             self.versionstr = '9.00'
866         elif self.version_num >= 7.1:
867             self.versionstr = '8.00'
868         if self.version_num >= 8.0:
869             self.versionstr = '9.00'
870
871         if 'slnguid' in env and env['slnguid']:
872             self.slnguid = env['slnguid']
873         else:
874             self.slnguid = _generateGUID(dswfile, self.name)
875
876         self.configs = {}
877
878         self.nokeep = 0
879         if 'nokeep' in env and env['variant'] != 0:
880             self.nokeep = 1
881
882         if self.nokeep == 0 and os.path.exists(self.dswfile):
883             self.Parse()
884
885         def AddConfig(self, variant, dswfile=dswfile):
886             config = Config()
887
888             match = re.match('(.*)\|(.*)', variant)
889             if match:
890                 config.variant = match.group(1)
891                 config.platform = match.group(2)
892             else:
893                 config.variant = variant
894                 config.platform = 'Win32'
895
896             self.configs[variant] = config
897             print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
898
899         if 'variant' not in env:
900             raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
901                   "'Release') to create an MSVS Solution File.")
902         elif SCons.Util.is_String(env['variant']):
903             AddConfig(self, env['variant'])
904         elif SCons.Util.is_List(env['variant']):
905             for variant in env['variant']:
906                 AddConfig(self, variant)
907
908         self.platforms = []
909         for key in self.configs.keys():
910             platform = self.configs[key].platform
911             if not platform in self.platforms:
912                 self.platforms.append(platform)
913
914     def Parse(self):
915         try:
916             dswfile = open(self.dswfile,'r')
917         except IOError:
918             return # doesn't exist yet, so can't add anything to configs.
919
920         line = dswfile.readline()
921         while line:
922             if line[:9] == "EndGlobal":
923                 break
924             line = dswfile.readline()
925
926         line = dswfile.readline()
927         datas = line
928         while line:
929             line = dswfile.readline()
930             datas = datas + line
931
932         # OK, we've found our little pickled cache of data.
933         try:
934             datas = base64.decodestring(datas)
935             data = pickle.loads(datas)
936         except KeyboardInterrupt:
937             raise
938         except:
939             return # unable to unpickle any data for some reason
940
941         self.configs.update(data)
942
943     def PrintSolution(self):
944         """Writes a solution file"""
945         self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
946         if self.version_num >= 8.0:
947             self.file.write('# Visual Studio 2005\n')
948         for p in self.dspfiles:
949             name = os.path.basename(p)
950             base, suffix = SCons.Util.splitext(name)
951             if suffix == '.vcproj':
952                 name = base
953             guid = _generateGUID(p, '')
954             self.file.write('Project("%s") = "%s", "%s", "%s"\n'
955                             % ( external_makefile_guid, name, p, guid ) )
956             if self.version_num >= 7.1 and self.version_num < 8.0:
957                 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
958                                 '\tEndProjectSection\n')
959             self.file.write('EndProject\n')
960
961         self.file.write('Global\n')
962
963         env = self.env
964         if 'MSVS_SCC_PROVIDER' in env:
965             dspfile_base = os.path.basename(self.dspfile)
966             slnguid = self.slnguid
967             scc_provider = env.get('MSVS_SCC_PROVIDER', '')
968             scc_provider = scc_provider.replace(' ', r'\u0020')
969             scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
970             # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
971             scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
972             scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
973             # project_guid = env.get('MSVS_PROJECT_GUID', '')
974
975             self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
976                             '\t\tSccNumberOfProjects = 2\n'
977                             '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
978                             '\t\tSccLocalPath0 = %(scc_local_path)s\n'
979                             '\t\tCanCheckoutShared = true\n'
980                             '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
981                             '\t\tSccProjectName1 = %(scc_project_name)s\n'
982                             '\t\tSccLocalPath1 = %(scc_local_path)s\n'
983                             '\t\tSccProvider1 = %(scc_provider)s\n'
984                             '\t\tCanCheckoutShared = true\n'
985                             '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
986                             '\t\tSolutionUniqueID = %(slnguid)s\n'
987                             '\tEndGlobalSection\n' % locals())
988
989         if self.version_num >= 8.0:
990             self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
991         else:
992             self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
993
994         confkeys = sorted(self.configs.keys())
995         cnt = 0
996         for name in confkeys:
997             variant = self.configs[name].variant
998             platform = self.configs[name].platform
999             if self.version_num >= 8.0:
1000                 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1001             else:
1002                 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1003             cnt = cnt + 1
1004         self.file.write('\tEndGlobalSection\n')
1005         if self.version_num < 7.1:
1006             self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1007                             '\tEndGlobalSection\n')
1008         if self.version_num >= 8.0:
1009             self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1010         else:
1011             self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1012
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                 for p in self.dspfiles:
1018                     guid = _generateGUID(p, '')
1019                     self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1020                                     '\t\t%s.%s|%s.Build.0 = %s|%s\n'  % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1021             else:
1022                 for p in self.dspfiles:
1023                     guid = _generateGUID(p, '')
1024                     self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1025                                     '\t\t%s.%s.Build.0 = %s|%s\n'  %(guid,variant,variant,platform,guid,variant,variant,platform))
1026
1027         self.file.write('\tEndGlobalSection\n')
1028
1029         if self.version_num >= 8.0:
1030             self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1031                             '\t\tHideSolutionNode = FALSE\n'
1032                             '\tEndGlobalSection\n')
1033         else:
1034             self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1035                             '\tEndGlobalSection\n'
1036                             '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1037                             '\tEndGlobalSection\n')
1038         self.file.write('EndGlobal\n')
1039         if self.nokeep == 0:
1040             pdata = pickle.dumps(self.configs,1)
1041             pdata = base64.encodestring(pdata)
1042             self.file.write(pdata + '\n')
1043
1044     def Build(self):
1045         try:
1046             self.file = open(self.dswfile,'w')
1047         except IOError, detail:
1048             raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1049         else:
1050             self.PrintSolution()
1051             self.file.close()
1052
1053 V6DSWHeader = """\
1054 Microsoft Developer Studio Workspace File, Format Version 6.00
1055 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1056
1057 ###############################################################################
1058
1059 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1060
1061 Package=<5>
1062 {{{
1063 }}}
1064
1065 Package=<4>
1066 {{{
1067 }}}
1068
1069 ###############################################################################
1070
1071 Global:
1072
1073 Package=<5>
1074 {{{
1075 }}}
1076
1077 Package=<3>
1078 {{{
1079 }}}
1080
1081 ###############################################################################
1082 """
1083
1084 class _GenerateV6DSW(_DSWGenerator):
1085     """Generates a Workspace file for MSVS 6.0"""
1086
1087     def PrintWorkspace(self):
1088         """ writes a DSW file """
1089         name = self.name
1090         dspfile = self.dspfiles[0]
1091         self.file.write(V6DSWHeader % locals())
1092
1093     def Build(self):
1094         try:
1095             self.file = open(self.dswfile,'w')
1096         except IOError, detail:
1097             raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1098         else:
1099             self.PrintWorkspace()
1100             self.file.close()
1101
1102
1103 def GenerateDSP(dspfile, source, env):
1104     """Generates a Project file based on the version of MSVS that is being used"""
1105
1106     version_num = 6.0
1107     if 'MSVS_VERSION' in env:
1108         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1109     if version_num >= 7.0:
1110         g = _GenerateV7DSP(dspfile, source, env)
1111         g.Build()
1112     else:
1113         g = _GenerateV6DSP(dspfile, source, env)
1114         g.Build()
1115
1116 def GenerateDSW(dswfile, source, env):
1117     """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1118
1119     version_num = 6.0
1120     if 'MSVS_VERSION' in env:
1121         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1122     if version_num >= 7.0:
1123         g = _GenerateV7DSW(dswfile, source, env)
1124         g.Build()
1125     else:
1126         g = _GenerateV6DSW(dswfile, source, env)
1127         g.Build()
1128
1129
1130 ##############################################################################
1131 # Above here are the classes and functions for generation of
1132 # DSP/DSW/SLN/VCPROJ files.
1133 ##############################################################################
1134
1135 def GetMSVSProjectSuffix(target, source, env, for_signature):
1136      return env['MSVS']['PROJECTSUFFIX']
1137
1138 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1139      return env['MSVS']['SOLUTIONSUFFIX']
1140
1141 def GenerateProject(target, source, env):
1142     # generate the dsp file, according to the version of MSVS.
1143     builddspfile = target[0]
1144     dspfile = builddspfile.srcnode()
1145
1146     # this detects whether or not we're using a VariantDir
1147     if not dspfile is builddspfile:
1148         try:
1149             bdsp = open(str(builddspfile), "w+")
1150         except IOError, detail:
1151             print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1152             raise
1153
1154         bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1155
1156     GenerateDSP(dspfile, source, env)
1157
1158     if env.get('auto_build_solution', 1):
1159         builddswfile = target[1]
1160         dswfile = builddswfile.srcnode()
1161
1162         if not dswfile is builddswfile:
1163
1164             try:
1165                 bdsw = open(str(builddswfile), "w+")
1166             except IOError, detail:
1167                 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1168                 raise
1169
1170             bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1171
1172         GenerateDSW(dswfile, source, env)
1173
1174 def GenerateSolution(target, source, env):
1175     GenerateDSW(target[0], source, env)
1176
1177 def projectEmitter(target, source, env):
1178     """Sets up the DSP dependencies."""
1179
1180     # todo: Not sure what sets source to what user has passed as target,
1181     # but this is what happens. When that is fixed, we also won't have
1182     # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1183     if source[0] == target[0]:
1184         source = []
1185
1186     # make sure the suffix is correct for the version of MSVS we're running.
1187     (base, suff) = SCons.Util.splitext(str(target[0]))
1188     suff = env.subst('$MSVSPROJECTSUFFIX')
1189     target[0] = base + suff
1190
1191     if not source:
1192         source = 'prj_inputs:'
1193         source = source + env.subst('$MSVSSCONSCOM', 1)
1194         source = source + env.subst('$MSVSENCODING', 1)
1195
1196         if 'buildtarget' in env and env['buildtarget'] != None:
1197             if SCons.Util.is_String(env['buildtarget']):
1198                 source = source + ' "%s"' % env['buildtarget']
1199             elif SCons.Util.is_List(env['buildtarget']):
1200                 for bt in env['buildtarget']:
1201                     if SCons.Util.is_String(bt):
1202                         source = source + ' "%s"' % bt
1203                     else:
1204                         try: source = source + ' "%s"' % bt.get_abspath()
1205                         except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1206             else:
1207                 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1208                 except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1209
1210         if 'outdir' in env and env['outdir'] != None:
1211             if SCons.Util.is_String(env['outdir']):
1212                 source = source + ' "%s"' % env['outdir']
1213             elif SCons.Util.is_List(env['outdir']):
1214                 for s in env['outdir']:
1215                     if SCons.Util.is_String(s):
1216                         source = source + ' "%s"' % s
1217                     else:
1218                         try: source = source + ' "%s"' % s.get_abspath()
1219                         except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1220             else:
1221                 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1222                 except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1223
1224         if 'name' in env:
1225             if SCons.Util.is_String(env['name']):
1226                 source = source + ' "%s"' % env['name']
1227             else:
1228                 raise SCons.Errors.InternalError("name must be a string")
1229
1230         if 'variant' in env:
1231             if SCons.Util.is_String(env['variant']):
1232                 source = source + ' "%s"' % env['variant']
1233             elif SCons.Util.is_List(env['variant']):
1234                 for variant in env['variant']:
1235                     if SCons.Util.is_String(variant):
1236                         source = source + ' "%s"' % variant
1237                     else:
1238                         raise SCons.Errors.InternalError("name must be a string or a list of strings")
1239             else:
1240                 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1241         else:
1242             raise SCons.Errors.InternalError("variant must be specified")
1243
1244         for s in _DSPGenerator.srcargs:
1245             if s in env:
1246                 if SCons.Util.is_String(env[s]):
1247                     source = source + ' "%s' % env[s]
1248                 elif SCons.Util.is_List(env[s]):
1249                     for t in env[s]:
1250                         if SCons.Util.is_String(t):
1251                             source = source + ' "%s"' % t
1252                         else:
1253                             raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1254                 else:
1255                     raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1256
1257         source = source + ' "%s"' % str(target[0])
1258         source = [SCons.Node.Python.Value(source)]
1259
1260     targetlist = [target[0]]
1261     sourcelist = source
1262
1263     if env.get('auto_build_solution', 1):
1264         env['projects'] = targetlist
1265         t, s = solutionEmitter(target, target, env)
1266         targetlist = targetlist + t
1267
1268     return (targetlist, sourcelist)
1269
1270 def solutionEmitter(target, source, env):
1271     """Sets up the DSW dependencies."""
1272
1273     # todo: Not sure what sets source to what user has passed as target,
1274     # but this is what happens. When that is fixed, we also won't have
1275     # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1276     if source[0] == target[0]:
1277         source = []
1278
1279     # make sure the suffix is correct for the version of MSVS we're running.
1280     (base, suff) = SCons.Util.splitext(str(target[0]))
1281     suff = env.subst('$MSVSSOLUTIONSUFFIX')
1282     target[0] = base + suff
1283
1284     if not source:
1285         source = 'sln_inputs:'
1286
1287         if 'name' in env:
1288             if SCons.Util.is_String(env['name']):
1289                 source = source + ' "%s"' % env['name']
1290             else:
1291                 raise SCons.Errors.InternalError("name must be a string")
1292
1293         if 'variant' in env:
1294             if SCons.Util.is_String(env['variant']):
1295                 source = source + ' "%s"' % env['variant']
1296             elif SCons.Util.is_List(env['variant']):
1297                 for variant in env['variant']:
1298                     if SCons.Util.is_String(variant):
1299                         source = source + ' "%s"' % variant
1300                     else:
1301                         raise SCons.Errors.InternalError("name must be a string or a list of strings")
1302             else:
1303                 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1304         else:
1305             raise SCons.Errors.InternalError("variant must be specified")
1306
1307         if 'slnguid' in env:
1308             if SCons.Util.is_String(env['slnguid']):
1309                 source = source + ' "%s"' % env['slnguid']
1310             else:
1311                 raise SCons.Errors.InternalError("slnguid must be a string")
1312
1313         if 'projects' in env:
1314             if SCons.Util.is_String(env['projects']):
1315                 source = source + ' "%s"' % env['projects']
1316             elif SCons.Util.is_List(env['projects']):
1317                 for t in env['projects']:
1318                     if SCons.Util.is_String(t):
1319                         source = source + ' "%s"' % t
1320
1321         source = source + ' "%s"' % str(target[0])
1322         source = [SCons.Node.Python.Value(source)]
1323
1324     return ([target[0]], source)
1325
1326 projectAction = SCons.Action.Action(GenerateProject, None)
1327
1328 solutionAction = SCons.Action.Action(GenerateSolution, None)
1329
1330 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1331                                        suffix = '$MSVSPROJECTSUFFIX',
1332                                        emitter = projectEmitter)
1333
1334 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1335                                         suffix = '$MSVSSOLUTIONSUFFIX',
1336                                         emitter = solutionEmitter)
1337
1338 default_MSVS_SConscript = None
1339
1340 def generate(env):
1341     """Add Builders and construction variables for Microsoft Visual
1342     Studio project files to an Environment."""
1343     try:
1344         env['BUILDERS']['MSVSProject']
1345     except KeyError:
1346         env['BUILDERS']['MSVSProject'] = projectBuilder
1347
1348     try:
1349         env['BUILDERS']['MSVSSolution']
1350     except KeyError:
1351         env['BUILDERS']['MSVSSolution'] = solutionBuilder
1352
1353     env['MSVSPROJECTCOM'] = projectAction
1354     env['MSVSSOLUTIONCOM'] = solutionAction
1355
1356     if SCons.Script.call_stack:
1357         # XXX Need to find a way to abstract this; the build engine
1358         # shouldn't depend on anything in SCons.Script.
1359         env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1360     else:
1361         global default_MSVS_SConscript
1362         if default_MSVS_SConscript is None:
1363             default_MSVS_SConscript = env.File('SConstruct')
1364         env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1365
1366     env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1367     env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1368     env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1369     env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1370     env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1371     env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1372     env['MSVSENCODING'] = 'Windows-1252'
1373
1374     # Set-up ms tools paths for default version
1375     msvc_setup_env_once(env)
1376
1377     if 'MSVS_VERSION' in env:
1378         version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1379     else:
1380         (version_num, suite) = (7.0, None) # guess at a default
1381     if 'MSVS' not in env:
1382         env['MSVS'] = {}
1383     if (version_num < 7.0):
1384         env['MSVS']['PROJECTSUFFIX']  = '.dsp'
1385         env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1386     else:
1387         env['MSVS']['PROJECTSUFFIX']  = '.vcproj'
1388         env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1389
1390     env['GET_MSVSPROJECTSUFFIX']  = GetMSVSProjectSuffix
1391     env['GET_MSVSSOLUTIONSUFFIX']  = GetMSVSSolutionSuffix
1392     env['MSVSPROJECTSUFFIX']  = '${GET_MSVSPROJECTSUFFIX}'
1393     env['MSVSSOLUTIONSUFFIX']  = '${GET_MSVSSOLUTIONSUFFIX}'
1394     env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1395
1396 def exists(env):
1397     return msvc_exists()
1398
1399 # Local Variables:
1400 # tab-width:4
1401 # indent-tabs-mode:nil
1402 # End:
1403 # vim: set expandtab tabstop=4 shiftwidth=4: