3 Tool-specific initialization for Microsoft Visual Studio project files.
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()
12 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
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:
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
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.
33 __revision__ = "src/engine/SCons/Tool/msvs.py 5023 2010/06/14 22:05:46 scons"
41 # compat layer imports "cPickle" for us if it's available.
48 import SCons.Platform.win32
49 import SCons.Script.SConscript
53 from MSCommon import msvc_exists, msvc_setup_env_once
54 from SCons.Defaults import processDefines
56 ##############################################################################
57 # Below here are the classes and functions for generation of
58 # DSP/DSW/SLN/VCPROJ files.
59 ##############################################################################
62 s = s.replace("&", "&") # do this first
63 s = s.replace("'", "'")
64 s = s.replace('"', """)
67 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
69 def _generateGUID(slnfile, name):
70 """This generates a dummy GUID for the sln file to use. It is
71 based on the MD5 signatures of the sln filename plus the name of
72 the project. It basically just needs to be unique, and not
73 change with each invocation."""
75 # Normalize the slnfile path to a Windows path (\ separators) so
76 # the generated file has a consistent GUID even if we generate
77 # it on a non-Windows platform.
78 m.update(ntpath.normpath(str(slnfile)) + str(name))
79 solution = m.hexdigest().upper()
80 # convert most of the signature to GUID form (discard the rest)
81 solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
84 version_re = re.compile(r'(\d+\.\d+)(.*)')
86 def msvs_parse_version(s):
88 Split a Visual Studio version, which may in fact be something like
89 '7.0Exp', into is version number (returned as a float) and trailing
92 num, suite = version_re.match(s).groups()
93 return float(num), suite
95 # This is how we re-invoke SCons from inside MSVS Project files.
96 # The problem is that we might have been invoked as either scons.bat
97 # or scons.py. If we were invoked directly as scons.py, then we could
98 # use sys.argv[0] to find the SCons "executable," but that doesn't work
99 # if we were invoked as scons.bat, which uses "python -c" to execute
100 # things and ends up with "-c" as sys.argv[0]. Consequently, we have
101 # the MSVS Project file invoke SCons the same way that scons.bat does,
102 # which works regardless of how we were invoked.
103 def getExecScriptMain(env, xml=None):
104 scons_home = env.get('SCONS_HOME')
105 if not scons_home and 'SCONS_LIB_DIR' in os.environ:
106 scons_home = os.environ['SCONS_LIB_DIR']
108 exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
110 version = SCons.__version__
111 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()
113 exec_script_main = xmlify(exec_script_main)
114 return exec_script_main
116 # The string for the Python executable we tell the Project file to use
117 # is either sys.executable or, if an external PYTHON_ROOT environment
118 # variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
119 # pluck the actual executable name from sys.executable).
121 python_root = os.environ['PYTHON_ROOT']
123 python_executable = sys.executable
125 python_executable = os.path.join('$$(PYTHON_ROOT)',
126 os.path.split(sys.executable)[1])
128 class Config(object):
131 def splitFully(path):
132 dir, base = os.path.split(path)
133 if dir and dir != '' and dir != path:
134 return splitFully(dir)+[base]
139 def makeHierarchy(sources):
140 '''Break a list of files into a hierarchy; for each value, if it is a string,
141 then it is a file. If it is a dictionary, it is a folder. The string is
142 the original path of the file.'''
146 path = splitFully(file)
149 for part in path[:-1]:
153 dict[path[-1]] = file
155 # print 'Warning: failed to decompose path for '+str(file)
158 class _DSPGenerator(object):
159 """ Base class for DSP generators """
168 def __init__(self, dspfile, source, env):
169 self.dspfile = str(dspfile)
171 get_abspath = dspfile.get_abspath
172 except AttributeError:
173 self.dspabs = os.path.abspath(dspfile)
175 self.dspabs = get_abspath()
177 if 'variant' not in env:
178 raise SCons.Errors.InternalError("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']
185 if 'buildtarget' not in env or env['buildtarget'] == None:
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("Sizes of 'buildtarget' and 'variant' lists must be the same.")
193 for bt in env['buildtarget']:
194 if SCons.Util.is_String(bt):
195 buildtarget.append(bt)
197 buildtarget.append(bt.get_abspath())
199 buildtarget = [env['buildtarget'].get_abspath()]
200 if len(buildtarget) == 1:
204 buildtarget.append(bt)
206 if 'outdir' not in env or env['outdir'] == None:
208 elif SCons.Util.is_String(env['outdir']):
209 outdir = [env['outdir']]
210 elif SCons.Util.is_List(env['outdir']):
211 if len(env['outdir']) != len(variants):
212 raise SCons.Errors.InternalError("Sizes of 'outdir' and 'variant' lists must be the same.")
214 for s in env['outdir']:
215 if SCons.Util.is_String(s):
218 outdir.append(s.get_abspath())
220 outdir = [env['outdir'].get_abspath()]
227 if 'runfile' not in env or env['runfile'] == None:
228 runfile = buildtarget[-1:]
229 elif SCons.Util.is_String(env['runfile']):
230 runfile = [env['runfile']]
231 elif SCons.Util.is_List(env['runfile']):
232 if len(env['runfile']) != len(variants):
233 raise SCons.Errors.InternalError("Sizes of 'runfile' and 'variant' lists must be the same.")
235 for s in env['runfile']:
236 if SCons.Util.is_String(s):
239 runfile.append(s.get_abspath())
241 runfile = [env['runfile'].get_abspath()]
242 if len(runfile) == 1:
248 self.sconscript = env['MSVSSCONSCRIPT']
250 cmdargs = env.get('cmdargs', '')
254 if 'name' in self.env:
255 self.name = self.env['name']
257 self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
258 self.name = self.env.subst(self.name)
268 for n in sourcenames:
274 if 'nokeep' in env and env['variant'] != 0:
277 if self.nokeep == 0 and os.path.exists(self.dspabs):
280 for t in zip(sourcenames,self.srcargs):
282 if SCons.Util.is_List(self.env[t[1]]):
283 for i in self.env[t[1]]:
284 if not i in self.sources[t[0]]:
285 self.sources[t[0]].append(i)
287 if not self.env[t[1]] in self.sources[t[0]]:
288 self.sources[t[0]].append(self.env[t[1]])
290 for n in sourcenames:
291 #TODO 2.4: compat layer supports sorted(key=) but not sort(key=)
292 #TODO 2.4: self.sources[n].sort(key=lambda a: a.lower())
293 self.sources[n] = sorted(self.sources[n], key=lambda a: a.lower())
295 def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
297 config.buildtarget = buildtarget
298 config.outdir = outdir
299 config.cmdargs = cmdargs
300 config.runfile = runfile
302 match = re.match('(.*)\|(.*)', variant)
304 config.variant = match.group(1)
305 config.platform = match.group(2)
307 config.variant = variant
308 config.platform = 'Win32'
310 self.configs[variant] = config
311 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
313 for i in range(len(variants)):
314 AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
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)
326 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
327 # Microsoft Developer Studio Generated Build File, Format Version 6.00
330 # TARGTYPE "Win32 (x86) External Target" 0x0106
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
336 !MESSAGE NMAKE /f "%(name)s.mak".
338 !MESSAGE You can specify a configuration when running NMAKE
339 !MESSAGE by defining the macro CFG on the command line. For example:
341 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
343 !MESSAGE Possible choices for configuration are:
347 class _GenerateV6DSP(_DSPGenerator):
348 """Generates a Project file for MSVS 6.0"""
350 def PrintHeader(self):
351 # pick a default config
352 confkeys = sorted(self.configs.keys())
355 confkey = confkeys[0]
357 self.file.write(V6DSPHeader % locals())
359 for kind in confkeys:
360 self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
362 self.file.write('!MESSAGE \n\n')
364 def PrintProject(self):
366 self.file.write('# Begin Project\n'
367 '# PROP AllowPerConfigDependencies 0\n'
368 '# PROP Scc_ProjName ""\n'
369 '# PROP Scc_LocalPath ""\n\n')
372 confkeys = sorted(self.configs.keys())
373 for kind in confkeys:
374 outdir = self.configs[kind].outdir
375 buildtarget = self.configs[kind].buildtarget
377 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
380 self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
382 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
383 if not env_has_buildtarget:
384 self.env['MSVSBUILDTARGET'] = buildtarget
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 if kind.lower().find('debug') < 0:
391 self.file.write('0\n')
393 self.file.write('1\n')
394 self.file.write('# PROP %sOutput_Dir "%s"\n'
395 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
396 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
397 self.file.write('# PROP %sCmd_Line "%s"\n'
398 '# PROP %sRebuild_Opt "-c && %s"\n'
399 '# PROP %sTarget_File "%s"\n'
400 '# PROP %sBsc_Name ""\n'
401 '# PROP %sTarget_Dir ""\n'\
402 %(base,cmd,base,cmd,base,buildtarget,base,base))
404 if not env_has_buildtarget:
405 del self.env['MSVSBUILDTARGET']
407 self.file.write('\n!ENDIF\n\n'
408 '# Begin Target\n\n')
409 for kind in confkeys:
410 self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
411 self.file.write('\n')
413 for kind in confkeys:
415 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
418 self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
419 self.file.write('!ENDIF \n\n')
420 self.PrintSourceFiles()
421 self.file.write('# End Target\n'
425 # now we pickle some data and add it to the file -- MSDEV will ignore it.
426 pdata = pickle.dumps(self.configs,1)
427 pdata = base64.encodestring(pdata)
428 self.file.write(pdata + '\n')
429 pdata = pickle.dumps(self.sources,1)
430 pdata = base64.encodestring(pdata)
431 self.file.write(pdata + '\n')
433 def PrintSourceFiles(self):
434 categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
435 'Header Files': 'h|hpp|hxx|hm|inl',
436 'Local Headers': 'h|hpp|hxx|hm|inl',
437 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
440 for kind in sorted(categories.keys(), key=lambda a: a.lower()):
441 if not self.sources[kind]:
442 continue # skip empty groups
444 self.file.write('# Begin Group "' + kind + '"\n\n')
445 typelist = categories[kind].replace('|', ';')
446 self.file.write('# PROP Default_Filter "' + typelist + '"\n')
448 for file in self.sources[kind]:
449 file = os.path.normpath(file)
450 self.file.write('# Begin Source File\n\n'
451 'SOURCE="' + file + '"\n'
452 '# End Source File\n')
453 self.file.write('# End Group\n')
455 # add the SConscript file outside of the groups
456 self.file.write('# Begin Source File\n\n'
457 'SOURCE="' + str(self.sconscript) + '"\n'
458 '# End Source File\n')
462 dspfile = open(self.dspabs,'r')
464 return # doesn't exist yet, so can't add anything to configs.
466 line = dspfile.readline()
468 if line.find("# End Project") > -1:
470 line = dspfile.readline()
472 line = dspfile.readline()
474 while line and line != '\n':
475 line = dspfile.readline()
478 # OK, we've found our little pickled cache of data.
480 datas = base64.decodestring(datas)
481 data = pickle.loads(datas)
482 except KeyboardInterrupt:
485 return # unable to unpickle any data for some reason
487 self.configs.update(data)
490 line = dspfile.readline()
492 while line and line != '\n':
493 line = dspfile.readline()
496 # OK, we've found our little pickled cache of data.
497 # it has a "# " in front of it, so we strip that.
499 datas = base64.decodestring(datas)
500 data = pickle.loads(datas)
501 except KeyboardInterrupt:
504 return # unable to unpickle any data for some reason
506 self.sources.update(data)
510 self.file = open(self.dspabs,'w')
511 except IOError, detail:
512 raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
519 <?xml version="1.0" encoding = "%(encoding)s"?>
521 \tProjectType="Visual C++"
522 \tVersion="%(versionstr)s"
525 \tKeyword="MakeFileProj">
528 V7DSPConfiguration = """\
530 \t\t\tName="%(variant)s|%(platform)s"
531 \t\t\tOutputDirectory="%(outdir)s"
532 \t\t\tIntermediateDirectory="%(outdir)s"
533 \t\t\tConfigurationType="0"
535 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
537 \t\t\t\tName="VCNMakeTool"
538 \t\t\t\tBuildCommandLine="%(buildcmd)s"
539 \t\t\t\tCleanCommandLine="%(cleancmd)s"
540 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
541 \t\t\t\tOutput="%(runfile)s"/>
546 <?xml version="1.0" encoding="%(encoding)s"?>
548 \tProjectType="Visual C++"
549 \tVersion="%(versionstr)s"
552 \tRootNamespace="%(name)s"
553 \tKeyword="MakeFileProj">
556 V8DSPConfiguration = """\
558 \t\t\tName="%(variant)s|%(platform)s"
559 \t\t\tConfigurationType="0"
561 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
564 \t\t\t\tName="VCNMakeTool"
565 \t\t\t\tBuildCommandLine="%(buildcmd)s"
566 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
567 \t\t\t\tCleanCommandLine="%(cleancmd)s"
568 \t\t\t\tOutput="%(runfile)s"
569 \t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
570 \t\t\t\tIncludeSearchPath="%(includepath)s"
571 \t\t\t\tForcedIncludes=""
572 \t\t\t\tAssemblySearchPath=""
573 \t\t\t\tForcedUsingAssemblies=""
574 \t\t\t\tCompileAsManaged=""
578 class _GenerateV7DSP(_DSPGenerator):
579 """Generates a Project file for MSVS .NET"""
581 def __init__(self, dspfile, source, env):
582 _DSPGenerator.__init__(self, dspfile, source, env)
583 self.version = env['MSVS_VERSION']
584 self.version_num, self.suite = msvs_parse_version(self.version)
585 if self.version_num >= 8.0:
586 self.versionstr = '8.00'
587 self.dspheader = V8DSPHeader
588 self.dspconfiguration = V8DSPConfiguration
590 if self.version_num >= 7.1:
591 self.versionstr = '7.10'
593 self.versionstr = '7.00'
594 self.dspheader = V7DSPHeader
595 self.dspconfiguration = V7DSPConfiguration
598 def PrintHeader(self):
600 versionstr = self.versionstr
602 encoding = self.env.subst('$MSVSENCODING')
603 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
604 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
605 scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
606 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
607 project_guid = env.get('MSVS_PROJECT_GUID', '')
608 if self.version_num >= 8.0 and not project_guid:
609 project_guid = _generateGUID(self.dspfile, '')
610 if scc_provider != '':
611 scc_attrs = ('\tProjectGUID="%s"\n'
612 '\tSccProjectName="%s"\n'
613 '\tSccAuxPath="%s"\n'
614 '\tSccLocalPath="%s"\n'
615 '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
617 scc_attrs = ('\tProjectGUID="%s"\n'
618 '\tSccProjectName="%s"\n'
619 '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
621 self.file.write(self.dspheader % locals())
623 self.file.write('\t<Platforms>\n')
624 for platform in self.platforms:
627 '\t\t\tName="%s"/>\n' % platform)
628 self.file.write('\t</Platforms>\n')
630 if self.version_num >= 8.0:
631 self.file.write('\t<ToolFiles>\n'
634 def PrintProject(self):
635 self.file.write('\t<Configurations>\n')
637 confkeys = sorted(self.configs.keys())
638 for kind in confkeys:
639 variant = self.configs[kind].variant
640 platform = self.configs[kind].platform
641 outdir = self.configs[kind].outdir
642 buildtarget = self.configs[kind].buildtarget
643 runfile = self.configs[kind].runfile
644 cmdargs = self.configs[kind].cmdargs
646 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
647 if not env_has_buildtarget:
648 self.env['MSVSBUILDTARGET'] = buildtarget
650 starting = 'echo Starting SCons && '
652 cmdargs = ' ' + cmdargs
655 buildcmd = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
656 rebuildcmd = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
657 cleancmd = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
659 preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
660 includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
662 if not env_has_buildtarget:
663 del self.env['MSVSBUILDTARGET']
665 self.file.write(self.dspconfiguration % locals())
667 self.file.write('\t</Configurations>\n')
669 if self.version_num >= 7.1:
670 self.file.write('\t<References>\n'
673 self.PrintSourceFiles()
675 self.file.write('</VisualStudioProject>\n')
678 # now we pickle some data and add it to the file -- MSDEV will ignore it.
679 pdata = pickle.dumps(self.configs,1)
680 pdata = base64.encodestring(pdata)
681 self.file.write('<!-- SCons Data:\n' + pdata + '\n')
682 pdata = pickle.dumps(self.sources,1)
683 pdata = base64.encodestring(pdata)
684 self.file.write(pdata + '-->\n')
686 def printSources(self, hierarchy, commonprefix):
687 sorteditems = sorted(hierarchy.items(), key=lambda a: a[0].lower())
689 # First folders, then files
690 for key, value in sorteditems:
691 if SCons.Util.is_Dict(value):
692 self.file.write('\t\t\t<Filter\n'
693 '\t\t\t\tName="%s"\n'
694 '\t\t\t\tFilter="">\n' % (key))
695 self.printSources(value, commonprefix)
696 self.file.write('\t\t\t</Filter>\n')
698 for key, value in sorteditems:
699 if SCons.Util.is_String(value):
702 file = os.path.join(commonprefix, value)
703 file = os.path.normpath(file)
704 self.file.write('\t\t\t<File\n'
705 '\t\t\t\tRelativePath="%s">\n'
706 '\t\t\t</File>\n' % (file))
708 def PrintSourceFiles(self):
709 categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
710 'Header Files': 'h;hpp;hxx;hm;inl',
711 'Local Headers': 'h;hpp;hxx;hm;inl',
712 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
715 self.file.write('\t<Files>\n')
717 cats = sorted([k for k in categories.keys() if self.sources[k]],
718 key=lambda a: a.lower())
721 self.file.write('\t\t<Filter\n'
723 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
725 sources = self.sources[kind]
727 # First remove any common prefix
730 s = list(map(os.path.normpath, sources))
731 # take the dirname because the prefix may include parts
732 # of the filenames (e.g. if you have 'dir\abcd' and
733 # 'dir\acde' then the cp will be 'dir\a' )
734 cp = os.path.dirname( os.path.commonprefix(s) )
735 if cp and s[0][len(cp)] == os.sep:
736 # +1 because the filename starts after the separator
737 sources = [s[len(cp)+1:] for s in sources]
739 elif len(sources) == 1:
740 commonprefix = os.path.dirname( sources[0] )
741 sources[0] = os.path.basename( sources[0] )
743 hierarchy = makeHierarchy(sources)
744 self.printSources(hierarchy, commonprefix=commonprefix)
747 self.file.write('\t\t</Filter>\n')
749 # add the SConscript file outside of the groups
750 self.file.write('\t\t<File\n'
751 '\t\t\tRelativePath="%s">\n'
752 '\t\t</File>\n' % str(self.sconscript))
754 self.file.write('\t</Files>\n'
760 dspfile = open(self.dspabs,'r')
762 return # doesn't exist yet, so can't add anything to configs.
764 line = dspfile.readline()
766 if line.find('<!-- SCons Data:') > -1:
768 line = dspfile.readline()
770 line = dspfile.readline()
772 while line and line != '\n':
773 line = dspfile.readline()
776 # OK, we've found our little pickled cache of data.
778 datas = base64.decodestring(datas)
779 data = pickle.loads(datas)
780 except KeyboardInterrupt:
783 return # unable to unpickle any data for some reason
785 self.configs.update(data)
788 line = dspfile.readline()
790 while line and line != '\n':
791 line = dspfile.readline()
794 # OK, we've found our little pickled cache of data.
796 datas = base64.decodestring(datas)
797 data = pickle.loads(datas)
798 except KeyboardInterrupt:
801 return # unable to unpickle any data for some reason
803 self.sources.update(data)
807 self.file = open(self.dspabs,'w')
808 except IOError, detail:
809 raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
815 class _DSWGenerator(object):
816 """ Base class for DSW generators """
817 def __init__(self, dswfile, source, env):
818 self.dswfile = os.path.normpath(str(dswfile))
821 if 'projects' not in env:
822 raise SCons.Errors.UserError("You must specify a 'projects' argument to create an MSVSSolution.")
823 projects = env['projects']
824 if not SCons.Util.is_List(projects):
825 raise SCons.Errors.InternalError("The 'projects' argument must be a list of nodes.")
826 projects = SCons.Util.flatten(projects)
827 if len(projects) < 1:
828 raise SCons.Errors.UserError("You must specify at least one project to create an MSVSSolution.")
829 self.dspfiles = list(map(str, projects))
831 if 'name' in self.env:
832 self.name = self.env['name']
834 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
835 self.name = self.env.subst(self.name)
840 class _GenerateV7DSW(_DSWGenerator):
841 """Generates a Solution file for MSVS .NET"""
842 def __init__(self, dswfile, source, env):
843 _DSWGenerator.__init__(self, dswfile, source, env)
846 self.version = self.env['MSVS_VERSION']
847 self.version_num, self.suite = msvs_parse_version(self.version)
848 self.versionstr = '7.00'
849 if self.version_num >= 8.0:
850 self.versionstr = '9.00'
851 elif self.version_num >= 7.1:
852 self.versionstr = '8.00'
853 if self.version_num >= 8.0:
854 self.versionstr = '9.00'
856 if 'slnguid' in env and env['slnguid']:
857 self.slnguid = env['slnguid']
859 self.slnguid = _generateGUID(dswfile, self.name)
864 if 'nokeep' in env and env['variant'] != 0:
867 if self.nokeep == 0 and os.path.exists(self.dswfile):
870 def AddConfig(self, variant, dswfile=dswfile):
873 match = re.match('(.*)\|(.*)', variant)
875 config.variant = match.group(1)
876 config.platform = match.group(2)
878 config.variant = variant
879 config.platform = 'Win32'
881 self.configs[variant] = config
882 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
884 if 'variant' not in env:
885 raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
886 "'Release') to create an MSVS Solution File.")
887 elif SCons.Util.is_String(env['variant']):
888 AddConfig(self, env['variant'])
889 elif SCons.Util.is_List(env['variant']):
890 for variant in env['variant']:
891 AddConfig(self, variant)
894 for key in self.configs.keys():
895 platform = self.configs[key].platform
896 if not platform in self.platforms:
897 self.platforms.append(platform)
901 dswfile = open(self.dswfile,'r')
903 return # doesn't exist yet, so can't add anything to configs.
905 line = dswfile.readline()
907 if line[:9] == "EndGlobal":
909 line = dswfile.readline()
911 line = dswfile.readline()
914 line = dswfile.readline()
917 # OK, we've found our little pickled cache of data.
919 datas = base64.decodestring(datas)
920 data = pickle.loads(datas)
921 except KeyboardInterrupt:
924 return # unable to unpickle any data for some reason
926 self.configs.update(data)
928 def PrintSolution(self):
929 """Writes a solution file"""
930 self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
931 if self.version_num >= 8.0:
932 self.file.write('# Visual Studio 2005\n')
933 for p in self.dspfiles:
934 name = os.path.basename(p)
935 base, suffix = SCons.Util.splitext(name)
936 if suffix == '.vcproj':
938 guid = _generateGUID(p, '')
939 self.file.write('Project("%s") = "%s", "%s", "%s"\n'
940 % ( external_makefile_guid, name, p, guid ) )
941 if self.version_num >= 7.1 and self.version_num < 8.0:
942 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
943 '\tEndProjectSection\n')
944 self.file.write('EndProject\n')
946 self.file.write('Global\n')
949 if 'MSVS_SCC_PROVIDER' in env:
950 dspfile_base = os.path.basename(self.dspfile)
951 slnguid = self.slnguid
952 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
953 scc_provider = scc_provider.replace(' ', r'\u0020')
954 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
955 # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
956 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
957 scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
958 # project_guid = env.get('MSVS_PROJECT_GUID', '')
960 self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
961 '\t\tSccNumberOfProjects = 2\n'
962 '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
963 '\t\tSccLocalPath0 = %(scc_local_path)s\n'
964 '\t\tCanCheckoutShared = true\n'
965 '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
966 '\t\tSccProjectName1 = %(scc_project_name)s\n'
967 '\t\tSccLocalPath1 = %(scc_local_path)s\n'
968 '\t\tSccProvider1 = %(scc_provider)s\n'
969 '\t\tCanCheckoutShared = true\n'
970 '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
971 '\t\tSolutionUniqueID = %(slnguid)s\n'
972 '\tEndGlobalSection\n' % locals())
974 if self.version_num >= 8.0:
975 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
977 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
979 confkeys = sorted(self.configs.keys())
981 for name in confkeys:
982 variant = self.configs[name].variant
983 platform = self.configs[name].platform
984 if self.version_num >= 8.0:
985 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
987 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
989 self.file.write('\tEndGlobalSection\n')
990 if self.version_num < 7.1:
991 self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
992 '\tEndGlobalSection\n')
993 if self.version_num >= 8.0:
994 self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
996 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
998 for name in confkeys:
999 variant = self.configs[name].variant
1000 platform = self.configs[name].platform
1001 if self.version_num >= 8.0:
1002 for p in self.dspfiles:
1003 guid = _generateGUID(p, '')
1004 self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1005 '\t\t%s.%s|%s.Build.0 = %s|%s\n' % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1007 for p in self.dspfiles:
1008 guid = _generateGUID(p, '')
1009 self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1010 '\t\t%s.%s.Build.0 = %s|%s\n' %(guid,variant,variant,platform,guid,variant,variant,platform))
1012 self.file.write('\tEndGlobalSection\n')
1014 if self.version_num >= 8.0:
1015 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1016 '\t\tHideSolutionNode = FALSE\n'
1017 '\tEndGlobalSection\n')
1019 self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1020 '\tEndGlobalSection\n'
1021 '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1022 '\tEndGlobalSection\n')
1023 self.file.write('EndGlobal\n')
1024 if self.nokeep == 0:
1025 pdata = pickle.dumps(self.configs,1)
1026 pdata = base64.encodestring(pdata)
1027 self.file.write(pdata + '\n')
1031 self.file = open(self.dswfile,'w')
1032 except IOError, detail:
1033 raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1035 self.PrintSolution()
1039 Microsoft Developer Studio Workspace File, Format Version 6.00
1040 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1042 ###############################################################################
1044 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1054 ###############################################################################
1066 ###############################################################################
1069 class _GenerateV6DSW(_DSWGenerator):
1070 """Generates a Workspace file for MSVS 6.0"""
1072 def PrintWorkspace(self):
1073 """ writes a DSW file """
1075 dspfile = self.dspfiles[0]
1076 self.file.write(V6DSWHeader % locals())
1080 self.file = open(self.dswfile,'w')
1081 except IOError, detail:
1082 raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1084 self.PrintWorkspace()
1088 def GenerateDSP(dspfile, source, env):
1089 """Generates a Project file based on the version of MSVS that is being used"""
1092 if 'MSVS_VERSION' in env:
1093 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1094 if version_num >= 7.0:
1095 g = _GenerateV7DSP(dspfile, source, env)
1098 g = _GenerateV6DSP(dspfile, source, env)
1101 def GenerateDSW(dswfile, source, env):
1102 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1105 if 'MSVS_VERSION' in env:
1106 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1107 if version_num >= 7.0:
1108 g = _GenerateV7DSW(dswfile, source, env)
1111 g = _GenerateV6DSW(dswfile, source, env)
1115 ##############################################################################
1116 # Above here are the classes and functions for generation of
1117 # DSP/DSW/SLN/VCPROJ files.
1118 ##############################################################################
1120 def GetMSVSProjectSuffix(target, source, env, for_signature):
1121 return env['MSVS']['PROJECTSUFFIX']
1123 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1124 return env['MSVS']['SOLUTIONSUFFIX']
1126 def GenerateProject(target, source, env):
1127 # generate the dsp file, according to the version of MSVS.
1128 builddspfile = target[0]
1129 dspfile = builddspfile.srcnode()
1131 # this detects whether or not we're using a VariantDir
1132 if not dspfile is builddspfile:
1134 bdsp = open(str(builddspfile), "w+")
1135 except IOError, detail:
1136 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1139 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1141 GenerateDSP(dspfile, source, env)
1143 if env.get('auto_build_solution', 1):
1144 builddswfile = target[1]
1145 dswfile = builddswfile.srcnode()
1147 if not dswfile is builddswfile:
1150 bdsw = open(str(builddswfile), "w+")
1151 except IOError, detail:
1152 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1155 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1157 GenerateDSW(dswfile, source, env)
1159 def GenerateSolution(target, source, env):
1160 GenerateDSW(target[0], source, env)
1162 def projectEmitter(target, source, env):
1163 """Sets up the DSP dependencies."""
1165 # todo: Not sure what sets source to what user has passed as target,
1166 # but this is what happens. When that is fixed, we also won't have
1167 # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1168 if source[0] == target[0]:
1171 # make sure the suffix is correct for the version of MSVS we're running.
1172 (base, suff) = SCons.Util.splitext(str(target[0]))
1173 suff = env.subst('$MSVSPROJECTSUFFIX')
1174 target[0] = base + suff
1177 source = 'prj_inputs:'
1178 source = source + env.subst('$MSVSSCONSCOM', 1)
1179 source = source + env.subst('$MSVSENCODING', 1)
1181 if 'buildtarget' in env and env['buildtarget'] != None:
1182 if SCons.Util.is_String(env['buildtarget']):
1183 source = source + ' "%s"' % env['buildtarget']
1184 elif SCons.Util.is_List(env['buildtarget']):
1185 for bt in env['buildtarget']:
1186 if SCons.Util.is_String(bt):
1187 source = source + ' "%s"' % bt
1189 try: source = source + ' "%s"' % bt.get_abspath()
1190 except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1192 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1193 except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1195 if 'outdir' in env and env['outdir'] != None:
1196 if SCons.Util.is_String(env['outdir']):
1197 source = source + ' "%s"' % env['outdir']
1198 elif SCons.Util.is_List(env['outdir']):
1199 for s in env['outdir']:
1200 if SCons.Util.is_String(s):
1201 source = source + ' "%s"' % s
1203 try: source = source + ' "%s"' % s.get_abspath()
1204 except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1206 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1207 except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1210 if SCons.Util.is_String(env['name']):
1211 source = source + ' "%s"' % env['name']
1213 raise SCons.Errors.InternalError("name must be a string")
1215 if 'variant' in env:
1216 if SCons.Util.is_String(env['variant']):
1217 source = source + ' "%s"' % env['variant']
1218 elif SCons.Util.is_List(env['variant']):
1219 for variant in env['variant']:
1220 if SCons.Util.is_String(variant):
1221 source = source + ' "%s"' % variant
1223 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1225 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1227 raise SCons.Errors.InternalError("variant must be specified")
1229 for s in _DSPGenerator.srcargs:
1231 if SCons.Util.is_String(env[s]):
1232 source = source + ' "%s' % env[s]
1233 elif SCons.Util.is_List(env[s]):
1235 if SCons.Util.is_String(t):
1236 source = source + ' "%s"' % t
1238 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1240 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1242 source = source + ' "%s"' % str(target[0])
1243 source = [SCons.Node.Python.Value(source)]
1245 targetlist = [target[0]]
1248 if env.get('auto_build_solution', 1):
1249 env['projects'] = targetlist
1250 t, s = solutionEmitter(target, target, env)
1251 targetlist = targetlist + t
1253 return (targetlist, sourcelist)
1255 def solutionEmitter(target, source, env):
1256 """Sets up the DSW dependencies."""
1258 # todo: Not sure what sets source to what user has passed as target,
1259 # but this is what happens. When that is fixed, we also won't have
1260 # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1261 if source[0] == target[0]:
1264 # make sure the suffix is correct for the version of MSVS we're running.
1265 (base, suff) = SCons.Util.splitext(str(target[0]))
1266 suff = env.subst('$MSVSSOLUTIONSUFFIX')
1267 target[0] = base + suff
1270 source = 'sln_inputs:'
1273 if SCons.Util.is_String(env['name']):
1274 source = source + ' "%s"' % env['name']
1276 raise SCons.Errors.InternalError("name must be a string")
1278 if 'variant' in env:
1279 if SCons.Util.is_String(env['variant']):
1280 source = source + ' "%s"' % env['variant']
1281 elif SCons.Util.is_List(env['variant']):
1282 for variant in env['variant']:
1283 if SCons.Util.is_String(variant):
1284 source = source + ' "%s"' % variant
1286 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1288 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1290 raise SCons.Errors.InternalError("variant must be specified")
1292 if 'slnguid' in env:
1293 if SCons.Util.is_String(env['slnguid']):
1294 source = source + ' "%s"' % env['slnguid']
1296 raise SCons.Errors.InternalError("slnguid must be a string")
1298 if 'projects' in env:
1299 if SCons.Util.is_String(env['projects']):
1300 source = source + ' "%s"' % env['projects']
1301 elif SCons.Util.is_List(env['projects']):
1302 for t in env['projects']:
1303 if SCons.Util.is_String(t):
1304 source = source + ' "%s"' % t
1306 source = source + ' "%s"' % str(target[0])
1307 source = [SCons.Node.Python.Value(source)]
1309 return ([target[0]], source)
1311 projectAction = SCons.Action.Action(GenerateProject, None)
1313 solutionAction = SCons.Action.Action(GenerateSolution, None)
1315 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1316 suffix = '$MSVSPROJECTSUFFIX',
1317 emitter = projectEmitter)
1319 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1320 suffix = '$MSVSSOLUTIONSUFFIX',
1321 emitter = solutionEmitter)
1323 default_MSVS_SConscript = None
1326 """Add Builders and construction variables for Microsoft Visual
1327 Studio project files to an Environment."""
1329 env['BUILDERS']['MSVSProject']
1331 env['BUILDERS']['MSVSProject'] = projectBuilder
1334 env['BUILDERS']['MSVSSolution']
1336 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1338 env['MSVSPROJECTCOM'] = projectAction
1339 env['MSVSSOLUTIONCOM'] = solutionAction
1341 if SCons.Script.call_stack:
1342 # XXX Need to find a way to abstract this; the build engine
1343 # shouldn't depend on anything in SCons.Script.
1344 env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1346 global default_MSVS_SConscript
1347 if default_MSVS_SConscript is None:
1348 default_MSVS_SConscript = env.File('SConstruct')
1349 env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1351 env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1352 env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1353 env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1354 env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1355 env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1356 env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1357 env['MSVSENCODING'] = 'Windows-1252'
1359 # Set-up ms tools paths for default version
1360 msvc_setup_env_once(env)
1362 if 'MSVS_VERSION' in env:
1363 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1365 (version_num, suite) = (7.0, None) # guess at a default
1366 if 'MSVS' not in env:
1368 if (version_num < 7.0):
1369 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1370 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1372 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1373 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1375 env['GET_MSVSPROJECTSUFFIX'] = GetMSVSProjectSuffix
1376 env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix
1377 env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}'
1378 env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}'
1379 env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1382 return msvc_exists()
1386 # indent-tabs-mode:nil
1388 # vim: set expandtab tabstop=4 shiftwidth=4: