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, \
181 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
182 "'Release') to create an MSVSProject."
183 elif SCons.Util.is_String(env['variant']):
184 variants = [env['variant']]
185 elif SCons.Util.is_List(env['variant']):
186 variants = env['variant']
188 if 'buildtarget' not in env or env['buildtarget'] == None:
190 elif SCons.Util.is_String(env['buildtarget']):
191 buildtarget = [env['buildtarget']]
192 elif SCons.Util.is_List(env['buildtarget']):
193 if len(env['buildtarget']) != len(variants):
194 raise SCons.Errors.InternalError, \
195 "Sizes of 'buildtarget' and 'variant' lists must be the same."
197 for bt in env['buildtarget']:
198 if SCons.Util.is_String(bt):
199 buildtarget.append(bt)
201 buildtarget.append(bt.get_abspath())
203 buildtarget = [env['buildtarget'].get_abspath()]
204 if len(buildtarget) == 1:
208 buildtarget.append(bt)
210 if 'outdir' not in env or env['outdir'] == None:
212 elif SCons.Util.is_String(env['outdir']):
213 outdir = [env['outdir']]
214 elif SCons.Util.is_List(env['outdir']):
215 if len(env['outdir']) != len(variants):
216 raise SCons.Errors.InternalError, \
217 "Sizes of 'outdir' and 'variant' lists must be the same."
219 for s in env['outdir']:
220 if SCons.Util.is_String(s):
223 outdir.append(s.get_abspath())
225 outdir = [env['outdir'].get_abspath()]
232 if 'runfile' not in env or env['runfile'] == None:
233 runfile = buildtarget[-1:]
234 elif SCons.Util.is_String(env['runfile']):
235 runfile = [env['runfile']]
236 elif SCons.Util.is_List(env['runfile']):
237 if len(env['runfile']) != len(variants):
238 raise SCons.Errors.InternalError, \
239 "Sizes of 'runfile' and 'variant' lists must be the same."
241 for s in env['runfile']:
242 if SCons.Util.is_String(s):
245 runfile.append(s.get_abspath())
247 runfile = [env['runfile'].get_abspath()]
248 if len(runfile) == 1:
254 self.sconscript = env['MSVSSCONSCRIPT']
256 cmdargs = env.get('cmdargs', '')
260 if 'name' in self.env:
261 self.name = self.env['name']
263 self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
264 self.name = self.env.subst(self.name)
274 for n in sourcenames:
280 if 'nokeep' in env and env['variant'] != 0:
283 if self.nokeep == 0 and os.path.exists(self.dspabs):
286 for t in zip(sourcenames,self.srcargs):
288 if SCons.Util.is_List(self.env[t[1]]):
289 for i in self.env[t[1]]:
290 if not i in self.sources[t[0]]:
291 self.sources[t[0]].append(i)
293 if not self.env[t[1]] in self.sources[t[0]]:
294 self.sources[t[0]].append(self.env[t[1]])
296 for n in sourcenames:
297 self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
299 def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
301 config.buildtarget = buildtarget
302 config.outdir = outdir
303 config.cmdargs = cmdargs
304 config.runfile = runfile
306 match = re.match('(.*)\|(.*)', variant)
308 config.variant = match.group(1)
309 config.platform = match.group(2)
311 config.variant = variant
312 config.platform = 'Win32'
314 self.configs[variant] = config
315 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
317 for i in range(len(variants)):
318 AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
321 for key in self.configs.keys():
322 platform = self.configs[key].platform
323 if not platform in self.platforms:
324 self.platforms.append(platform)
330 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
331 # Microsoft Developer Studio Generated Build File, Format Version 6.00
334 # TARGTYPE "Win32 (x86) External Target" 0x0106
336 CFG=%(name)s - Win32 %(confkey)s
337 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
338 !MESSAGE use the Export Makefile command and run
340 !MESSAGE NMAKE /f "%(name)s.mak".
342 !MESSAGE You can specify a configuration when running NMAKE
343 !MESSAGE by defining the macro CFG on the command line. For example:
345 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
347 !MESSAGE Possible choices for configuration are:
351 class _GenerateV6DSP(_DSPGenerator):
352 """Generates a Project file for MSVS 6.0"""
354 def PrintHeader(self):
355 # pick a default config
356 confkeys = sorted(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 = sorted(self.configs.keys())
377 for kind in confkeys:
378 outdir = self.configs[kind].outdir
379 buildtarget = self.configs[kind].buildtarget
381 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
384 self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
386 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
387 if not env_has_buildtarget:
388 self.env['MSVSBUILDTARGET'] = buildtarget
390 # have to write this twice, once with the BASE settings, and once without
391 for base in ("BASE ",""):
392 self.file.write('# PROP %sUse_MFC 0\n'
393 '# PROP %sUse_Debug_Libraries ' % (base, base))
395 #if kind.lower().find('debug') < 0:
396 if kind.lower().find('debug') < 0:
397 self.file.write('0\n')
399 self.file.write('1\n')
400 self.file.write('# PROP %sOutput_Dir "%s"\n'
401 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
402 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
403 self.file.write('# PROP %sCmd_Line "%s"\n'
404 '# PROP %sRebuild_Opt "-c && %s"\n'
405 '# PROP %sTarget_File "%s"\n'
406 '# PROP %sBsc_Name ""\n'
407 '# PROP %sTarget_Dir ""\n'\
408 %(base,cmd,base,cmd,base,buildtarget,base,base))
410 if not env_has_buildtarget:
411 del self.env['MSVSBUILDTARGET']
413 self.file.write('\n!ENDIF\n\n'
414 '# Begin Target\n\n')
415 for kind in confkeys:
416 self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
417 self.file.write('\n')
419 for kind in confkeys:
421 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
424 self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
425 self.file.write('!ENDIF \n\n')
426 self.PrintSourceFiles()
427 self.file.write('# End Target\n'
431 # now we pickle some data and add it to the file -- MSDEV will ignore it.
432 pdata = pickle.dumps(self.configs,1)
433 pdata = base64.encodestring(pdata)
434 self.file.write(pdata + '\n')
435 pdata = pickle.dumps(self.sources,1)
436 pdata = base64.encodestring(pdata)
437 self.file.write(pdata + '\n')
439 def PrintSourceFiles(self):
440 categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
441 'Header Files': 'h|hpp|hxx|hm|inl',
442 'Local Headers': 'h|hpp|hxx|hm|inl',
443 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
446 cats = categories.keys()
447 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
449 if not self.sources[kind]:
450 continue # skip empty groups
452 self.file.write('# Begin Group "' + kind + '"\n\n')
454 #typelist = categories[kind].replace('|', ';')
455 typelist = categories[kind].replace('|', ';')
456 self.file.write('# PROP Default_Filter "' + typelist + '"\n')
458 for file in self.sources[kind]:
459 file = os.path.normpath(file)
460 self.file.write('# Begin Source File\n\n'
461 'SOURCE="' + file + '"\n'
462 '# End Source File\n')
463 self.file.write('# End Group\n')
465 # add the SConscript file outside of the groups
466 self.file.write('# Begin Source File\n\n'
467 'SOURCE="' + str(self.sconscript) + '"\n'
468 '# End Source File\n')
472 dspfile = open(self.dspabs,'r')
474 return # doesn't exist yet, so can't add anything to configs.
476 line = dspfile.readline()
479 #if line.find("# End Project") > -1:
480 if line.find("# End Project") > -1:
482 line = dspfile.readline()
484 line = dspfile.readline()
486 while line and line != '\n':
487 line = dspfile.readline()
490 # OK, we've found our little pickled cache of data.
492 datas = base64.decodestring(datas)
493 data = pickle.loads(datas)
494 except KeyboardInterrupt:
497 return # unable to unpickle any data for some reason
499 self.configs.update(data)
502 line = dspfile.readline()
504 while line and line != '\n':
505 line = dspfile.readline()
508 # OK, we've found our little pickled cache of data.
509 # it has a "# " in front of it, so we strip that.
511 datas = base64.decodestring(datas)
512 data = pickle.loads(datas)
513 except KeyboardInterrupt:
516 return # unable to unpickle any data for some reason
518 self.sources.update(data)
522 self.file = open(self.dspabs,'w')
523 except IOError, detail:
524 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
531 <?xml version="1.0" encoding = "%(encoding)s"?>
533 \tProjectType="Visual C++"
534 \tVersion="%(versionstr)s"
537 \tKeyword="MakeFileProj">
540 V7DSPConfiguration = """\
542 \t\t\tName="%(variant)s|%(platform)s"
543 \t\t\tOutputDirectory="%(outdir)s"
544 \t\t\tIntermediateDirectory="%(outdir)s"
545 \t\t\tConfigurationType="0"
547 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
549 \t\t\t\tName="VCNMakeTool"
550 \t\t\t\tBuildCommandLine="%(buildcmd)s"
551 \t\t\t\tCleanCommandLine="%(cleancmd)s"
552 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
553 \t\t\t\tOutput="%(runfile)s"/>
558 <?xml version="1.0" encoding="%(encoding)s"?>
560 \tProjectType="Visual C++"
561 \tVersion="%(versionstr)s"
564 \tRootNamespace="%(name)s"
565 \tKeyword="MakeFileProj">
568 V8DSPConfiguration = """\
570 \t\t\tName="%(variant)s|%(platform)s"
571 \t\t\tConfigurationType="0"
573 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
576 \t\t\t\tName="VCNMakeTool"
577 \t\t\t\tBuildCommandLine="%(buildcmd)s"
578 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
579 \t\t\t\tCleanCommandLine="%(cleancmd)s"
580 \t\t\t\tOutput="%(runfile)s"
581 \t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
582 \t\t\t\tIncludeSearchPath="%(includepath)s"
583 \t\t\t\tForcedIncludes=""
584 \t\t\t\tAssemblySearchPath=""
585 \t\t\t\tForcedUsingAssemblies=""
586 \t\t\t\tCompileAsManaged=""
590 class _GenerateV7DSP(_DSPGenerator):
591 """Generates a Project file for MSVS .NET"""
593 def __init__(self, dspfile, source, env):
594 _DSPGenerator.__init__(self, dspfile, source, env)
595 self.version = env['MSVS_VERSION']
596 self.version_num, self.suite = msvs_parse_version(self.version)
597 if self.version_num >= 8.0:
598 self.versionstr = '8.00'
599 self.dspheader = V8DSPHeader
600 self.dspconfiguration = V8DSPConfiguration
602 if self.version_num >= 7.1:
603 self.versionstr = '7.10'
605 self.versionstr = '7.00'
606 self.dspheader = V7DSPHeader
607 self.dspconfiguration = V7DSPConfiguration
610 def PrintHeader(self):
612 versionstr = self.versionstr
614 encoding = self.env.subst('$MSVSENCODING')
615 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
616 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
617 scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
618 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
619 project_guid = env.get('MSVS_PROJECT_GUID', '')
620 if self.version_num >= 8.0 and not project_guid:
621 project_guid = _generateGUID(self.dspfile, '')
622 if scc_provider != '':
623 scc_attrs = ('\tProjectGUID="%s"\n'
624 '\tSccProjectName="%s"\n'
625 '\tSccAuxPath="%s"\n'
626 '\tSccLocalPath="%s"\n'
627 '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
629 scc_attrs = ('\tProjectGUID="%s"\n'
630 '\tSccProjectName="%s"\n'
631 '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
633 self.file.write(self.dspheader % locals())
635 self.file.write('\t<Platforms>\n')
636 for platform in self.platforms:
639 '\t\t\tName="%s"/>\n' % platform)
640 self.file.write('\t</Platforms>\n')
642 if self.version_num >= 8.0:
643 self.file.write('\t<ToolFiles>\n'
646 def PrintProject(self):
647 self.file.write('\t<Configurations>\n')
649 confkeys = sorted(self.configs.keys())
650 for kind in confkeys:
651 variant = self.configs[kind].variant
652 platform = self.configs[kind].platform
653 outdir = self.configs[kind].outdir
654 buildtarget = self.configs[kind].buildtarget
655 runfile = self.configs[kind].runfile
656 cmdargs = self.configs[kind].cmdargs
658 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
659 if not env_has_buildtarget:
660 self.env['MSVSBUILDTARGET'] = buildtarget
662 starting = 'echo Starting SCons && '
664 cmdargs = ' ' + cmdargs
667 buildcmd = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
668 rebuildcmd = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
669 cleancmd = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
672 #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', [])))
673 #includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
674 preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
675 includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
677 if not env_has_buildtarget:
678 del self.env['MSVSBUILDTARGET']
680 self.file.write(self.dspconfiguration % locals())
682 self.file.write('\t</Configurations>\n')
684 if self.version_num >= 7.1:
685 self.file.write('\t<References>\n'
688 self.PrintSourceFiles()
690 self.file.write('</VisualStudioProject>\n')
693 # now we pickle some data and add it to the file -- MSDEV will ignore it.
694 pdata = pickle.dumps(self.configs,1)
695 pdata = base64.encodestring(pdata)
696 self.file.write('<!-- SCons Data:\n' + pdata + '\n')
697 pdata = pickle.dumps(self.sources,1)
698 pdata = base64.encodestring(pdata)
699 self.file.write(pdata + '-->\n')
701 def printSources(self, hierarchy, commonprefix):
702 sorteditems = hierarchy.items()
703 sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
705 # First folders, then files
706 for key, value in sorteditems:
707 if SCons.Util.is_Dict(value):
708 self.file.write('\t\t\t<Filter\n'
709 '\t\t\t\tName="%s"\n'
710 '\t\t\t\tFilter="">\n' % (key))
711 self.printSources(value, commonprefix)
712 self.file.write('\t\t\t</Filter>\n')
714 for key, value in sorteditems:
715 if SCons.Util.is_String(value):
718 file = os.path.join(commonprefix, value)
719 file = os.path.normpath(file)
720 self.file.write('\t\t\t<File\n'
721 '\t\t\t\tRelativePath="%s">\n'
722 '\t\t\t</File>\n' % (file))
724 def PrintSourceFiles(self):
725 categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
726 'Header Files': 'h;hpp;hxx;hm;inl',
727 'Local Headers': 'h;hpp;hxx;hm;inl',
728 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
731 self.file.write('\t<Files>\n')
733 cats = categories.keys()
734 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
735 cats = [k for k in cats if self.sources[k]]
738 self.file.write('\t\t<Filter\n'
740 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
742 sources = self.sources[kind]
744 # First remove any common prefix
747 s = list(map(os.path.normpath, sources))
748 # take the dirname because the prefix may include parts
749 # of the filenames (e.g. if you have 'dir\abcd' and
750 # 'dir\acde' then the cp will be 'dir\a' )
751 cp = os.path.dirname( os.path.commonprefix(s) )
752 if cp and s[0][len(cp)] == os.sep:
753 # +1 because the filename starts after the separator
754 sources = [s[len(cp)+1:] for s in sources]
756 elif len(sources) == 1:
757 commonprefix = os.path.dirname( sources[0] )
758 sources[0] = os.path.basename( sources[0] )
760 hierarchy = makeHierarchy(sources)
761 self.printSources(hierarchy, commonprefix=commonprefix)
764 self.file.write('\t\t</Filter>\n')
766 # add the SConscript file outside of the groups
767 self.file.write('\t\t<File\n'
768 '\t\t\tRelativePath="%s">\n'
769 '\t\t</File>\n' % str(self.sconscript))
771 self.file.write('\t</Files>\n'
777 dspfile = open(self.dspabs,'r')
779 return # doesn't exist yet, so can't add anything to configs.
781 line = dspfile.readline()
784 #if line.find('<!-- SCons Data:') > -1:
785 if line.find('<!-- SCons Data:') > -1:
787 line = dspfile.readline()
789 line = dspfile.readline()
791 while line and line != '\n':
792 line = dspfile.readline()
795 # OK, we've found our little pickled cache of data.
797 datas = base64.decodestring(datas)
798 data = pickle.loads(datas)
799 except KeyboardInterrupt:
802 return # unable to unpickle any data for some reason
804 self.configs.update(data)
807 line = dspfile.readline()
809 while line and line != '\n':
810 line = dspfile.readline()
813 # OK, we've found our little pickled cache of data.
815 datas = base64.decodestring(datas)
816 data = pickle.loads(datas)
817 except KeyboardInterrupt:
820 return # unable to unpickle any data for some reason
822 self.sources.update(data)
826 self.file = open(self.dspabs,'w')
827 except IOError, detail:
828 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
835 """ Base class for DSW generators """
836 def __init__(self, dswfile, source, env):
837 self.dswfile = os.path.normpath(str(dswfile))
840 if 'projects' not in env:
841 raise SCons.Errors.UserError, \
842 "You must specify a 'projects' argument to create an MSVSSolution."
843 projects = env['projects']
844 if not SCons.Util.is_List(projects):
845 raise SCons.Errors.InternalError, \
846 "The 'projects' argument must be a list of nodes."
847 projects = SCons.Util.flatten(projects)
848 if len(projects) < 1:
849 raise SCons.Errors.UserError, \
850 "You must specify at least one project to create an MSVSSolution."
851 self.dspfiles = list(map(str, projects))
853 if 'name' in self.env:
854 self.name = self.env['name']
856 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
857 self.name = self.env.subst(self.name)
862 class _GenerateV7DSW(_DSWGenerator):
863 """Generates a Solution file for MSVS .NET"""
864 def __init__(self, dswfile, source, env):
865 _DSWGenerator.__init__(self, dswfile, source, env)
868 self.version = self.env['MSVS_VERSION']
869 self.version_num, self.suite = msvs_parse_version(self.version)
870 self.versionstr = '7.00'
871 if self.version_num >= 8.0:
872 self.versionstr = '9.00'
873 elif self.version_num >= 7.1:
874 self.versionstr = '8.00'
875 if self.version_num >= 8.0:
876 self.versionstr = '9.00'
878 if 'slnguid' in env and env['slnguid']:
879 self.slnguid = env['slnguid']
881 self.slnguid = _generateGUID(dswfile, self.name)
886 if 'nokeep' in env and env['variant'] != 0:
889 if self.nokeep == 0 and os.path.exists(self.dswfile):
892 def AddConfig(self, variant, dswfile=dswfile):
895 match = re.match('(.*)\|(.*)', variant)
897 config.variant = match.group(1)
898 config.platform = match.group(2)
900 config.variant = variant
901 config.platform = 'Win32'
903 self.configs[variant] = config
904 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
906 if 'variant' not in env:
907 raise SCons.Errors.InternalError, \
908 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
909 "'Release') to create an MSVS Solution File."
910 elif SCons.Util.is_String(env['variant']):
911 AddConfig(self, env['variant'])
912 elif SCons.Util.is_List(env['variant']):
913 for variant in env['variant']:
914 AddConfig(self, variant)
917 for key in self.configs.keys():
918 platform = self.configs[key].platform
919 if not platform in self.platforms:
920 self.platforms.append(platform)
924 dswfile = open(self.dswfile,'r')
926 return # doesn't exist yet, so can't add anything to configs.
928 line = dswfile.readline()
930 if line[:9] == "EndGlobal":
932 line = dswfile.readline()
934 line = dswfile.readline()
937 line = dswfile.readline()
940 # OK, we've found our little pickled cache of data.
942 datas = base64.decodestring(datas)
943 data = pickle.loads(datas)
944 except KeyboardInterrupt:
947 return # unable to unpickle any data for some reason
949 self.configs.update(data)
951 def PrintSolution(self):
952 """Writes a solution file"""
953 self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
954 if self.version_num >= 8.0:
955 self.file.write('# Visual Studio 2005\n')
956 for p in self.dspfiles:
957 name = os.path.basename(p)
958 base, suffix = SCons.Util.splitext(name)
959 if suffix == '.vcproj':
961 guid = _generateGUID(p, '')
962 self.file.write('Project("%s") = "%s", "%s", "%s"\n'
963 % ( external_makefile_guid, name, p, guid ) )
964 if self.version_num >= 7.1 and self.version_num < 8.0:
965 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
966 '\tEndProjectSection\n')
967 self.file.write('EndProject\n')
969 self.file.write('Global\n')
972 if 'MSVS_SCC_PROVIDER' in env:
973 dspfile_base = os.path.basename(self.dspfile)
974 slnguid = self.slnguid
975 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
976 scc_provider = scc_provider.replace(' ', r'\u0020')
977 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
978 # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
979 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
980 scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
981 # project_guid = env.get('MSVS_PROJECT_GUID', '')
983 self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
984 '\t\tSccNumberOfProjects = 2\n'
985 '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
986 '\t\tSccLocalPath0 = %(scc_local_path)s\n'
987 '\t\tCanCheckoutShared = true\n'
988 '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
989 '\t\tSccProjectName1 = %(scc_project_name)s\n'
990 '\t\tSccLocalPath1 = %(scc_local_path)s\n'
991 '\t\tSccProvider1 = %(scc_provider)s\n'
992 '\t\tCanCheckoutShared = true\n'
993 '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
994 '\t\tSolutionUniqueID = %(slnguid)s\n'
995 '\tEndGlobalSection\n' % locals())
997 if self.version_num >= 8.0:
998 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
1000 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
1002 confkeys = sorted(self.configs.keys())
1004 for name in confkeys:
1005 variant = self.configs[name].variant
1006 platform = self.configs[name].platform
1007 if self.version_num >= 8.0:
1008 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1010 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1012 self.file.write('\tEndGlobalSection\n')
1013 if self.version_num < 7.1:
1014 self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1015 '\tEndGlobalSection\n')
1016 if self.version_num >= 8.0:
1017 self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1019 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1021 for name in confkeys:
1022 variant = self.configs[name].variant
1023 platform = self.configs[name].platform
1024 if self.version_num >= 8.0:
1025 for p in self.dspfiles:
1026 guid = _generateGUID(p, '')
1027 self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1028 '\t\t%s.%s|%s.Build.0 = %s|%s\n' % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1030 for p in self.dspfiles:
1031 guid = _generateGUID(p, '')
1032 self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1033 '\t\t%s.%s.Build.0 = %s|%s\n' %(guid,variant,variant,platform,guid,variant,variant,platform))
1035 self.file.write('\tEndGlobalSection\n')
1037 if self.version_num >= 8.0:
1038 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1039 '\t\tHideSolutionNode = FALSE\n'
1040 '\tEndGlobalSection\n')
1042 self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1043 '\tEndGlobalSection\n'
1044 '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1045 '\tEndGlobalSection\n')
1046 self.file.write('EndGlobal\n')
1047 if self.nokeep == 0:
1048 pdata = pickle.dumps(self.configs,1)
1049 pdata = base64.encodestring(pdata)
1050 self.file.write(pdata + '\n')
1054 self.file = open(self.dswfile,'w')
1055 except IOError, detail:
1056 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1058 self.PrintSolution()
1062 Microsoft Developer Studio Workspace File, Format Version 6.00
1063 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1065 ###############################################################################
1067 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1077 ###############################################################################
1089 ###############################################################################
1092 class _GenerateV6DSW(_DSWGenerator):
1093 """Generates a Workspace file for MSVS 6.0"""
1095 def PrintWorkspace(self):
1096 """ writes a DSW file """
1098 dspfile = self.dspfiles[0]
1099 self.file.write(V6DSWHeader % locals())
1103 self.file = open(self.dswfile,'w')
1104 except IOError, detail:
1105 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1107 self.PrintWorkspace()
1111 def GenerateDSP(dspfile, source, env):
1112 """Generates a Project file based on the version of MSVS that is being used"""
1115 if 'MSVS_VERSION' in env:
1116 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1117 if version_num >= 7.0:
1118 g = _GenerateV7DSP(dspfile, source, env)
1121 g = _GenerateV6DSP(dspfile, source, env)
1124 def GenerateDSW(dswfile, source, env):
1125 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1128 if 'MSVS_VERSION' in env:
1129 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1130 if version_num >= 7.0:
1131 g = _GenerateV7DSW(dswfile, source, env)
1134 g = _GenerateV6DSW(dswfile, source, env)
1138 ##############################################################################
1139 # Above here are the classes and functions for generation of
1140 # DSP/DSW/SLN/VCPROJ files.
1141 ##############################################################################
1143 def GetMSVSProjectSuffix(target, source, env, for_signature):
1144 return env['MSVS']['PROJECTSUFFIX']
1146 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1147 return env['MSVS']['SOLUTIONSUFFIX']
1149 def GenerateProject(target, source, env):
1150 # generate the dsp file, according to the version of MSVS.
1151 builddspfile = target[0]
1152 dspfile = builddspfile.srcnode()
1154 # this detects whether or not we're using a VariantDir
1155 if not dspfile is builddspfile:
1157 bdsp = open(str(builddspfile), "w+")
1158 except IOError, detail:
1159 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1162 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1164 GenerateDSP(dspfile, source, env)
1166 if env.get('auto_build_solution', 1):
1167 builddswfile = target[1]
1168 dswfile = builddswfile.srcnode()
1170 if not dswfile is builddswfile:
1173 bdsw = open(str(builddswfile), "w+")
1174 except IOError, detail:
1175 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1178 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1180 GenerateDSW(dswfile, source, env)
1182 def GenerateSolution(target, source, env):
1183 GenerateDSW(target[0], source, env)
1185 def projectEmitter(target, source, env):
1186 """Sets up the DSP dependencies."""
1188 # todo: Not sure what sets source to what user has passed as target,
1189 # but this is what happens. When that is fixed, we also won't have
1190 # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1191 if source[0] == target[0]:
1194 # make sure the suffix is correct for the version of MSVS we're running.
1195 (base, suff) = SCons.Util.splitext(str(target[0]))
1196 suff = env.subst('$MSVSPROJECTSUFFIX')
1197 target[0] = base + suff
1200 source = 'prj_inputs:'
1201 source = source + env.subst('$MSVSSCONSCOM', 1)
1202 source = source + env.subst('$MSVSENCODING', 1)
1204 if 'buildtarget' in env and env['buildtarget'] != None:
1205 if SCons.Util.is_String(env['buildtarget']):
1206 source = source + ' "%s"' % env['buildtarget']
1207 elif SCons.Util.is_List(env['buildtarget']):
1208 for bt in env['buildtarget']:
1209 if SCons.Util.is_String(bt):
1210 source = source + ' "%s"' % bt
1212 try: source = source + ' "%s"' % bt.get_abspath()
1213 except AttributeError: raise SCons.Errors.InternalError, \
1214 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1216 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1217 except AttributeError: raise SCons.Errors.InternalError, \
1218 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1220 if 'outdir' in env and env['outdir'] != None:
1221 if SCons.Util.is_String(env['outdir']):
1222 source = source + ' "%s"' % env['outdir']
1223 elif SCons.Util.is_List(env['outdir']):
1224 for s in env['outdir']:
1225 if SCons.Util.is_String(s):
1226 source = source + ' "%s"' % s
1228 try: source = source + ' "%s"' % s.get_abspath()
1229 except AttributeError: raise SCons.Errors.InternalError, \
1230 "outdir can be a string, a node, a list of strings or nodes, or None"
1232 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1233 except AttributeError: raise SCons.Errors.InternalError, \
1234 "outdir can be a string, a node, a list of strings or nodes, or None"
1237 if SCons.Util.is_String(env['name']):
1238 source = source + ' "%s"' % env['name']
1240 raise SCons.Errors.InternalError, "name must be a string"
1242 if 'variant' in env:
1243 if SCons.Util.is_String(env['variant']):
1244 source = source + ' "%s"' % env['variant']
1245 elif SCons.Util.is_List(env['variant']):
1246 for variant in env['variant']:
1247 if SCons.Util.is_String(variant):
1248 source = source + ' "%s"' % variant
1250 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1252 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1254 raise SCons.Errors.InternalError, "variant must be specified"
1256 for s in _DSPGenerator.srcargs:
1258 if SCons.Util.is_String(env[s]):
1259 source = source + ' "%s' % env[s]
1260 elif SCons.Util.is_List(env[s]):
1262 if SCons.Util.is_String(t):
1263 source = source + ' "%s"' % t
1265 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1267 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1269 source = source + ' "%s"' % str(target[0])
1270 source = [SCons.Node.Python.Value(source)]
1272 targetlist = [target[0]]
1275 if env.get('auto_build_solution', 1):
1276 env['projects'] = targetlist
1277 t, s = solutionEmitter(target, target, env)
1278 targetlist = targetlist + t
1280 return (targetlist, sourcelist)
1282 def solutionEmitter(target, source, env):
1283 """Sets up the DSW dependencies."""
1285 # todo: Not sure what sets source to what user has passed as target,
1286 # but this is what happens. When that is fixed, we also won't have
1287 # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1288 if source[0] == target[0]:
1291 # make sure the suffix is correct for the version of MSVS we're running.
1292 (base, suff) = SCons.Util.splitext(str(target[0]))
1293 suff = env.subst('$MSVSSOLUTIONSUFFIX')
1294 target[0] = base + suff
1297 source = 'sln_inputs:'
1300 if SCons.Util.is_String(env['name']):
1301 source = source + ' "%s"' % env['name']
1303 raise SCons.Errors.InternalError, "name must be a string"
1305 if 'variant' in env:
1306 if SCons.Util.is_String(env['variant']):
1307 source = source + ' "%s"' % env['variant']
1308 elif SCons.Util.is_List(env['variant']):
1309 for variant in env['variant']:
1310 if SCons.Util.is_String(variant):
1311 source = source + ' "%s"' % variant
1313 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1315 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1317 raise SCons.Errors.InternalError, "variant must be specified"
1319 if 'slnguid' in env:
1320 if SCons.Util.is_String(env['slnguid']):
1321 source = source + ' "%s"' % env['slnguid']
1323 raise SCons.Errors.InternalError, "slnguid must be a string"
1325 if 'projects' in env:
1326 if SCons.Util.is_String(env['projects']):
1327 source = source + ' "%s"' % env['projects']
1328 elif SCons.Util.is_List(env['projects']):
1329 for t in env['projects']:
1330 if SCons.Util.is_String(t):
1331 source = source + ' "%s"' % t
1333 source = source + ' "%s"' % str(target[0])
1334 source = [SCons.Node.Python.Value(source)]
1336 return ([target[0]], source)
1338 projectAction = SCons.Action.Action(GenerateProject, None)
1340 solutionAction = SCons.Action.Action(GenerateSolution, None)
1342 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1343 suffix = '$MSVSPROJECTSUFFIX',
1344 emitter = projectEmitter)
1346 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1347 suffix = '$MSVSSOLUTIONSUFFIX',
1348 emitter = solutionEmitter)
1350 default_MSVS_SConscript = None
1353 """Add Builders and construction variables for Microsoft Visual
1354 Studio project files to an Environment."""
1356 env['BUILDERS']['MSVSProject']
1358 env['BUILDERS']['MSVSProject'] = projectBuilder
1361 env['BUILDERS']['MSVSSolution']
1363 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1365 env['MSVSPROJECTCOM'] = projectAction
1366 env['MSVSSOLUTIONCOM'] = solutionAction
1368 if SCons.Script.call_stack:
1369 # XXX Need to find a way to abstract this; the build engine
1370 # shouldn't depend on anything in SCons.Script.
1371 env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1373 global default_MSVS_SConscript
1374 if default_MSVS_SConscript is None:
1375 default_MSVS_SConscript = env.File('SConstruct')
1376 env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1378 env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1379 env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1380 env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1381 env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1382 env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1383 env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1384 env['MSVSENCODING'] = 'Windows-1252'
1386 # Set-up ms tools paths for default version
1387 msvc_setup_env_once(env)
1389 if 'MSVS_VERSION' in env:
1390 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1392 (version_num, suite) = (7.0, None) # guess at a default
1393 if 'MSVS' not in env:
1395 if (version_num < 7.0):
1396 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1397 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1399 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1400 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1402 env['GET_MSVSPROJECTSUFFIX'] = GetMSVSProjectSuffix
1403 env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix
1404 env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}'
1405 env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}'
1406 env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1409 return msvc_exists()
1413 # indent-tabs-mode:nil
1415 # vim: set expandtab tabstop=4 shiftwidth=4: