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()
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 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
47 import SCons.Platform.win32
48 import SCons.Script.SConscript
52 from MSCommon import msvc_exists, msvc_setup_env_once
53 from SCons.Defaults import processDefines
55 ##############################################################################
56 # Below here are the classes and functions for generation of
57 # DSP/DSW/SLN/VCPROJ files.
58 ##############################################################################
61 s = s.replace("&", "&") # do this first
62 s = s.replace("'", "'")
63 s = s.replace('"', """)
66 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
68 def _generateGUID(slnfile, name):
69 """This generates a dummy GUID for the sln file to use. It is
70 based on the MD5 signatures of the sln filename plus the name of
71 the project. It basically just needs to be unique, and not
72 change with each invocation."""
74 # Normalize the slnfile path to a Windows path (\ separators) so
75 # the generated file has a consistent GUID even if we generate
76 # it on a non-Windows platform.
77 m.update(ntpath.normpath(str(slnfile)) + str(name))
78 solution = m.hexdigest().upper()
79 # convert most of the signature to GUID form (discard the rest)
80 solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
83 version_re = re.compile(r'(\d+\.\d+)(.*)')
85 def msvs_parse_version(s):
87 Split a Visual Studio version, which may in fact be something like
88 '7.0Exp', into is version number (returned as a float) and trailing
91 num, suite = version_re.match(s).groups()
92 return float(num), suite
94 # This is how we re-invoke SCons from inside MSVS Project files.
95 # The problem is that we might have been invoked as either scons.bat
96 # or scons.py. If we were invoked directly as scons.py, then we could
97 # use sys.argv[0] to find the SCons "executable," but that doesn't work
98 # if we were invoked as scons.bat, which uses "python -c" to execute
99 # things and ends up with "-c" as sys.argv[0]. Consequently, we have
100 # the MSVS Project file invoke SCons the same way that scons.bat does,
101 # which works regardless of how we were invoked.
102 def getExecScriptMain(env, xml=None):
103 scons_home = env.get('SCONS_HOME')
104 if not scons_home and 'SCONS_LIB_DIR' in os.environ:
105 scons_home = os.environ['SCONS_LIB_DIR']
107 exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
109 version = SCons.__version__
110 exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%(version)s'), join(sys.prefix, 'scons-%(version)s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % locals()
112 exec_script_main = xmlify(exec_script_main)
113 return exec_script_main
115 # The string for the Python executable we tell the Project file to use
116 # is either sys.executable or, if an external PYTHON_ROOT environment
117 # variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
118 # pluck the actual executable name from sys.executable).
120 python_root = os.environ['PYTHON_ROOT']
122 python_executable = sys.executable
124 python_executable = os.path.join('$$(PYTHON_ROOT)',
125 os.path.split(sys.executable)[1])
130 def splitFully(path):
131 dir, base = os.path.split(path)
132 if dir and dir != '' and dir != path:
133 return splitFully(dir)+[base]
138 def makeHierarchy(sources):
139 '''Break a list of files into a hierarchy; for each value, if it is a string,
140 then it is a file. If it is a dictionary, it is a folder. The string is
141 the original path of the file.'''
145 path = splitFully(file)
148 for part in path[:-1]:
152 dict[path[-1]] = file
154 # print 'Warning: failed to decompose path for '+str(file)
158 """ Base class for DSP generators """
167 def __init__(self, dspfile, source, env):
168 self.dspfile = str(dspfile)
170 get_abspath = dspfile.get_abspath
171 except AttributeError:
172 self.dspabs = os.path.abspath(dspfile)
174 self.dspabs = get_abspath()
176 if 'variant' not in env:
177 raise SCons.Errors.InternalError, \
178 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
179 "'Release') to create an MSVSProject."
180 elif SCons.Util.is_String(env['variant']):
181 variants = [env['variant']]
182 elif SCons.Util.is_List(env['variant']):
183 variants = env['variant']
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, \
192 "Sizes of 'buildtarget' and 'variant' lists must be the same."
194 for bt in env['buildtarget']:
195 if SCons.Util.is_String(bt):
196 buildtarget.append(bt)
198 buildtarget.append(bt.get_abspath())
200 buildtarget = [env['buildtarget'].get_abspath()]
201 if len(buildtarget) == 1:
205 buildtarget.append(bt)
207 if 'outdir' not in env or env['outdir'] == None:
209 elif SCons.Util.is_String(env['outdir']):
210 outdir = [env['outdir']]
211 elif SCons.Util.is_List(env['outdir']):
212 if len(env['outdir']) != len(variants):
213 raise SCons.Errors.InternalError, \
214 "Sizes of 'outdir' and 'variant' lists must be the same."
216 for s in env['outdir']:
217 if SCons.Util.is_String(s):
220 outdir.append(s.get_abspath())
222 outdir = [env['outdir'].get_abspath()]
229 if 'runfile' not in env or env['runfile'] == None:
230 runfile = buildtarget[-1:]
231 elif SCons.Util.is_String(env['runfile']):
232 runfile = [env['runfile']]
233 elif SCons.Util.is_List(env['runfile']):
234 if len(env['runfile']) != len(variants):
235 raise SCons.Errors.InternalError, \
236 "Sizes of 'runfile' and 'variant' lists must be the same."
238 for s in env['runfile']:
239 if SCons.Util.is_String(s):
242 runfile.append(s.get_abspath())
244 runfile = [env['runfile'].get_abspath()]
245 if len(runfile) == 1:
251 self.sconscript = env['MSVSSCONSCRIPT']
253 cmdargs = env.get('cmdargs', '')
257 if 'name' in self.env:
258 self.name = self.env['name']
260 self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
261 self.name = self.env.subst(self.name)
271 for n in sourcenames:
277 if 'nokeep' in env and env['variant'] != 0:
280 if self.nokeep == 0 and os.path.exists(self.dspabs):
283 for t in zip(sourcenames,self.srcargs):
285 if SCons.Util.is_List(self.env[t[1]]):
286 for i in self.env[t[1]]:
287 if not i in self.sources[t[0]]:
288 self.sources[t[0]].append(i)
290 if not self.env[t[1]] in self.sources[t[0]]:
291 self.sources[t[0]].append(self.env[t[1]])
293 for n in sourcenames:
295 #self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
296 self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
298 def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
300 config.buildtarget = buildtarget
301 config.outdir = outdir
302 config.cmdargs = cmdargs
303 config.runfile = runfile
305 match = re.match('(.*)\|(.*)', variant)
307 config.variant = match.group(1)
308 config.platform = match.group(2)
310 config.variant = variant
311 config.platform = 'Win32'
313 self.configs[variant] = config
314 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
316 for i in range(len(variants)):
317 AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
320 for key in self.configs.keys():
321 platform = self.configs[key].platform
322 if not platform in self.platforms:
323 self.platforms.append(platform)
329 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
330 # Microsoft Developer Studio Generated Build File, Format Version 6.00
333 # TARGTYPE "Win32 (x86) External Target" 0x0106
335 CFG=%(name)s - Win32 %(confkey)s
336 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
337 !MESSAGE use the Export Makefile command and run
339 !MESSAGE NMAKE /f "%(name)s.mak".
341 !MESSAGE You can specify a configuration when running NMAKE
342 !MESSAGE by defining the macro CFG on the command line. For example:
344 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
346 !MESSAGE Possible choices for configuration are:
350 class _GenerateV6DSP(_DSPGenerator):
351 """Generates a Project file for MSVS 6.0"""
353 def PrintHeader(self):
354 # pick a default config
355 confkeys = self.configs.keys()
359 confkey = confkeys[0]
361 self.file.write(V6DSPHeader % locals())
363 for kind in confkeys:
364 self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
366 self.file.write('!MESSAGE \n\n')
368 def PrintProject(self):
370 self.file.write('# Begin Project\n'
371 '# PROP AllowPerConfigDependencies 0\n'
372 '# PROP Scc_ProjName ""\n'
373 '# PROP Scc_LocalPath ""\n\n')
376 confkeys = self.configs.keys()
378 for kind in confkeys:
379 outdir = self.configs[kind].outdir
380 buildtarget = self.configs[kind].buildtarget
382 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
385 self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
387 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
388 if not env_has_buildtarget:
389 self.env['MSVSBUILDTARGET'] = buildtarget
391 # have to write this twice, once with the BASE settings, and once without
392 for base in ("BASE ",""):
393 self.file.write('# PROP %sUse_MFC 0\n'
394 '# PROP %sUse_Debug_Libraries ' % (base, base))
396 #if kind.lower().find('debug') < 0:
397 if kind.lower().find('debug') < 0:
398 self.file.write('0\n')
400 self.file.write('1\n')
401 self.file.write('# PROP %sOutput_Dir "%s"\n'
402 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
403 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
404 self.file.write('# PROP %sCmd_Line "%s"\n'
405 '# PROP %sRebuild_Opt "-c && %s"\n'
406 '# PROP %sTarget_File "%s"\n'
407 '# PROP %sBsc_Name ""\n'
408 '# PROP %sTarget_Dir ""\n'\
409 %(base,cmd,base,cmd,base,buildtarget,base,base))
411 if not env_has_buildtarget:
412 del self.env['MSVSBUILDTARGET']
414 self.file.write('\n!ENDIF\n\n'
415 '# Begin Target\n\n')
416 for kind in confkeys:
417 self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
418 self.file.write('\n')
420 for kind in confkeys:
422 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
425 self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
426 self.file.write('!ENDIF \n\n')
427 self.PrintSourceFiles()
428 self.file.write('# End Target\n'
432 # now we pickle some data and add it to the file -- MSDEV will ignore it.
433 pdata = pickle.dumps(self.configs,1)
434 pdata = base64.encodestring(pdata)
435 self.file.write(pdata + '\n')
436 pdata = pickle.dumps(self.sources,1)
437 pdata = base64.encodestring(pdata)
438 self.file.write(pdata + '\n')
440 def PrintSourceFiles(self):
441 categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
442 'Header Files': 'h|hpp|hxx|hm|inl',
443 'Local Headers': 'h|hpp|hxx|hm|inl',
444 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
447 cats = categories.keys()
449 #cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
450 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
452 if not self.sources[kind]:
453 continue # skip empty groups
455 self.file.write('# Begin Group "' + kind + '"\n\n')
457 #typelist = categories[kind].replace('|', ';')
458 typelist = categories[kind].replace('|', ';')
459 self.file.write('# PROP Default_Filter "' + typelist + '"\n')
461 for file in self.sources[kind]:
462 file = os.path.normpath(file)
463 self.file.write('# Begin Source File\n\n'
464 'SOURCE="' + file + '"\n'
465 '# End Source File\n')
466 self.file.write('# End Group\n')
468 # add the SConscript file outside of the groups
469 self.file.write('# Begin Source File\n\n'
470 'SOURCE="' + str(self.sconscript) + '"\n'
471 '# End Source File\n')
475 dspfile = open(self.dspabs,'r')
477 return # doesn't exist yet, so can't add anything to configs.
479 line = dspfile.readline()
482 #if line.find("# End Project") > -1:
483 if line.find("# End Project") > -1:
485 line = dspfile.readline()
487 line = dspfile.readline()
489 while line and line != '\n':
490 line = dspfile.readline()
493 # OK, we've found our little pickled cache of data.
495 datas = base64.decodestring(datas)
496 data = pickle.loads(datas)
497 except KeyboardInterrupt:
500 return # unable to unpickle any data for some reason
502 self.configs.update(data)
505 line = dspfile.readline()
507 while line and line != '\n':
508 line = dspfile.readline()
511 # OK, we've found our little pickled cache of data.
512 # it has a "# " in front of it, so we strip that.
514 datas = base64.decodestring(datas)
515 data = pickle.loads(datas)
516 except KeyboardInterrupt:
519 return # unable to unpickle any data for some reason
521 self.sources.update(data)
525 self.file = open(self.dspabs,'w')
526 except IOError, detail:
527 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
534 <?xml version="1.0" encoding = "%(encoding)s"?>
536 \tProjectType="Visual C++"
537 \tVersion="%(versionstr)s"
540 \tKeyword="MakeFileProj">
543 V7DSPConfiguration = """\
545 \t\t\tName="%(variant)s|%(platform)s"
546 \t\t\tOutputDirectory="%(outdir)s"
547 \t\t\tIntermediateDirectory="%(outdir)s"
548 \t\t\tConfigurationType="0"
550 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
552 \t\t\t\tName="VCNMakeTool"
553 \t\t\t\tBuildCommandLine="%(buildcmd)s"
554 \t\t\t\tCleanCommandLine="%(cleancmd)s"
555 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
556 \t\t\t\tOutput="%(runfile)s"/>
561 <?xml version="1.0" encoding="%(encoding)s"?>
563 \tProjectType="Visual C++"
564 \tVersion="%(versionstr)s"
567 \tRootNamespace="%(name)s"
568 \tKeyword="MakeFileProj">
571 V8DSPConfiguration = """\
573 \t\t\tName="%(variant)s|%(platform)s"
574 \t\t\tConfigurationType="0"
576 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
579 \t\t\t\tName="VCNMakeTool"
580 \t\t\t\tBuildCommandLine="%(buildcmd)s"
581 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
582 \t\t\t\tCleanCommandLine="%(cleancmd)s"
583 \t\t\t\tOutput="%(runfile)s"
584 \t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
585 \t\t\t\tIncludeSearchPath="%(includepath)s"
586 \t\t\t\tForcedIncludes=""
587 \t\t\t\tAssemblySearchPath=""
588 \t\t\t\tForcedUsingAssemblies=""
589 \t\t\t\tCompileAsManaged=""
593 class _GenerateV7DSP(_DSPGenerator):
594 """Generates a Project file for MSVS .NET"""
596 def __init__(self, dspfile, source, env):
597 _DSPGenerator.__init__(self, dspfile, source, env)
598 self.version = env['MSVS_VERSION']
599 self.version_num, self.suite = msvs_parse_version(self.version)
600 if self.version_num >= 8.0:
601 self.versionstr = '8.00'
602 self.dspheader = V8DSPHeader
603 self.dspconfiguration = V8DSPConfiguration
605 if self.version_num >= 7.1:
606 self.versionstr = '7.10'
608 self.versionstr = '7.00'
609 self.dspheader = V7DSPHeader
610 self.dspconfiguration = V7DSPConfiguration
613 def PrintHeader(self):
615 versionstr = self.versionstr
617 encoding = self.env.subst('$MSVSENCODING')
618 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
619 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
620 scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
621 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
622 project_guid = env.get('MSVS_PROJECT_GUID', '')
623 if self.version_num >= 8.0 and not project_guid:
624 project_guid = _generateGUID(self.dspfile, '')
625 if scc_provider != '':
626 scc_attrs = ('\tProjectGUID="%s"\n'
627 '\tSccProjectName="%s"\n'
628 '\tSccAuxPath="%s"\n'
629 '\tSccLocalPath="%s"\n'
630 '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
632 scc_attrs = ('\tProjectGUID="%s"\n'
633 '\tSccProjectName="%s"\n'
634 '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
636 self.file.write(self.dspheader % locals())
638 self.file.write('\t<Platforms>\n')
639 for platform in self.platforms:
642 '\t\t\tName="%s"/>\n' % platform)
643 self.file.write('\t</Platforms>\n')
645 if self.version_num >= 8.0:
646 self.file.write('\t<ToolFiles>\n'
649 def PrintProject(self):
650 self.file.write('\t<Configurations>\n')
652 confkeys = self.configs.keys()
654 for kind in confkeys:
655 variant = self.configs[kind].variant
656 platform = self.configs[kind].platform
657 outdir = self.configs[kind].outdir
658 buildtarget = self.configs[kind].buildtarget
659 runfile = self.configs[kind].runfile
660 cmdargs = self.configs[kind].cmdargs
662 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
663 if not env_has_buildtarget:
664 self.env['MSVSBUILDTARGET'] = buildtarget
666 starting = 'echo Starting SCons && '
668 cmdargs = ' ' + cmdargs
671 buildcmd = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
672 rebuildcmd = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
673 cleancmd = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
676 #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', [])))
677 #includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
678 preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
679 includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
681 if not env_has_buildtarget:
682 del self.env['MSVSBUILDTARGET']
684 self.file.write(self.dspconfiguration % locals())
686 self.file.write('\t</Configurations>\n')
688 if self.version_num >= 7.1:
689 self.file.write('\t<References>\n'
692 self.PrintSourceFiles()
694 self.file.write('</VisualStudioProject>\n')
697 # now we pickle some data and add it to the file -- MSDEV will ignore it.
698 pdata = pickle.dumps(self.configs,1)
699 pdata = base64.encodestring(pdata)
700 self.file.write('<!-- SCons Data:\n' + pdata + '\n')
701 pdata = pickle.dumps(self.sources,1)
702 pdata = base64.encodestring(pdata)
703 self.file.write(pdata + '-->\n')
705 def printSources(self, hierarchy, commonprefix):
706 sorteditems = hierarchy.items()
708 #sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
709 sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
711 # First folders, then files
712 for key, value in sorteditems:
713 if SCons.Util.is_Dict(value):
714 self.file.write('\t\t\t<Filter\n'
715 '\t\t\t\tName="%s"\n'
716 '\t\t\t\tFilter="">\n' % (key))
717 self.printSources(value, commonprefix)
718 self.file.write('\t\t\t</Filter>\n')
720 for key, value in sorteditems:
721 if SCons.Util.is_String(value):
724 file = os.path.join(commonprefix, value)
725 file = os.path.normpath(file)
726 self.file.write('\t\t\t<File\n'
727 '\t\t\t\tRelativePath="%s">\n'
728 '\t\t\t</File>\n' % (file))
730 def PrintSourceFiles(self):
731 categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
732 'Header Files': 'h;hpp;hxx;hm;inl',
733 'Local Headers': 'h;hpp;hxx;hm;inl',
734 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
737 self.file.write('\t<Files>\n')
739 cats = categories.keys()
741 #cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
742 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
743 cats = [k for k in cats if self.sources[k]]
746 self.file.write('\t\t<Filter\n'
748 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
750 sources = self.sources[kind]
752 # First remove any common prefix
755 s = list(map(os.path.normpath, sources))
756 # take the dirname because the prefix may include parts
757 # of the filenames (e.g. if you have 'dir\abcd' and
758 # 'dir\acde' then the cp will be 'dir\a' )
759 cp = os.path.dirname( os.path.commonprefix(s) )
760 if cp and s[0][len(cp)] == os.sep:
761 # +1 because the filename starts after the separator
762 sources = [s[len(cp)+1:] for s in sources]
764 elif len(sources) == 1:
765 commonprefix = os.path.dirname( sources[0] )
766 sources[0] = os.path.basename( sources[0] )
768 hierarchy = makeHierarchy(sources)
769 self.printSources(hierarchy, commonprefix=commonprefix)
772 self.file.write('\t\t</Filter>\n')
774 # add the SConscript file outside of the groups
775 self.file.write('\t\t<File\n'
776 '\t\t\tRelativePath="%s">\n'
777 '\t\t</File>\n' % str(self.sconscript))
779 self.file.write('\t</Files>\n'
785 dspfile = open(self.dspabs,'r')
787 return # doesn't exist yet, so can't add anything to configs.
789 line = dspfile.readline()
792 #if line.find('<!-- SCons Data:') > -1:
793 if line.find('<!-- SCons Data:') > -1:
795 line = dspfile.readline()
797 line = dspfile.readline()
799 while line and line != '\n':
800 line = dspfile.readline()
803 # OK, we've found our little pickled cache of data.
805 datas = base64.decodestring(datas)
806 data = pickle.loads(datas)
807 except KeyboardInterrupt:
810 return # unable to unpickle any data for some reason
812 self.configs.update(data)
815 line = dspfile.readline()
817 while line and line != '\n':
818 line = dspfile.readline()
821 # OK, we've found our little pickled cache of data.
823 datas = base64.decodestring(datas)
824 data = pickle.loads(datas)
825 except KeyboardInterrupt:
828 return # unable to unpickle any data for some reason
830 self.sources.update(data)
834 self.file = open(self.dspabs,'w')
835 except IOError, detail:
836 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
843 """ Base class for DSW generators """
844 def __init__(self, dswfile, source, env):
845 self.dswfile = os.path.normpath(str(dswfile))
848 if 'projects' not in env:
849 raise SCons.Errors.UserError, \
850 "You must specify a 'projects' argument to create an MSVSSolution."
851 projects = env['projects']
852 if not SCons.Util.is_List(projects):
853 raise SCons.Errors.InternalError, \
854 "The 'projects' argument must be a list of nodes."
855 projects = SCons.Util.flatten(projects)
856 if len(projects) < 1:
857 raise SCons.Errors.UserError, \
858 "You must specify at least one project to create an MSVSSolution."
859 self.dspfiles = list(map(str, projects))
861 if 'name' in self.env:
862 self.name = self.env['name']
864 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
865 self.name = self.env.subst(self.name)
870 class _GenerateV7DSW(_DSWGenerator):
871 """Generates a Solution file for MSVS .NET"""
872 def __init__(self, dswfile, source, env):
873 _DSWGenerator.__init__(self, dswfile, source, env)
876 self.version = self.env['MSVS_VERSION']
877 self.version_num, self.suite = msvs_parse_version(self.version)
878 self.versionstr = '7.00'
879 if self.version_num >= 8.0:
880 self.versionstr = '9.00'
881 elif self.version_num >= 7.1:
882 self.versionstr = '8.00'
883 if self.version_num >= 8.0:
884 self.versionstr = '9.00'
886 if 'slnguid' in env and env['slnguid']:
887 self.slnguid = env['slnguid']
889 self.slnguid = _generateGUID(dswfile, self.name)
894 if 'nokeep' in env and env['variant'] != 0:
897 if self.nokeep == 0 and os.path.exists(self.dswfile):
900 def AddConfig(self, variant, dswfile=dswfile):
903 match = re.match('(.*)\|(.*)', variant)
905 config.variant = match.group(1)
906 config.platform = match.group(2)
908 config.variant = variant
909 config.platform = 'Win32'
911 self.configs[variant] = config
912 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
914 if 'variant' not in env:
915 raise SCons.Errors.InternalError, \
916 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
917 "'Release') to create an MSVS Solution File."
918 elif SCons.Util.is_String(env['variant']):
919 AddConfig(self, env['variant'])
920 elif SCons.Util.is_List(env['variant']):
921 for variant in env['variant']:
922 AddConfig(self, variant)
925 for key in self.configs.keys():
926 platform = self.configs[key].platform
927 if not platform in self.platforms:
928 self.platforms.append(platform)
932 dswfile = open(self.dswfile,'r')
934 return # doesn't exist yet, so can't add anything to configs.
936 line = dswfile.readline()
938 if line[:9] == "EndGlobal":
940 line = dswfile.readline()
942 line = dswfile.readline()
945 line = dswfile.readline()
948 # OK, we've found our little pickled cache of data.
950 datas = base64.decodestring(datas)
951 data = pickle.loads(datas)
952 except KeyboardInterrupt:
955 return # unable to unpickle any data for some reason
957 self.configs.update(data)
959 def PrintSolution(self):
960 """Writes a solution file"""
961 self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
962 if self.version_num >= 8.0:
963 self.file.write('# Visual Studio 2005\n')
964 for p in self.dspfiles:
965 name = os.path.basename(p)
966 base, suffix = SCons.Util.splitext(name)
967 if suffix == '.vcproj':
969 guid = _generateGUID(p, '')
970 self.file.write('Project("%s") = "%s", "%s", "%s"\n'
971 % ( external_makefile_guid, name, p, guid ) )
972 if self.version_num >= 7.1 and self.version_num < 8.0:
973 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
974 '\tEndProjectSection\n')
975 self.file.write('EndProject\n')
977 self.file.write('Global\n')
980 if 'MSVS_SCC_PROVIDER' in env:
981 dspfile_base = os.path.basename(self.dspfile)
982 slnguid = self.slnguid
983 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
984 scc_provider = scc_provider.replace(' ', r'\u0020')
985 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
986 # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
987 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
988 scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
989 # project_guid = env.get('MSVS_PROJECT_GUID', '')
991 self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
992 '\t\tSccNumberOfProjects = 2\n'
993 '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
994 '\t\tSccLocalPath0 = %(scc_local_path)s\n'
995 '\t\tCanCheckoutShared = true\n'
996 '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
997 '\t\tSccProjectName1 = %(scc_project_name)s\n'
998 '\t\tSccLocalPath1 = %(scc_local_path)s\n'
999 '\t\tSccProvider1 = %(scc_provider)s\n'
1000 '\t\tCanCheckoutShared = true\n'
1001 '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
1002 '\t\tSolutionUniqueID = %(slnguid)s\n'
1003 '\tEndGlobalSection\n' % locals())
1005 if self.version_num >= 8.0:
1006 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
1008 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
1010 confkeys = self.configs.keys()
1013 for name in confkeys:
1014 variant = self.configs[name].variant
1015 platform = self.configs[name].platform
1016 if self.version_num >= 8.0:
1017 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1019 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1021 self.file.write('\tEndGlobalSection\n')
1022 if self.version_num < 7.1:
1023 self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1024 '\tEndGlobalSection\n')
1025 if self.version_num >= 8.0:
1026 self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1028 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1030 for name in confkeys:
1031 variant = self.configs[name].variant
1032 platform = self.configs[name].platform
1033 if self.version_num >= 8.0:
1034 for p in self.dspfiles:
1035 guid = _generateGUID(p, '')
1036 self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1037 '\t\t%s.%s|%s.Build.0 = %s|%s\n' % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1039 for p in self.dspfiles:
1040 guid = _generateGUID(p, '')
1041 self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1042 '\t\t%s.%s.Build.0 = %s|%s\n' %(guid,variant,variant,platform,guid,variant,variant,platform))
1044 self.file.write('\tEndGlobalSection\n')
1046 if self.version_num >= 8.0:
1047 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1048 '\t\tHideSolutionNode = FALSE\n'
1049 '\tEndGlobalSection\n')
1051 self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1052 '\tEndGlobalSection\n'
1053 '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1054 '\tEndGlobalSection\n')
1055 self.file.write('EndGlobal\n')
1056 if self.nokeep == 0:
1057 pdata = pickle.dumps(self.configs,1)
1058 pdata = base64.encodestring(pdata)
1059 self.file.write(pdata + '\n')
1063 self.file = open(self.dswfile,'w')
1064 except IOError, detail:
1065 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1067 self.PrintSolution()
1071 Microsoft Developer Studio Workspace File, Format Version 6.00
1072 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1074 ###############################################################################
1076 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1086 ###############################################################################
1098 ###############################################################################
1101 class _GenerateV6DSW(_DSWGenerator):
1102 """Generates a Workspace file for MSVS 6.0"""
1104 def PrintWorkspace(self):
1105 """ writes a DSW file """
1107 dspfile = self.dspfiles[0]
1108 self.file.write(V6DSWHeader % locals())
1112 self.file = open(self.dswfile,'w')
1113 except IOError, detail:
1114 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1116 self.PrintWorkspace()
1120 def GenerateDSP(dspfile, source, env):
1121 """Generates a Project file based on the version of MSVS that is being used"""
1124 if 'MSVS_VERSION' in env:
1125 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1126 if version_num >= 7.0:
1127 g = _GenerateV7DSP(dspfile, source, env)
1130 g = _GenerateV6DSP(dspfile, source, env)
1133 def GenerateDSW(dswfile, source, env):
1134 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1137 if 'MSVS_VERSION' in env:
1138 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1139 if version_num >= 7.0:
1140 g = _GenerateV7DSW(dswfile, source, env)
1143 g = _GenerateV6DSW(dswfile, source, env)
1147 ##############################################################################
1148 # Above here are the classes and functions for generation of
1149 # DSP/DSW/SLN/VCPROJ files.
1150 ##############################################################################
1152 def GetMSVSProjectSuffix(target, source, env, for_signature):
1153 return env['MSVS']['PROJECTSUFFIX']
1155 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1156 return env['MSVS']['SOLUTIONSUFFIX']
1158 def GenerateProject(target, source, env):
1159 # generate the dsp file, according to the version of MSVS.
1160 builddspfile = target[0]
1161 dspfile = builddspfile.srcnode()
1163 # this detects whether or not we're using a VariantDir
1164 if not dspfile is builddspfile:
1166 bdsp = open(str(builddspfile), "w+")
1167 except IOError, detail:
1168 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1171 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1173 GenerateDSP(dspfile, source, env)
1175 if env.get('auto_build_solution', 1):
1176 builddswfile = target[1]
1177 dswfile = builddswfile.srcnode()
1179 if not dswfile is builddswfile:
1182 bdsw = open(str(builddswfile), "w+")
1183 except IOError, detail:
1184 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1187 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1189 GenerateDSW(dswfile, source, env)
1191 def GenerateSolution(target, source, env):
1192 GenerateDSW(target[0], source, env)
1194 def projectEmitter(target, source, env):
1195 """Sets up the DSP dependencies."""
1197 # todo: Not sure what sets source to what user has passed as target,
1198 # but this is what happens. When that is fixed, we also won't have
1199 # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1200 if source[0] == target[0]:
1203 # make sure the suffix is correct for the version of MSVS we're running.
1204 (base, suff) = SCons.Util.splitext(str(target[0]))
1205 suff = env.subst('$MSVSPROJECTSUFFIX')
1206 target[0] = base + suff
1209 source = 'prj_inputs:'
1210 source = source + env.subst('$MSVSSCONSCOM', 1)
1211 source = source + env.subst('$MSVSENCODING', 1)
1213 if 'buildtarget' in env and env['buildtarget'] != None:
1214 if SCons.Util.is_String(env['buildtarget']):
1215 source = source + ' "%s"' % env['buildtarget']
1216 elif SCons.Util.is_List(env['buildtarget']):
1217 for bt in env['buildtarget']:
1218 if SCons.Util.is_String(bt):
1219 source = source + ' "%s"' % bt
1221 try: source = source + ' "%s"' % bt.get_abspath()
1222 except AttributeError: raise SCons.Errors.InternalError, \
1223 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1225 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1226 except AttributeError: raise SCons.Errors.InternalError, \
1227 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1229 if 'outdir' in env and env['outdir'] != None:
1230 if SCons.Util.is_String(env['outdir']):
1231 source = source + ' "%s"' % env['outdir']
1232 elif SCons.Util.is_List(env['outdir']):
1233 for s in env['outdir']:
1234 if SCons.Util.is_String(s):
1235 source = source + ' "%s"' % s
1237 try: source = source + ' "%s"' % s.get_abspath()
1238 except AttributeError: raise SCons.Errors.InternalError, \
1239 "outdir can be a string, a node, a list of strings or nodes, or None"
1241 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1242 except AttributeError: raise SCons.Errors.InternalError, \
1243 "outdir can be a string, a node, a list of strings or nodes, or None"
1246 if SCons.Util.is_String(env['name']):
1247 source = source + ' "%s"' % env['name']
1249 raise SCons.Errors.InternalError, "name must be a string"
1251 if 'variant' in env:
1252 if SCons.Util.is_String(env['variant']):
1253 source = source + ' "%s"' % env['variant']
1254 elif SCons.Util.is_List(env['variant']):
1255 for variant in env['variant']:
1256 if SCons.Util.is_String(variant):
1257 source = source + ' "%s"' % variant
1259 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1261 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1263 raise SCons.Errors.InternalError, "variant must be specified"
1265 for s in _DSPGenerator.srcargs:
1267 if SCons.Util.is_String(env[s]):
1268 source = source + ' "%s' % env[s]
1269 elif SCons.Util.is_List(env[s]):
1271 if SCons.Util.is_String(t):
1272 source = source + ' "%s"' % t
1274 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1276 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1278 source = source + ' "%s"' % str(target[0])
1279 source = [SCons.Node.Python.Value(source)]
1281 targetlist = [target[0]]
1284 if env.get('auto_build_solution', 1):
1285 env['projects'] = targetlist
1286 t, s = solutionEmitter(target, target, env)
1287 targetlist = targetlist + t
1289 return (targetlist, sourcelist)
1291 def solutionEmitter(target, source, env):
1292 """Sets up the DSW dependencies."""
1294 # todo: Not sure what sets source to what user has passed as target,
1295 # but this is what happens. When that is fixed, we also won't have
1296 # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1297 if source[0] == target[0]:
1300 # make sure the suffix is correct for the version of MSVS we're running.
1301 (base, suff) = SCons.Util.splitext(str(target[0]))
1302 suff = env.subst('$MSVSSOLUTIONSUFFIX')
1303 target[0] = base + suff
1306 source = 'sln_inputs:'
1309 if SCons.Util.is_String(env['name']):
1310 source = source + ' "%s"' % env['name']
1312 raise SCons.Errors.InternalError, "name must be a string"
1314 if 'variant' in env:
1315 if SCons.Util.is_String(env['variant']):
1316 source = source + ' "%s"' % env['variant']
1317 elif SCons.Util.is_List(env['variant']):
1318 for variant in env['variant']:
1319 if SCons.Util.is_String(variant):
1320 source = source + ' "%s"' % variant
1322 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1324 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1326 raise SCons.Errors.InternalError, "variant must be specified"
1328 if 'slnguid' in env:
1329 if SCons.Util.is_String(env['slnguid']):
1330 source = source + ' "%s"' % env['slnguid']
1332 raise SCons.Errors.InternalError, "slnguid must be a string"
1334 if 'projects' in env:
1335 if SCons.Util.is_String(env['projects']):
1336 source = source + ' "%s"' % env['projects']
1337 elif SCons.Util.is_List(env['projects']):
1338 for t in env['projects']:
1339 if SCons.Util.is_String(t):
1340 source = source + ' "%s"' % t
1342 source = source + ' "%s"' % str(target[0])
1343 source = [SCons.Node.Python.Value(source)]
1345 return ([target[0]], source)
1347 projectAction = SCons.Action.Action(GenerateProject, None)
1349 solutionAction = SCons.Action.Action(GenerateSolution, None)
1351 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1352 suffix = '$MSVSPROJECTSUFFIX',
1353 emitter = projectEmitter)
1355 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1356 suffix = '$MSVSSOLUTIONSUFFIX',
1357 emitter = solutionEmitter)
1359 default_MSVS_SConscript = None
1362 """Add Builders and construction variables for Microsoft Visual
1363 Studio project files to an Environment."""
1365 env['BUILDERS']['MSVSProject']
1367 env['BUILDERS']['MSVSProject'] = projectBuilder
1370 env['BUILDERS']['MSVSSolution']
1372 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1374 env['MSVSPROJECTCOM'] = projectAction
1375 env['MSVSSOLUTIONCOM'] = solutionAction
1377 if SCons.Script.call_stack:
1378 # XXX Need to find a way to abstract this; the build engine
1379 # shouldn't depend on anything in SCons.Script.
1380 env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1382 global default_MSVS_SConscript
1383 if default_MSVS_SConscript is None:
1384 default_MSVS_SConscript = env.File('SConstruct')
1385 env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1387 env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1388 env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1389 env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1390 env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1391 env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1392 env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1393 env['MSVSENCODING'] = 'Windows-1252'
1395 # Set-up ms tools paths for default version
1396 msvc_setup_env_once(env)
1398 if 'MSVS_VERSION' in env:
1399 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1401 (version_num, suite) = (7.0, None) # guess at a default
1402 if 'MSVS' not in env:
1404 if (version_num < 7.0):
1405 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1406 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1408 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1409 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1411 env['GET_MSVSPROJECTSUFFIX'] = GetMSVSProjectSuffix
1412 env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix
1413 env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}'
1414 env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}'
1415 env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1418 return msvc_exists()
1422 # indent-tabs-mode:nil
1424 # vim: set expandtab tabstop=4 shiftwidth=4: