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__"
43 # compat layer imports "cPickle" for us if it's available.
50 import SCons.Platform.win32
51 import SCons.Script.SConscript
55 from MSCommon import msvc_exists, msvc_setup_env_once
56 from SCons.Defaults import processDefines
58 ##############################################################################
59 # Below here are the classes and functions for generation of
60 # DSP/DSW/SLN/VCPROJ files.
61 ##############################################################################
64 s = s.replace("&", "&") # do this first
65 s = s.replace("'", "'")
66 s = s.replace('"', """)
69 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
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."""
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] + "}"
86 version_re = re.compile(r'(\d+\.\d+)(.*)')
88 def msvs_parse_version(s):
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
94 num, suite = version_re.match(s).groups()
95 return float(num), suite
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']
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
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()
115 exec_script_main = xmlify(exec_script_main)
116 return exec_script_main
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).
123 python_root = os.environ['PYTHON_ROOT']
125 python_executable = sys.executable
127 python_executable = os.path.join('$$(PYTHON_ROOT)',
128 os.path.split(sys.executable)[1])
133 def splitFully(path):
134 dir, base = os.path.split(path)
135 if dir and dir != '' and dir != path:
136 return splitFully(dir)+[base]
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.'''
148 path = splitFully(file)
151 for part in path[:-1]:
155 dict[path[-1]] = file
157 # print 'Warning: failed to decompose path for '+str(file)
161 """ Base class for DSP generators """
170 def __init__(self, dspfile, source, env):
171 self.dspfile = str(dspfile)
173 get_abspath = dspfile.get_abspath
174 except AttributeError:
175 self.dspabs = os.path.abspath(dspfile)
177 self.dspabs = get_abspath()
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']
187 if 'buildtarget' not in env or env['buildtarget'] == None:
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.")
195 for bt in env['buildtarget']:
196 if SCons.Util.is_String(bt):
197 buildtarget.append(bt)
199 buildtarget.append(bt.get_abspath())
201 buildtarget = [env['buildtarget'].get_abspath()]
202 if len(buildtarget) == 1:
206 buildtarget.append(bt)
208 if 'outdir' not in env or env['outdir'] == None:
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.")
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("Sizes of 'runfile' and 'variant' lists must be the same.")
237 for s in env['runfile']:
238 if SCons.Util.is_String(s):
241 runfile.append(s.get_abspath())
243 runfile = [env['runfile'].get_abspath()]
244 if len(runfile) == 1:
250 self.sconscript = env['MSVSSCONSCRIPT']
252 cmdargs = env.get('cmdargs', '')
256 if 'name' in self.env:
257 self.name = self.env['name']
259 self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
260 self.name = self.env.subst(self.name)
270 for n in sourcenames:
276 if 'nokeep' in env and env['variant'] != 0:
279 if self.nokeep == 0 and os.path.exists(self.dspabs):
282 for t in zip(sourcenames,self.srcargs):
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)
289 if not self.env[t[1]] in self.sources[t[0]]:
290 self.sources[t[0]].append(self.env[t[1]])
292 for n in sourcenames:
293 self.sources[n].sort(lambda a, b: cmp(a.lower(), b.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))
391 #if kind.lower().find('debug') < 0:
392 if kind.lower().find('debug') < 0:
393 self.file.write('0\n')
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))
406 if not env_has_buildtarget:
407 del self.env['MSVSBUILDTARGET']
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')
415 for kind in confkeys:
417 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
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'
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')
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',
442 cats = categories.keys()
443 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
445 if not self.sources[kind]:
446 continue # skip empty groups
448 self.file.write('# Begin Group "' + kind + '"\n\n')
450 #typelist = categories[kind].replace('|', ';')
451 typelist = categories[kind].replace('|', ';')
452 self.file.write('# PROP Default_Filter "' + typelist + '"\n')
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')
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')
468 dspfile = open(self.dspabs,'r')
470 return # doesn't exist yet, so can't add anything to configs.
472 line = dspfile.readline()
475 #if line.find("# End Project") > -1:
476 if line.find("# End Project") > -1:
478 line = dspfile.readline()
480 line = dspfile.readline()
482 while line and line != '\n':
483 line = dspfile.readline()
486 # OK, we've found our little pickled cache of data.
488 datas = base64.decodestring(datas)
489 data = pickle.loads(datas)
490 except KeyboardInterrupt:
493 return # unable to unpickle any data for some reason
495 self.configs.update(data)
498 line = dspfile.readline()
500 while line and line != '\n':
501 line = dspfile.readline()
504 # OK, we've found our little pickled cache of data.
505 # it has a "# " in front of it, so we strip that.
507 datas = base64.decodestring(datas)
508 data = pickle.loads(datas)
509 except KeyboardInterrupt:
512 return # unable to unpickle any data for some reason
514 self.sources.update(data)
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))
527 <?xml version="1.0" encoding = "%(encoding)s"?>
529 \tProjectType="Visual C++"
530 \tVersion="%(versionstr)s"
533 \tKeyword="MakeFileProj">
536 V7DSPConfiguration = """\
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"
543 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
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"/>
554 <?xml version="1.0" encoding="%(encoding)s"?>
556 \tProjectType="Visual C++"
557 \tVersion="%(versionstr)s"
560 \tRootNamespace="%(name)s"
561 \tKeyword="MakeFileProj">
564 V8DSPConfiguration = """\
566 \t\t\tName="%(variant)s|%(platform)s"
567 \t\t\tConfigurationType="0"
569 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
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=""
586 class _GenerateV7DSP(_DSPGenerator):
587 """Generates a Project file for MSVS .NET"""
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
598 if self.version_num >= 7.1:
599 self.versionstr = '7.10'
601 self.versionstr = '7.00'
602 self.dspheader = V7DSPHeader
603 self.dspconfiguration = V7DSPConfiguration
606 def PrintHeader(self):
608 versionstr = self.versionstr
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))
625 scc_attrs = ('\tProjectGUID="%s"\n'
626 '\tSccProjectName="%s"\n'
627 '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
629 self.file.write(self.dspheader % locals())
631 self.file.write('\t<Platforms>\n')
632 for platform in self.platforms:
635 '\t\t\tName="%s"/>\n' % platform)
636 self.file.write('\t</Platforms>\n')
638 if self.version_num >= 8.0:
639 self.file.write('\t<ToolFiles>\n'
642 def PrintProject(self):
643 self.file.write('\t<Configurations>\n')
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
654 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
655 if not env_has_buildtarget:
656 self.env['MSVSBUILDTARGET'] = buildtarget
658 starting = 'echo Starting SCons && '
660 cmdargs = ' ' + 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)
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', [])))
673 if not env_has_buildtarget:
674 del self.env['MSVSBUILDTARGET']
676 self.file.write(self.dspconfiguration % locals())
678 self.file.write('\t</Configurations>\n')
680 if self.version_num >= 7.1:
681 self.file.write('\t<References>\n'
684 self.PrintSourceFiles()
686 self.file.write('</VisualStudioProject>\n')
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')
697 def printSources(self, hierarchy, commonprefix):
698 sorteditems = hierarchy.items()
699 sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
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')
710 for key, value in sorteditems:
711 if SCons.Util.is_String(value):
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))
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',
727 self.file.write('\t<Files>\n')
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]]
734 self.file.write('\t\t<Filter\n'
736 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
738 sources = self.sources[kind]
740 # First remove any common prefix
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]
752 elif len(sources) == 1:
753 commonprefix = os.path.dirname( sources[0] )
754 sources[0] = os.path.basename( sources[0] )
756 hierarchy = makeHierarchy(sources)
757 self.printSources(hierarchy, commonprefix=commonprefix)
760 self.file.write('\t\t</Filter>\n')
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))
767 self.file.write('\t</Files>\n'
773 dspfile = open(self.dspabs,'r')
775 return # doesn't exist yet, so can't add anything to configs.
777 line = dspfile.readline()
780 #if line.find('<!-- SCons Data:') > -1:
781 if line.find('<!-- SCons Data:') > -1:
783 line = dspfile.readline()
785 line = dspfile.readline()
787 while line and line != '\n':
788 line = dspfile.readline()
791 # OK, we've found our little pickled cache of data.
793 datas = base64.decodestring(datas)
794 data = pickle.loads(datas)
795 except KeyboardInterrupt:
798 return # unable to unpickle any data for some reason
800 self.configs.update(data)
803 line = dspfile.readline()
805 while line and line != '\n':
806 line = dspfile.readline()
809 # OK, we've found our little pickled cache of data.
811 datas = base64.decodestring(datas)
812 data = pickle.loads(datas)
813 except KeyboardInterrupt:
816 return # unable to unpickle any data for some reason
818 self.sources.update(data)
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))
831 """ Base class for DSW generators """
832 def __init__(self, dswfile, source, env):
833 self.dswfile = os.path.normpath(str(dswfile))
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))
846 if 'name' in self.env:
847 self.name = self.env['name']
849 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
850 self.name = self.env.subst(self.name)
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)
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'
871 if 'slnguid' in env and env['slnguid']:
872 self.slnguid = env['slnguid']
874 self.slnguid = _generateGUID(dswfile, self.name)
879 if 'nokeep' in env and env['variant'] != 0:
882 if self.nokeep == 0 and os.path.exists(self.dswfile):
885 def AddConfig(self, variant, dswfile=dswfile):
888 match = re.match('(.*)\|(.*)', variant)
890 config.variant = match.group(1)
891 config.platform = match.group(2)
893 config.variant = variant
894 config.platform = 'Win32'
896 self.configs[variant] = config
897 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
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)
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)
916 dswfile = open(self.dswfile,'r')
918 return # doesn't exist yet, so can't add anything to configs.
920 line = dswfile.readline()
922 if line[:9] == "EndGlobal":
924 line = dswfile.readline()
926 line = dswfile.readline()
929 line = dswfile.readline()
932 # OK, we've found our little pickled cache of data.
934 datas = base64.decodestring(datas)
935 data = pickle.loads(datas)
936 except KeyboardInterrupt:
939 return # unable to unpickle any data for some reason
941 self.configs.update(data)
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':
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')
961 self.file.write('Global\n')
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', '')
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())
989 if self.version_num >= 8.0:
990 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
992 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
994 confkeys = sorted(self.configs.keys())
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))
1002 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
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')
1011 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
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))
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))
1027 self.file.write('\tEndGlobalSection\n')
1029 if self.version_num >= 8.0:
1030 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1031 '\t\tHideSolutionNode = FALSE\n'
1032 '\tEndGlobalSection\n')
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')
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))
1050 self.PrintSolution()
1054 Microsoft Developer Studio Workspace File, Format Version 6.00
1055 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1057 ###############################################################################
1059 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1069 ###############################################################################
1081 ###############################################################################
1084 class _GenerateV6DSW(_DSWGenerator):
1085 """Generates a Workspace file for MSVS 6.0"""
1087 def PrintWorkspace(self):
1088 """ writes a DSW file """
1090 dspfile = self.dspfiles[0]
1091 self.file.write(V6DSWHeader % locals())
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))
1099 self.PrintWorkspace()
1103 def GenerateDSP(dspfile, source, env):
1104 """Generates a Project file based on the version of MSVS that is being used"""
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)
1113 g = _GenerateV6DSP(dspfile, source, env)
1116 def GenerateDSW(dswfile, source, env):
1117 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
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)
1126 g = _GenerateV6DSW(dswfile, source, env)
1130 ##############################################################################
1131 # Above here are the classes and functions for generation of
1132 # DSP/DSW/SLN/VCPROJ files.
1133 ##############################################################################
1135 def GetMSVSProjectSuffix(target, source, env, for_signature):
1136 return env['MSVS']['PROJECTSUFFIX']
1138 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1139 return env['MSVS']['SOLUTIONSUFFIX']
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()
1146 # this detects whether or not we're using a VariantDir
1147 if not dspfile is builddspfile:
1149 bdsp = open(str(builddspfile), "w+")
1150 except IOError, detail:
1151 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1154 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1156 GenerateDSP(dspfile, source, env)
1158 if env.get('auto_build_solution', 1):
1159 builddswfile = target[1]
1160 dswfile = builddswfile.srcnode()
1162 if not dswfile is builddswfile:
1165 bdsw = open(str(builddswfile), "w+")
1166 except IOError, detail:
1167 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1170 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1172 GenerateDSW(dswfile, source, env)
1174 def GenerateSolution(target, source, env):
1175 GenerateDSW(target[0], source, env)
1177 def projectEmitter(target, source, env):
1178 """Sets up the DSP dependencies."""
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]:
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
1192 source = 'prj_inputs:'
1193 source = source + env.subst('$MSVSSCONSCOM', 1)
1194 source = source + env.subst('$MSVSENCODING', 1)
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
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")
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")
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
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")
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")
1225 if SCons.Util.is_String(env['name']):
1226 source = source + ' "%s"' % env['name']
1228 raise SCons.Errors.InternalError("name must be a string")
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
1238 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1240 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1242 raise SCons.Errors.InternalError("variant must be specified")
1244 for s in _DSPGenerator.srcargs:
1246 if SCons.Util.is_String(env[s]):
1247 source = source + ' "%s' % env[s]
1248 elif SCons.Util.is_List(env[s]):
1250 if SCons.Util.is_String(t):
1251 source = source + ' "%s"' % t
1253 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1255 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1257 source = source + ' "%s"' % str(target[0])
1258 source = [SCons.Node.Python.Value(source)]
1260 targetlist = [target[0]]
1263 if env.get('auto_build_solution', 1):
1264 env['projects'] = targetlist
1265 t, s = solutionEmitter(target, target, env)
1266 targetlist = targetlist + t
1268 return (targetlist, sourcelist)
1270 def solutionEmitter(target, source, env):
1271 """Sets up the DSW dependencies."""
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]:
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
1285 source = 'sln_inputs:'
1288 if SCons.Util.is_String(env['name']):
1289 source = source + ' "%s"' % env['name']
1291 raise SCons.Errors.InternalError("name must be a string")
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
1301 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1303 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1305 raise SCons.Errors.InternalError("variant must be specified")
1307 if 'slnguid' in env:
1308 if SCons.Util.is_String(env['slnguid']):
1309 source = source + ' "%s"' % env['slnguid']
1311 raise SCons.Errors.InternalError("slnguid must be a string")
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
1321 source = source + ' "%s"' % str(target[0])
1322 source = [SCons.Node.Python.Value(source)]
1324 return ([target[0]], source)
1326 projectAction = SCons.Action.Action(GenerateProject, None)
1328 solutionAction = SCons.Action.Action(GenerateSolution, None)
1330 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1331 suffix = '$MSVSPROJECTSUFFIX',
1332 emitter = projectEmitter)
1334 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1335 suffix = '$MSVSSOLUTIONSUFFIX',
1336 emitter = solutionEmitter)
1338 default_MSVS_SConscript = None
1341 """Add Builders and construction variables for Microsoft Visual
1342 Studio project files to an Environment."""
1344 env['BUILDERS']['MSVSProject']
1346 env['BUILDERS']['MSVSProject'] = projectBuilder
1349 env['BUILDERS']['MSVSSolution']
1351 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1353 env['MSVSPROJECTCOM'] = projectAction
1354 env['MSVSSOLUTIONCOM'] = solutionAction
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
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
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'
1374 # Set-up ms tools paths for default version
1375 msvc_setup_env_once(env)
1377 if 'MSVS_VERSION' in env:
1378 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1380 (version_num, suite) = (7.0, None) # guess at a default
1381 if 'MSVS' not in env:
1383 if (version_num < 7.0):
1384 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1385 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1387 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1388 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
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')
1397 return msvc_exists()
1401 # indent-tabs-mode:nil
1403 # vim: set expandtab tabstop=4 shiftwidth=4: