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 #TODO 2.4: compat layer supports sorted(key=) but not sort(key=)
294 #TODO 2.4: self.sources[n].sort(key=lambda a: a.lower())
295 self.sources[n] = sorted(self.sources[n], key=lambda a: a.lower())
297 def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
299 config.buildtarget = buildtarget
300 config.outdir = outdir
301 config.cmdargs = cmdargs
302 config.runfile = runfile
304 match = re.match('(.*)\|(.*)', variant)
306 config.variant = match.group(1)
307 config.platform = match.group(2)
309 config.variant = variant
310 config.platform = 'Win32'
312 self.configs[variant] = config
313 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
315 for i in range(len(variants)):
316 AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
319 for key in self.configs.keys():
320 platform = self.configs[key].platform
321 if not platform in self.platforms:
322 self.platforms.append(platform)
328 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
329 # Microsoft Developer Studio Generated Build File, Format Version 6.00
332 # TARGTYPE "Win32 (x86) External Target" 0x0106
334 CFG=%(name)s - Win32 %(confkey)s
335 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
336 !MESSAGE use the Export Makefile command and run
338 !MESSAGE NMAKE /f "%(name)s.mak".
340 !MESSAGE You can specify a configuration when running NMAKE
341 !MESSAGE by defining the macro CFG on the command line. For example:
343 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
345 !MESSAGE Possible choices for configuration are:
349 class _GenerateV6DSP(_DSPGenerator):
350 """Generates a Project file for MSVS 6.0"""
352 def PrintHeader(self):
353 # pick a default config
354 confkeys = sorted(self.configs.keys())
357 confkey = confkeys[0]
359 self.file.write(V6DSPHeader % locals())
361 for kind in confkeys:
362 self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
364 self.file.write('!MESSAGE \n\n')
366 def PrintProject(self):
368 self.file.write('# Begin Project\n'
369 '# PROP AllowPerConfigDependencies 0\n'
370 '# PROP Scc_ProjName ""\n'
371 '# PROP Scc_LocalPath ""\n\n')
374 confkeys = sorted(self.configs.keys())
375 for kind in confkeys:
376 outdir = self.configs[kind].outdir
377 buildtarget = self.configs[kind].buildtarget
379 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
382 self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
384 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
385 if not env_has_buildtarget:
386 self.env['MSVSBUILDTARGET'] = buildtarget
388 # have to write this twice, once with the BASE settings, and once without
389 for base in ("BASE ",""):
390 self.file.write('# PROP %sUse_MFC 0\n'
391 '# PROP %sUse_Debug_Libraries ' % (base, base))
393 #if kind.lower().find('debug') < 0:
394 if kind.lower().find('debug') < 0:
395 self.file.write('0\n')
397 self.file.write('1\n')
398 self.file.write('# PROP %sOutput_Dir "%s"\n'
399 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
400 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
401 self.file.write('# PROP %sCmd_Line "%s"\n'
402 '# PROP %sRebuild_Opt "-c && %s"\n'
403 '# PROP %sTarget_File "%s"\n'
404 '# PROP %sBsc_Name ""\n'
405 '# PROP %sTarget_Dir ""\n'\
406 %(base,cmd,base,cmd,base,buildtarget,base,base))
408 if not env_has_buildtarget:
409 del self.env['MSVSBUILDTARGET']
411 self.file.write('\n!ENDIF\n\n'
412 '# Begin Target\n\n')
413 for kind in confkeys:
414 self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
415 self.file.write('\n')
417 for kind in confkeys:
419 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
422 self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
423 self.file.write('!ENDIF \n\n')
424 self.PrintSourceFiles()
425 self.file.write('# End Target\n'
429 # now we pickle some data and add it to the file -- MSDEV will ignore it.
430 pdata = pickle.dumps(self.configs,1)
431 pdata = base64.encodestring(pdata)
432 self.file.write(pdata + '\n')
433 pdata = pickle.dumps(self.sources,1)
434 pdata = base64.encodestring(pdata)
435 self.file.write(pdata + '\n')
437 def PrintSourceFiles(self):
438 categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
439 'Header Files': 'h|hpp|hxx|hm|inl',
440 'Local Headers': 'h|hpp|hxx|hm|inl',
441 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
444 for kind in sorted(categories.keys(), key=lambda a: a.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 = sorted(hierarchy.items(), key=lambda a: a[0].lower())
700 # First folders, then files
701 for key, value in sorteditems:
702 if SCons.Util.is_Dict(value):
703 self.file.write('\t\t\t<Filter\n'
704 '\t\t\t\tName="%s"\n'
705 '\t\t\t\tFilter="">\n' % (key))
706 self.printSources(value, commonprefix)
707 self.file.write('\t\t\t</Filter>\n')
709 for key, value in sorteditems:
710 if SCons.Util.is_String(value):
713 file = os.path.join(commonprefix, value)
714 file = os.path.normpath(file)
715 self.file.write('\t\t\t<File\n'
716 '\t\t\t\tRelativePath="%s">\n'
717 '\t\t\t</File>\n' % (file))
719 def PrintSourceFiles(self):
720 categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
721 'Header Files': 'h;hpp;hxx;hm;inl',
722 'Local Headers': 'h;hpp;hxx;hm;inl',
723 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
726 self.file.write('\t<Files>\n')
728 cats = sorted([k for k in categories.keys() if self.sources[k]],
729 key=lambda a: a.lower())
732 self.file.write('\t\t<Filter\n'
734 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
736 sources = self.sources[kind]
738 # First remove any common prefix
741 s = list(map(os.path.normpath, sources))
742 # take the dirname because the prefix may include parts
743 # of the filenames (e.g. if you have 'dir\abcd' and
744 # 'dir\acde' then the cp will be 'dir\a' )
745 cp = os.path.dirname( os.path.commonprefix(s) )
746 if cp and s[0][len(cp)] == os.sep:
747 # +1 because the filename starts after the separator
748 sources = [s[len(cp)+1:] for s in sources]
750 elif len(sources) == 1:
751 commonprefix = os.path.dirname( sources[0] )
752 sources[0] = os.path.basename( sources[0] )
754 hierarchy = makeHierarchy(sources)
755 self.printSources(hierarchy, commonprefix=commonprefix)
758 self.file.write('\t\t</Filter>\n')
760 # add the SConscript file outside of the groups
761 self.file.write('\t\t<File\n'
762 '\t\t\tRelativePath="%s">\n'
763 '\t\t</File>\n' % str(self.sconscript))
765 self.file.write('\t</Files>\n'
771 dspfile = open(self.dspabs,'r')
773 return # doesn't exist yet, so can't add anything to configs.
775 line = dspfile.readline()
778 #if line.find('<!-- SCons Data:') > -1:
779 if line.find('<!-- SCons Data:') > -1:
781 line = dspfile.readline()
783 line = dspfile.readline()
785 while line and line != '\n':
786 line = dspfile.readline()
789 # OK, we've found our little pickled cache of data.
791 datas = base64.decodestring(datas)
792 data = pickle.loads(datas)
793 except KeyboardInterrupt:
796 return # unable to unpickle any data for some reason
798 self.configs.update(data)
801 line = dspfile.readline()
803 while line and line != '\n':
804 line = dspfile.readline()
807 # OK, we've found our little pickled cache of data.
809 datas = base64.decodestring(datas)
810 data = pickle.loads(datas)
811 except KeyboardInterrupt:
814 return # unable to unpickle any data for some reason
816 self.sources.update(data)
820 self.file = open(self.dspabs,'w')
821 except IOError, detail:
822 raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
829 """ Base class for DSW generators """
830 def __init__(self, dswfile, source, env):
831 self.dswfile = os.path.normpath(str(dswfile))
834 if 'projects' not in env:
835 raise SCons.Errors.UserError("You must specify a 'projects' argument to create an MSVSSolution.")
836 projects = env['projects']
837 if not SCons.Util.is_List(projects):
838 raise SCons.Errors.InternalError("The 'projects' argument must be a list of nodes.")
839 projects = SCons.Util.flatten(projects)
840 if len(projects) < 1:
841 raise SCons.Errors.UserError("You must specify at least one project to create an MSVSSolution.")
842 self.dspfiles = list(map(str, projects))
844 if 'name' in self.env:
845 self.name = self.env['name']
847 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
848 self.name = self.env.subst(self.name)
853 class _GenerateV7DSW(_DSWGenerator):
854 """Generates a Solution file for MSVS .NET"""
855 def __init__(self, dswfile, source, env):
856 _DSWGenerator.__init__(self, dswfile, source, env)
859 self.version = self.env['MSVS_VERSION']
860 self.version_num, self.suite = msvs_parse_version(self.version)
861 self.versionstr = '7.00'
862 if self.version_num >= 8.0:
863 self.versionstr = '9.00'
864 elif self.version_num >= 7.1:
865 self.versionstr = '8.00'
866 if self.version_num >= 8.0:
867 self.versionstr = '9.00'
869 if 'slnguid' in env and env['slnguid']:
870 self.slnguid = env['slnguid']
872 self.slnguid = _generateGUID(dswfile, self.name)
877 if 'nokeep' in env and env['variant'] != 0:
880 if self.nokeep == 0 and os.path.exists(self.dswfile):
883 def AddConfig(self, variant, dswfile=dswfile):
886 match = re.match('(.*)\|(.*)', variant)
888 config.variant = match.group(1)
889 config.platform = match.group(2)
891 config.variant = variant
892 config.platform = 'Win32'
894 self.configs[variant] = config
895 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
897 if 'variant' not in env:
898 raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
899 "'Release') to create an MSVS Solution File.")
900 elif SCons.Util.is_String(env['variant']):
901 AddConfig(self, env['variant'])
902 elif SCons.Util.is_List(env['variant']):
903 for variant in env['variant']:
904 AddConfig(self, variant)
907 for key in self.configs.keys():
908 platform = self.configs[key].platform
909 if not platform in self.platforms:
910 self.platforms.append(platform)
914 dswfile = open(self.dswfile,'r')
916 return # doesn't exist yet, so can't add anything to configs.
918 line = dswfile.readline()
920 if line[:9] == "EndGlobal":
922 line = dswfile.readline()
924 line = dswfile.readline()
927 line = dswfile.readline()
930 # OK, we've found our little pickled cache of data.
932 datas = base64.decodestring(datas)
933 data = pickle.loads(datas)
934 except KeyboardInterrupt:
937 return # unable to unpickle any data for some reason
939 self.configs.update(data)
941 def PrintSolution(self):
942 """Writes a solution file"""
943 self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
944 if self.version_num >= 8.0:
945 self.file.write('# Visual Studio 2005\n')
946 for p in self.dspfiles:
947 name = os.path.basename(p)
948 base, suffix = SCons.Util.splitext(name)
949 if suffix == '.vcproj':
951 guid = _generateGUID(p, '')
952 self.file.write('Project("%s") = "%s", "%s", "%s"\n'
953 % ( external_makefile_guid, name, p, guid ) )
954 if self.version_num >= 7.1 and self.version_num < 8.0:
955 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
956 '\tEndProjectSection\n')
957 self.file.write('EndProject\n')
959 self.file.write('Global\n')
962 if 'MSVS_SCC_PROVIDER' in env:
963 dspfile_base = os.path.basename(self.dspfile)
964 slnguid = self.slnguid
965 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
966 scc_provider = scc_provider.replace(' ', r'\u0020')
967 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
968 # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
969 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
970 scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
971 # project_guid = env.get('MSVS_PROJECT_GUID', '')
973 self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
974 '\t\tSccNumberOfProjects = 2\n'
975 '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
976 '\t\tSccLocalPath0 = %(scc_local_path)s\n'
977 '\t\tCanCheckoutShared = true\n'
978 '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
979 '\t\tSccProjectName1 = %(scc_project_name)s\n'
980 '\t\tSccLocalPath1 = %(scc_local_path)s\n'
981 '\t\tSccProvider1 = %(scc_provider)s\n'
982 '\t\tCanCheckoutShared = true\n'
983 '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
984 '\t\tSolutionUniqueID = %(slnguid)s\n'
985 '\tEndGlobalSection\n' % locals())
987 if self.version_num >= 8.0:
988 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
990 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
992 confkeys = sorted(self.configs.keys())
994 for name in confkeys:
995 variant = self.configs[name].variant
996 platform = self.configs[name].platform
997 if self.version_num >= 8.0:
998 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1000 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1002 self.file.write('\tEndGlobalSection\n')
1003 if self.version_num < 7.1:
1004 self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1005 '\tEndGlobalSection\n')
1006 if self.version_num >= 8.0:
1007 self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1009 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1011 for name in confkeys:
1012 variant = self.configs[name].variant
1013 platform = self.configs[name].platform
1014 if self.version_num >= 8.0:
1015 for p in self.dspfiles:
1016 guid = _generateGUID(p, '')
1017 self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1018 '\t\t%s.%s|%s.Build.0 = %s|%s\n' % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1020 for p in self.dspfiles:
1021 guid = _generateGUID(p, '')
1022 self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1023 '\t\t%s.%s.Build.0 = %s|%s\n' %(guid,variant,variant,platform,guid,variant,variant,platform))
1025 self.file.write('\tEndGlobalSection\n')
1027 if self.version_num >= 8.0:
1028 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1029 '\t\tHideSolutionNode = FALSE\n'
1030 '\tEndGlobalSection\n')
1032 self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1033 '\tEndGlobalSection\n'
1034 '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1035 '\tEndGlobalSection\n')
1036 self.file.write('EndGlobal\n')
1037 if self.nokeep == 0:
1038 pdata = pickle.dumps(self.configs,1)
1039 pdata = base64.encodestring(pdata)
1040 self.file.write(pdata + '\n')
1044 self.file = open(self.dswfile,'w')
1045 except IOError, detail:
1046 raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1048 self.PrintSolution()
1052 Microsoft Developer Studio Workspace File, Format Version 6.00
1053 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1055 ###############################################################################
1057 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1067 ###############################################################################
1079 ###############################################################################
1082 class _GenerateV6DSW(_DSWGenerator):
1083 """Generates a Workspace file for MSVS 6.0"""
1085 def PrintWorkspace(self):
1086 """ writes a DSW file """
1088 dspfile = self.dspfiles[0]
1089 self.file.write(V6DSWHeader % locals())
1093 self.file = open(self.dswfile,'w')
1094 except IOError, detail:
1095 raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
1097 self.PrintWorkspace()
1101 def GenerateDSP(dspfile, source, env):
1102 """Generates a Project 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 = _GenerateV7DSP(dspfile, source, env)
1111 g = _GenerateV6DSP(dspfile, source, env)
1114 def GenerateDSW(dswfile, source, env):
1115 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1118 if 'MSVS_VERSION' in env:
1119 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1120 if version_num >= 7.0:
1121 g = _GenerateV7DSW(dswfile, source, env)
1124 g = _GenerateV6DSW(dswfile, source, env)
1128 ##############################################################################
1129 # Above here are the classes and functions for generation of
1130 # DSP/DSW/SLN/VCPROJ files.
1131 ##############################################################################
1133 def GetMSVSProjectSuffix(target, source, env, for_signature):
1134 return env['MSVS']['PROJECTSUFFIX']
1136 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1137 return env['MSVS']['SOLUTIONSUFFIX']
1139 def GenerateProject(target, source, env):
1140 # generate the dsp file, according to the version of MSVS.
1141 builddspfile = target[0]
1142 dspfile = builddspfile.srcnode()
1144 # this detects whether or not we're using a VariantDir
1145 if not dspfile is builddspfile:
1147 bdsp = open(str(builddspfile), "w+")
1148 except IOError, detail:
1149 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1152 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1154 GenerateDSP(dspfile, source, env)
1156 if env.get('auto_build_solution', 1):
1157 builddswfile = target[1]
1158 dswfile = builddswfile.srcnode()
1160 if not dswfile is builddswfile:
1163 bdsw = open(str(builddswfile), "w+")
1164 except IOError, detail:
1165 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1168 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1170 GenerateDSW(dswfile, source, env)
1172 def GenerateSolution(target, source, env):
1173 GenerateDSW(target[0], source, env)
1175 def projectEmitter(target, source, env):
1176 """Sets up the DSP dependencies."""
1178 # todo: Not sure what sets source to what user has passed as target,
1179 # but this is what happens. When that is fixed, we also won't have
1180 # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1181 if source[0] == target[0]:
1184 # make sure the suffix is correct for the version of MSVS we're running.
1185 (base, suff) = SCons.Util.splitext(str(target[0]))
1186 suff = env.subst('$MSVSPROJECTSUFFIX')
1187 target[0] = base + suff
1190 source = 'prj_inputs:'
1191 source = source + env.subst('$MSVSSCONSCOM', 1)
1192 source = source + env.subst('$MSVSENCODING', 1)
1194 if 'buildtarget' in env and env['buildtarget'] != None:
1195 if SCons.Util.is_String(env['buildtarget']):
1196 source = source + ' "%s"' % env['buildtarget']
1197 elif SCons.Util.is_List(env['buildtarget']):
1198 for bt in env['buildtarget']:
1199 if SCons.Util.is_String(bt):
1200 source = source + ' "%s"' % bt
1202 try: source = source + ' "%s"' % bt.get_abspath()
1203 except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1205 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1206 except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
1208 if 'outdir' in env and env['outdir'] != None:
1209 if SCons.Util.is_String(env['outdir']):
1210 source = source + ' "%s"' % env['outdir']
1211 elif SCons.Util.is_List(env['outdir']):
1212 for s in env['outdir']:
1213 if SCons.Util.is_String(s):
1214 source = source + ' "%s"' % s
1216 try: source = source + ' "%s"' % s.get_abspath()
1217 except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1219 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1220 except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
1223 if SCons.Util.is_String(env['name']):
1224 source = source + ' "%s"' % env['name']
1226 raise SCons.Errors.InternalError("name must be a string")
1228 if 'variant' in env:
1229 if SCons.Util.is_String(env['variant']):
1230 source = source + ' "%s"' % env['variant']
1231 elif SCons.Util.is_List(env['variant']):
1232 for variant in env['variant']:
1233 if SCons.Util.is_String(variant):
1234 source = source + ' "%s"' % variant
1236 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1238 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1240 raise SCons.Errors.InternalError("variant must be specified")
1242 for s in _DSPGenerator.srcargs:
1244 if SCons.Util.is_String(env[s]):
1245 source = source + ' "%s' % env[s]
1246 elif SCons.Util.is_List(env[s]):
1248 if SCons.Util.is_String(t):
1249 source = source + ' "%s"' % t
1251 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1253 raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
1255 source = source + ' "%s"' % str(target[0])
1256 source = [SCons.Node.Python.Value(source)]
1258 targetlist = [target[0]]
1261 if env.get('auto_build_solution', 1):
1262 env['projects'] = targetlist
1263 t, s = solutionEmitter(target, target, env)
1264 targetlist = targetlist + t
1266 return (targetlist, sourcelist)
1268 def solutionEmitter(target, source, env):
1269 """Sets up the DSW dependencies."""
1271 # todo: Not sure what sets source to what user has passed as target,
1272 # but this is what happens. When that is fixed, we also won't have
1273 # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1274 if source[0] == target[0]:
1277 # make sure the suffix is correct for the version of MSVS we're running.
1278 (base, suff) = SCons.Util.splitext(str(target[0]))
1279 suff = env.subst('$MSVSSOLUTIONSUFFIX')
1280 target[0] = base + suff
1283 source = 'sln_inputs:'
1286 if SCons.Util.is_String(env['name']):
1287 source = source + ' "%s"' % env['name']
1289 raise SCons.Errors.InternalError("name must be a string")
1291 if 'variant' in env:
1292 if SCons.Util.is_String(env['variant']):
1293 source = source + ' "%s"' % env['variant']
1294 elif SCons.Util.is_List(env['variant']):
1295 for variant in env['variant']:
1296 if SCons.Util.is_String(variant):
1297 source = source + ' "%s"' % variant
1299 raise SCons.Errors.InternalError("name must be a string or a list of strings")
1301 raise SCons.Errors.InternalError("variant must be a string or a list of strings")
1303 raise SCons.Errors.InternalError("variant must be specified")
1305 if 'slnguid' in env:
1306 if SCons.Util.is_String(env['slnguid']):
1307 source = source + ' "%s"' % env['slnguid']
1309 raise SCons.Errors.InternalError("slnguid must be a string")
1311 if 'projects' in env:
1312 if SCons.Util.is_String(env['projects']):
1313 source = source + ' "%s"' % env['projects']
1314 elif SCons.Util.is_List(env['projects']):
1315 for t in env['projects']:
1316 if SCons.Util.is_String(t):
1317 source = source + ' "%s"' % t
1319 source = source + ' "%s"' % str(target[0])
1320 source = [SCons.Node.Python.Value(source)]
1322 return ([target[0]], source)
1324 projectAction = SCons.Action.Action(GenerateProject, None)
1326 solutionAction = SCons.Action.Action(GenerateSolution, None)
1328 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1329 suffix = '$MSVSPROJECTSUFFIX',
1330 emitter = projectEmitter)
1332 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1333 suffix = '$MSVSSOLUTIONSUFFIX',
1334 emitter = solutionEmitter)
1336 default_MSVS_SConscript = None
1339 """Add Builders and construction variables for Microsoft Visual
1340 Studio project files to an Environment."""
1342 env['BUILDERS']['MSVSProject']
1344 env['BUILDERS']['MSVSProject'] = projectBuilder
1347 env['BUILDERS']['MSVSSolution']
1349 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1351 env['MSVSPROJECTCOM'] = projectAction
1352 env['MSVSSOLUTIONCOM'] = solutionAction
1354 if SCons.Script.call_stack:
1355 # XXX Need to find a way to abstract this; the build engine
1356 # shouldn't depend on anything in SCons.Script.
1357 env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1359 global default_MSVS_SConscript
1360 if default_MSVS_SConscript is None:
1361 default_MSVS_SConscript = env.File('SConstruct')
1362 env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1364 env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1365 env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1366 env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1367 env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1368 env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1369 env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1370 env['MSVSENCODING'] = 'Windows-1252'
1372 # Set-up ms tools paths for default version
1373 msvc_setup_env_once(env)
1375 if 'MSVS_VERSION' in env:
1376 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1378 (version_num, suite) = (7.0, None) # guess at a default
1379 if 'MSVS' not in env:
1381 if (version_num < 7.0):
1382 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1383 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1385 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1386 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1388 env['GET_MSVSPROJECTSUFFIX'] = GetMSVSProjectSuffix
1389 env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix
1390 env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}'
1391 env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}'
1392 env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1395 return msvc_exists()
1399 # indent-tabs-mode:nil
1401 # vim: set expandtab tabstop=4 shiftwidth=4: