3 Tool-specific initialization for Microsoft Visual Studio project files.
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
47 import SCons.Platform.win32
48 import SCons.Script.SConscript
52 from MSCommon import msvc_exists, msvc_setup_env_once
53 from SCons.Defaults import processDefines
55 ##############################################################################
56 # Below here are the classes and functions for generation of
57 # DSP/DSW/SLN/VCPROJ files.
58 ##############################################################################
61 s = s.replace("&", "&") # do this first
62 s = s.replace("'", "'")
63 s = s.replace('"', """)
66 external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
68 def _generateGUID(slnfile, name):
69 """This generates a dummy GUID for the sln file to use. It is
70 based on the MD5 signatures of the sln filename plus the name of
71 the project. It basically just needs to be unique, and not
72 change with each invocation."""
74 # Normalize the slnfile path to a Windows path (\ separators) so
75 # the generated file has a consistent GUID even if we generate
76 # it on a non-Windows platform.
77 m.update(ntpath.normpath(str(slnfile)) + str(name))
78 solution = m.hexdigest().upper()
79 # convert most of the signature to GUID form (discard the rest)
80 solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}"
83 version_re = re.compile(r'(\d+\.\d+)(.*)')
85 def msvs_parse_version(s):
87 Split a Visual Studio version, which may in fact be something like
88 '7.0Exp', into is version number (returned as a float) and trailing
91 num, suite = version_re.match(s).groups()
92 return float(num), suite
94 # This is how we re-invoke SCons from inside MSVS Project files.
95 # The problem is that we might have been invoked as either scons.bat
96 # or scons.py. If we were invoked directly as scons.py, then we could
97 # use sys.argv[0] to find the SCons "executable," but that doesn't work
98 # if we were invoked as scons.bat, which uses "python -c" to execute
99 # things and ends up with "-c" as sys.argv[0]. Consequently, we have
100 # the MSVS Project file invoke SCons the same way that scons.bat does,
101 # which works regardless of how we were invoked.
102 def getExecScriptMain(env, xml=None):
103 scons_home = env.get('SCONS_HOME')
104 if not scons_home and 'SCONS_LIB_DIR' in os.environ:
105 scons_home = os.environ['SCONS_LIB_DIR']
107 exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
109 version = SCons.__version__
110 exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%(version)s'), join(sys.prefix, 'scons-%(version)s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % locals()
112 exec_script_main = xmlify(exec_script_main)
113 return exec_script_main
115 # The string for the Python executable we tell the Project file to use
116 # is either sys.executable or, if an external PYTHON_ROOT environment
117 # variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
118 # pluck the actual executable name from sys.executable).
120 python_root = os.environ['PYTHON_ROOT']
122 python_executable = sys.executable
124 python_executable = os.path.join('$$(PYTHON_ROOT)',
125 os.path.split(sys.executable)[1])
130 def splitFully(path):
131 dir, base = os.path.split(path)
132 if dir and dir != '' and dir != path:
133 return splitFully(dir)+[base]
138 def makeHierarchy(sources):
139 '''Break a list of files into a hierarchy; for each value, if it is a string,
140 then it is a file. If it is a dictionary, it is a folder. The string is
141 the original path of the file.'''
145 path = splitFully(file)
148 for part in path[:-1]:
152 dict[path[-1]] = file
154 # print 'Warning: failed to decompose path for '+str(file)
158 """ Base class for DSP generators """
167 def __init__(self, dspfile, source, env):
168 self.dspfile = str(dspfile)
170 get_abspath = dspfile.get_abspath
171 except AttributeError:
172 self.dspabs = os.path.abspath(dspfile)
174 self.dspabs = get_abspath()
176 if 'variant' not in env:
177 raise SCons.Errors.InternalError, \
178 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
179 "'Release') to create an MSVSProject."
180 elif SCons.Util.is_String(env['variant']):
181 variants = [env['variant']]
182 elif SCons.Util.is_List(env['variant']):
183 variants = env['variant']
185 if 'buildtarget' not in env or env['buildtarget'] == None:
187 elif SCons.Util.is_String(env['buildtarget']):
188 buildtarget = [env['buildtarget']]
189 elif SCons.Util.is_List(env['buildtarget']):
190 if len(env['buildtarget']) != len(variants):
191 raise SCons.Errors.InternalError, \
192 "Sizes of 'buildtarget' and 'variant' lists must be the same."
194 for bt in env['buildtarget']:
195 if SCons.Util.is_String(bt):
196 buildtarget.append(bt)
198 buildtarget.append(bt.get_abspath())
200 buildtarget = [env['buildtarget'].get_abspath()]
201 if len(buildtarget) == 1:
205 buildtarget.append(bt)
207 if 'outdir' not in env or env['outdir'] == None:
209 elif SCons.Util.is_String(env['outdir']):
210 outdir = [env['outdir']]
211 elif SCons.Util.is_List(env['outdir']):
212 if len(env['outdir']) != len(variants):
213 raise SCons.Errors.InternalError, \
214 "Sizes of 'outdir' and 'variant' lists must be the same."
216 for s in env['outdir']:
217 if SCons.Util.is_String(s):
220 outdir.append(s.get_abspath())
222 outdir = [env['outdir'].get_abspath()]
229 if 'runfile' not in env or env['runfile'] == None:
230 runfile = buildtarget[-1:]
231 elif SCons.Util.is_String(env['runfile']):
232 runfile = [env['runfile']]
233 elif SCons.Util.is_List(env['runfile']):
234 if len(env['runfile']) != len(variants):
235 raise SCons.Errors.InternalError, \
236 "Sizes of 'runfile' and 'variant' lists must be the same."
238 for s in env['runfile']:
239 if SCons.Util.is_String(s):
242 runfile.append(s.get_abspath())
244 runfile = [env['runfile'].get_abspath()]
245 if len(runfile) == 1:
251 self.sconscript = env['MSVSSCONSCRIPT']
253 cmdargs = env.get('cmdargs', '')
257 if 'name' in self.env:
258 self.name = self.env['name']
260 self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
261 self.name = self.env.subst(self.name)
271 for n in sourcenames:
277 if 'nokeep' in env and env['variant'] != 0:
280 if self.nokeep == 0 and os.path.exists(self.dspabs):
283 for t in zip(sourcenames,self.srcargs):
285 if SCons.Util.is_List(self.env[t[1]]):
286 for i in self.env[t[1]]:
287 if not i in self.sources[t[0]]:
288 self.sources[t[0]].append(i)
290 if not self.env[t[1]] in self.sources[t[0]]:
291 self.sources[t[0]].append(self.env[t[1]])
293 for n in sourcenames:
294 self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower()))
296 def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile):
298 config.buildtarget = buildtarget
299 config.outdir = outdir
300 config.cmdargs = cmdargs
301 config.runfile = runfile
303 match = re.match('(.*)\|(.*)', variant)
305 config.variant = match.group(1)
306 config.platform = match.group(2)
308 config.variant = variant
309 config.platform = 'Win32'
311 self.configs[variant] = config
312 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'"
314 for i in range(len(variants)):
315 AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs)
318 for key in self.configs.keys():
319 platform = self.configs[key].platform
320 if not platform in self.platforms:
321 self.platforms.append(platform)
327 # Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
328 # Microsoft Developer Studio Generated Build File, Format Version 6.00
331 # TARGTYPE "Win32 (x86) External Target" 0x0106
333 CFG=%(name)s - Win32 %(confkey)s
334 !MESSAGE This is not a valid makefile. To build this project using NMAKE,
335 !MESSAGE use the Export Makefile command and run
337 !MESSAGE NMAKE /f "%(name)s.mak".
339 !MESSAGE You can specify a configuration when running NMAKE
340 !MESSAGE by defining the macro CFG on the command line. For example:
342 !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
344 !MESSAGE Possible choices for configuration are:
348 class _GenerateV6DSP(_DSPGenerator):
349 """Generates a Project file for MSVS 6.0"""
351 def PrintHeader(self):
352 # pick a default config
353 confkeys = sorted(self.configs.keys())
356 confkey = confkeys[0]
358 self.file.write(V6DSPHeader % locals())
360 for kind in confkeys:
361 self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))
363 self.file.write('!MESSAGE \n\n')
365 def PrintProject(self):
367 self.file.write('# Begin Project\n'
368 '# PROP AllowPerConfigDependencies 0\n'
369 '# PROP Scc_ProjName ""\n'
370 '# PROP Scc_LocalPath ""\n\n')
373 confkeys = sorted(self.configs.keys())
374 for kind in confkeys:
375 outdir = self.configs[kind].outdir
376 buildtarget = self.configs[kind].buildtarget
378 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
381 self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
383 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
384 if not env_has_buildtarget:
385 self.env['MSVSBUILDTARGET'] = buildtarget
387 # have to write this twice, once with the BASE settings, and once without
388 for base in ("BASE ",""):
389 self.file.write('# PROP %sUse_MFC 0\n'
390 '# PROP %sUse_Debug_Libraries ' % (base, base))
392 #if kind.lower().find('debug') < 0:
393 if kind.lower().find('debug') < 0:
394 self.file.write('0\n')
396 self.file.write('1\n')
397 self.file.write('# PROP %sOutput_Dir "%s"\n'
398 '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
399 cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
400 self.file.write('# PROP %sCmd_Line "%s"\n'
401 '# PROP %sRebuild_Opt "-c && %s"\n'
402 '# PROP %sTarget_File "%s"\n'
403 '# PROP %sBsc_Name ""\n'
404 '# PROP %sTarget_Dir ""\n'\
405 %(base,cmd,base,cmd,base,buildtarget,base,base))
407 if not env_has_buildtarget:
408 del self.env['MSVSBUILDTARGET']
410 self.file.write('\n!ENDIF\n\n'
411 '# Begin Target\n\n')
412 for kind in confkeys:
413 self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
414 self.file.write('\n')
416 for kind in confkeys:
418 self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
421 self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
422 self.file.write('!ENDIF \n\n')
423 self.PrintSourceFiles()
424 self.file.write('# End Target\n'
428 # now we pickle some data and add it to the file -- MSDEV will ignore it.
429 pdata = pickle.dumps(self.configs,1)
430 pdata = base64.encodestring(pdata)
431 self.file.write(pdata + '\n')
432 pdata = pickle.dumps(self.sources,1)
433 pdata = base64.encodestring(pdata)
434 self.file.write(pdata + '\n')
436 def PrintSourceFiles(self):
437 categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
438 'Header Files': 'h|hpp|hxx|hm|inl',
439 'Local Headers': 'h|hpp|hxx|hm|inl',
440 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
443 cats = categories.keys()
444 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
446 if not self.sources[kind]:
447 continue # skip empty groups
449 self.file.write('# Begin Group "' + kind + '"\n\n')
451 #typelist = categories[kind].replace('|', ';')
452 typelist = categories[kind].replace('|', ';')
453 self.file.write('# PROP Default_Filter "' + typelist + '"\n')
455 for file in self.sources[kind]:
456 file = os.path.normpath(file)
457 self.file.write('# Begin Source File\n\n'
458 'SOURCE="' + file + '"\n'
459 '# End Source File\n')
460 self.file.write('# End Group\n')
462 # add the SConscript file outside of the groups
463 self.file.write('# Begin Source File\n\n'
464 'SOURCE="' + str(self.sconscript) + '"\n'
465 '# End Source File\n')
469 dspfile = open(self.dspabs,'r')
471 return # doesn't exist yet, so can't add anything to configs.
473 line = dspfile.readline()
476 #if line.find("# End Project") > -1:
477 if line.find("# End Project") > -1:
479 line = dspfile.readline()
481 line = dspfile.readline()
483 while line and line != '\n':
484 line = dspfile.readline()
487 # OK, we've found our little pickled cache of data.
489 datas = base64.decodestring(datas)
490 data = pickle.loads(datas)
491 except KeyboardInterrupt:
494 return # unable to unpickle any data for some reason
496 self.configs.update(data)
499 line = dspfile.readline()
501 while line and line != '\n':
502 line = dspfile.readline()
505 # OK, we've found our little pickled cache of data.
506 # it has a "# " in front of it, so we strip that.
508 datas = base64.decodestring(datas)
509 data = pickle.loads(datas)
510 except KeyboardInterrupt:
513 return # unable to unpickle any data for some reason
515 self.sources.update(data)
519 self.file = open(self.dspabs,'w')
520 except IOError, detail:
521 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
528 <?xml version="1.0" encoding = "%(encoding)s"?>
530 \tProjectType="Visual C++"
531 \tVersion="%(versionstr)s"
534 \tKeyword="MakeFileProj">
537 V7DSPConfiguration = """\
539 \t\t\tName="%(variant)s|%(platform)s"
540 \t\t\tOutputDirectory="%(outdir)s"
541 \t\t\tIntermediateDirectory="%(outdir)s"
542 \t\t\tConfigurationType="0"
544 \t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
546 \t\t\t\tName="VCNMakeTool"
547 \t\t\t\tBuildCommandLine="%(buildcmd)s"
548 \t\t\t\tCleanCommandLine="%(cleancmd)s"
549 \t\t\t\tRebuildCommandLine="%(rebuildcmd)s"
550 \t\t\t\tOutput="%(runfile)s"/>
555 <?xml version="1.0" encoding="%(encoding)s"?>
557 \tProjectType="Visual C++"
558 \tVersion="%(versionstr)s"
561 \tRootNamespace="%(name)s"
562 \tKeyword="MakeFileProj">
565 V8DSPConfiguration = """\
567 \t\t\tName="%(variant)s|%(platform)s"
568 \t\t\tConfigurationType="0"
570 \t\t\tATLMinimizesCRunTimeLibraryUsage="false"
573 \t\t\t\tName="VCNMakeTool"
574 \t\t\t\tBuildCommandLine="%(buildcmd)s"
575 \t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
576 \t\t\t\tCleanCommandLine="%(cleancmd)s"
577 \t\t\t\tOutput="%(runfile)s"
578 \t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
579 \t\t\t\tIncludeSearchPath="%(includepath)s"
580 \t\t\t\tForcedIncludes=""
581 \t\t\t\tAssemblySearchPath=""
582 \t\t\t\tForcedUsingAssemblies=""
583 \t\t\t\tCompileAsManaged=""
587 class _GenerateV7DSP(_DSPGenerator):
588 """Generates a Project file for MSVS .NET"""
590 def __init__(self, dspfile, source, env):
591 _DSPGenerator.__init__(self, dspfile, source, env)
592 self.version = env['MSVS_VERSION']
593 self.version_num, self.suite = msvs_parse_version(self.version)
594 if self.version_num >= 8.0:
595 self.versionstr = '8.00'
596 self.dspheader = V8DSPHeader
597 self.dspconfiguration = V8DSPConfiguration
599 if self.version_num >= 7.1:
600 self.versionstr = '7.10'
602 self.versionstr = '7.00'
603 self.dspheader = V7DSPHeader
604 self.dspconfiguration = V7DSPConfiguration
607 def PrintHeader(self):
609 versionstr = self.versionstr
611 encoding = self.env.subst('$MSVSENCODING')
612 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
613 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
614 scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
615 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
616 project_guid = env.get('MSVS_PROJECT_GUID', '')
617 if self.version_num >= 8.0 and not project_guid:
618 project_guid = _generateGUID(self.dspfile, '')
619 if scc_provider != '':
620 scc_attrs = ('\tProjectGUID="%s"\n'
621 '\tSccProjectName="%s"\n'
622 '\tSccAuxPath="%s"\n'
623 '\tSccLocalPath="%s"\n'
624 '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider))
626 scc_attrs = ('\tProjectGUID="%s"\n'
627 '\tSccProjectName="%s"\n'
628 '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path))
630 self.file.write(self.dspheader % locals())
632 self.file.write('\t<Platforms>\n')
633 for platform in self.platforms:
636 '\t\t\tName="%s"/>\n' % platform)
637 self.file.write('\t</Platforms>\n')
639 if self.version_num >= 8.0:
640 self.file.write('\t<ToolFiles>\n'
643 def PrintProject(self):
644 self.file.write('\t<Configurations>\n')
646 confkeys = sorted(self.configs.keys())
647 for kind in confkeys:
648 variant = self.configs[kind].variant
649 platform = self.configs[kind].platform
650 outdir = self.configs[kind].outdir
651 buildtarget = self.configs[kind].buildtarget
652 runfile = self.configs[kind].runfile
653 cmdargs = self.configs[kind].cmdargs
655 env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
656 if not env_has_buildtarget:
657 self.env['MSVSBUILDTARGET'] = buildtarget
659 starting = 'echo Starting SCons && '
661 cmdargs = ' ' + cmdargs
664 buildcmd = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
665 rebuildcmd = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
666 cleancmd = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)
669 #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', [])))
670 #includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
671 preprocdefs = xmlify(';'.join(processDefines(self.env.get('CPPDEFINES', []))))
672 includepath = xmlify(';'.join(self.env.get('CPPPATH', [])))
674 if not env_has_buildtarget:
675 del self.env['MSVSBUILDTARGET']
677 self.file.write(self.dspconfiguration % locals())
679 self.file.write('\t</Configurations>\n')
681 if self.version_num >= 7.1:
682 self.file.write('\t<References>\n'
685 self.PrintSourceFiles()
687 self.file.write('</VisualStudioProject>\n')
690 # now we pickle some data and add it to the file -- MSDEV will ignore it.
691 pdata = pickle.dumps(self.configs,1)
692 pdata = base64.encodestring(pdata)
693 self.file.write('<!-- SCons Data:\n' + pdata + '\n')
694 pdata = pickle.dumps(self.sources,1)
695 pdata = base64.encodestring(pdata)
696 self.file.write(pdata + '-->\n')
698 def printSources(self, hierarchy, commonprefix):
699 sorteditems = hierarchy.items()
700 sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower()))
702 # First folders, then files
703 for key, value in sorteditems:
704 if SCons.Util.is_Dict(value):
705 self.file.write('\t\t\t<Filter\n'
706 '\t\t\t\tName="%s"\n'
707 '\t\t\t\tFilter="">\n' % (key))
708 self.printSources(value, commonprefix)
709 self.file.write('\t\t\t</Filter>\n')
711 for key, value in sorteditems:
712 if SCons.Util.is_String(value):
715 file = os.path.join(commonprefix, value)
716 file = os.path.normpath(file)
717 self.file.write('\t\t\t<File\n'
718 '\t\t\t\tRelativePath="%s">\n'
719 '\t\t\t</File>\n' % (file))
721 def PrintSourceFiles(self):
722 categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
723 'Header Files': 'h;hpp;hxx;hm;inl',
724 'Local Headers': 'h;hpp;hxx;hm;inl',
725 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
728 self.file.write('\t<Files>\n')
730 cats = categories.keys()
731 cats.sort(lambda a, b: cmp(a.lower(), b.lower()))
732 cats = [k for k in cats if self.sources[k]]
735 self.file.write('\t\t<Filter\n'
737 '\t\t\tFilter="%s">\n' % (kind, categories[kind]))
739 sources = self.sources[kind]
741 # First remove any common prefix
744 s = list(map(os.path.normpath, sources))
745 # take the dirname because the prefix may include parts
746 # of the filenames (e.g. if you have 'dir\abcd' and
747 # 'dir\acde' then the cp will be 'dir\a' )
748 cp = os.path.dirname( os.path.commonprefix(s) )
749 if cp and s[0][len(cp)] == os.sep:
750 # +1 because the filename starts after the separator
751 sources = [s[len(cp)+1:] for s in sources]
753 elif len(sources) == 1:
754 commonprefix = os.path.dirname( sources[0] )
755 sources[0] = os.path.basename( sources[0] )
757 hierarchy = makeHierarchy(sources)
758 self.printSources(hierarchy, commonprefix=commonprefix)
761 self.file.write('\t\t</Filter>\n')
763 # add the SConscript file outside of the groups
764 self.file.write('\t\t<File\n'
765 '\t\t\tRelativePath="%s">\n'
766 '\t\t</File>\n' % str(self.sconscript))
768 self.file.write('\t</Files>\n'
774 dspfile = open(self.dspabs,'r')
776 return # doesn't exist yet, so can't add anything to configs.
778 line = dspfile.readline()
781 #if line.find('<!-- SCons Data:') > -1:
782 if line.find('<!-- SCons Data:') > -1:
784 line = dspfile.readline()
786 line = dspfile.readline()
788 while line and line != '\n':
789 line = dspfile.readline()
792 # OK, we've found our little pickled cache of data.
794 datas = base64.decodestring(datas)
795 data = pickle.loads(datas)
796 except KeyboardInterrupt:
799 return # unable to unpickle any data for some reason
801 self.configs.update(data)
804 line = dspfile.readline()
806 while line and line != '\n':
807 line = dspfile.readline()
810 # OK, we've found our little pickled cache of data.
812 datas = base64.decodestring(datas)
813 data = pickle.loads(datas)
814 except KeyboardInterrupt:
817 return # unable to unpickle any data for some reason
819 self.sources.update(data)
823 self.file = open(self.dspabs,'w')
824 except IOError, detail:
825 raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail)
832 """ Base class for DSW generators """
833 def __init__(self, dswfile, source, env):
834 self.dswfile = os.path.normpath(str(dswfile))
837 if 'projects' not in env:
838 raise SCons.Errors.UserError, \
839 "You must specify a 'projects' argument to create an MSVSSolution."
840 projects = env['projects']
841 if not SCons.Util.is_List(projects):
842 raise SCons.Errors.InternalError, \
843 "The 'projects' argument must be a list of nodes."
844 projects = SCons.Util.flatten(projects)
845 if len(projects) < 1:
846 raise SCons.Errors.UserError, \
847 "You must specify at least one project to create an MSVSSolution."
848 self.dspfiles = list(map(str, projects))
850 if 'name' in self.env:
851 self.name = self.env['name']
853 self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
854 self.name = self.env.subst(self.name)
859 class _GenerateV7DSW(_DSWGenerator):
860 """Generates a Solution file for MSVS .NET"""
861 def __init__(self, dswfile, source, env):
862 _DSWGenerator.__init__(self, dswfile, source, env)
865 self.version = self.env['MSVS_VERSION']
866 self.version_num, self.suite = msvs_parse_version(self.version)
867 self.versionstr = '7.00'
868 if self.version_num >= 8.0:
869 self.versionstr = '9.00'
870 elif self.version_num >= 7.1:
871 self.versionstr = '8.00'
872 if self.version_num >= 8.0:
873 self.versionstr = '9.00'
875 if 'slnguid' in env and env['slnguid']:
876 self.slnguid = env['slnguid']
878 self.slnguid = _generateGUID(dswfile, self.name)
883 if 'nokeep' in env and env['variant'] != 0:
886 if self.nokeep == 0 and os.path.exists(self.dswfile):
889 def AddConfig(self, variant, dswfile=dswfile):
892 match = re.match('(.*)\|(.*)', variant)
894 config.variant = match.group(1)
895 config.platform = match.group(2)
897 config.variant = variant
898 config.platform = 'Win32'
900 self.configs[variant] = config
901 print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'"
903 if 'variant' not in env:
904 raise SCons.Errors.InternalError, \
905 "You must specify a 'variant' argument (i.e. 'Debug' or " +\
906 "'Release') to create an MSVS Solution File."
907 elif SCons.Util.is_String(env['variant']):
908 AddConfig(self, env['variant'])
909 elif SCons.Util.is_List(env['variant']):
910 for variant in env['variant']:
911 AddConfig(self, variant)
914 for key in self.configs.keys():
915 platform = self.configs[key].platform
916 if not platform in self.platforms:
917 self.platforms.append(platform)
921 dswfile = open(self.dswfile,'r')
923 return # doesn't exist yet, so can't add anything to configs.
925 line = dswfile.readline()
927 if line[:9] == "EndGlobal":
929 line = dswfile.readline()
931 line = dswfile.readline()
934 line = dswfile.readline()
937 # OK, we've found our little pickled cache of data.
939 datas = base64.decodestring(datas)
940 data = pickle.loads(datas)
941 except KeyboardInterrupt:
944 return # unable to unpickle any data for some reason
946 self.configs.update(data)
948 def PrintSolution(self):
949 """Writes a solution file"""
950 self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr )
951 if self.version_num >= 8.0:
952 self.file.write('# Visual Studio 2005\n')
953 for p in self.dspfiles:
954 name = os.path.basename(p)
955 base, suffix = SCons.Util.splitext(name)
956 if suffix == '.vcproj':
958 guid = _generateGUID(p, '')
959 self.file.write('Project("%s") = "%s", "%s", "%s"\n'
960 % ( external_makefile_guid, name, p, guid ) )
961 if self.version_num >= 7.1 and self.version_num < 8.0:
962 self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
963 '\tEndProjectSection\n')
964 self.file.write('EndProject\n')
966 self.file.write('Global\n')
969 if 'MSVS_SCC_PROVIDER' in env:
970 dspfile_base = os.path.basename(self.dspfile)
971 slnguid = self.slnguid
972 scc_provider = env.get('MSVS_SCC_PROVIDER', '')
973 scc_provider = scc_provider.replace(' ', r'\u0020')
974 scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
975 # scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
976 scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '')
977 scc_project_base_path = env.get('MSVS_SCC_PROJECT_BASE_PATH', '')
978 # project_guid = env.get('MSVS_PROJECT_GUID', '')
980 self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
981 '\t\tSccNumberOfProjects = 2\n'
982 '\t\tSccProjectUniqueName0 = %(dspfile_base)s\n'
983 '\t\tSccLocalPath0 = %(scc_local_path)s\n'
984 '\t\tCanCheckoutShared = true\n'
985 '\t\tSccProjectFilePathRelativizedFromConnection0 = %(scc_project_base_path)s\n'
986 '\t\tSccProjectName1 = %(scc_project_name)s\n'
987 '\t\tSccLocalPath1 = %(scc_local_path)s\n'
988 '\t\tSccProvider1 = %(scc_provider)s\n'
989 '\t\tCanCheckoutShared = true\n'
990 '\t\tSccProjectFilePathRelativizedFromConnection1 = %(scc_project_base_path)s\n'
991 '\t\tSolutionUniqueID = %(slnguid)s\n'
992 '\tEndGlobalSection\n' % locals())
994 if self.version_num >= 8.0:
995 self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
997 self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')
999 confkeys = sorted(self.configs.keys())
1001 for name in confkeys:
1002 variant = self.configs[name].variant
1003 platform = self.configs[name].platform
1004 if self.version_num >= 8.0:
1005 self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
1007 self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
1009 self.file.write('\tEndGlobalSection\n')
1010 if self.version_num < 7.1:
1011 self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
1012 '\tEndGlobalSection\n')
1013 if self.version_num >= 8.0:
1014 self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
1016 self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')
1018 for name in confkeys:
1019 variant = self.configs[name].variant
1020 platform = self.configs[name].platform
1021 if self.version_num >= 8.0:
1022 for p in self.dspfiles:
1023 guid = _generateGUID(p, '')
1024 self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
1025 '\t\t%s.%s|%s.Build.0 = %s|%s\n' % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
1027 for p in self.dspfiles:
1028 guid = _generateGUID(p, '')
1029 self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
1030 '\t\t%s.%s.Build.0 = %s|%s\n' %(guid,variant,variant,platform,guid,variant,variant,platform))
1032 self.file.write('\tEndGlobalSection\n')
1034 if self.version_num >= 8.0:
1035 self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
1036 '\t\tHideSolutionNode = FALSE\n'
1037 '\tEndGlobalSection\n')
1039 self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
1040 '\tEndGlobalSection\n'
1041 '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
1042 '\tEndGlobalSection\n')
1043 self.file.write('EndGlobal\n')
1044 if self.nokeep == 0:
1045 pdata = pickle.dumps(self.configs,1)
1046 pdata = base64.encodestring(pdata)
1047 self.file.write(pdata + '\n')
1051 self.file = open(self.dswfile,'w')
1052 except IOError, detail:
1053 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1055 self.PrintSolution()
1059 Microsoft Developer Studio Workspace File, Format Version 6.00
1060 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
1062 ###############################################################################
1064 Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>
1074 ###############################################################################
1086 ###############################################################################
1089 class _GenerateV6DSW(_DSWGenerator):
1090 """Generates a Workspace file for MSVS 6.0"""
1092 def PrintWorkspace(self):
1093 """ writes a DSW file """
1095 dspfile = self.dspfiles[0]
1096 self.file.write(V6DSWHeader % locals())
1100 self.file = open(self.dswfile,'w')
1101 except IOError, detail:
1102 raise SCons.Errors.InternalError, 'Unable to open "' + self.dswfile + '" for writing:' + str(detail)
1104 self.PrintWorkspace()
1108 def GenerateDSP(dspfile, source, env):
1109 """Generates a Project file based on the version of MSVS that is being used"""
1112 if 'MSVS_VERSION' in env:
1113 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1114 if version_num >= 7.0:
1115 g = _GenerateV7DSP(dspfile, source, env)
1118 g = _GenerateV6DSP(dspfile, source, env)
1121 def GenerateDSW(dswfile, source, env):
1122 """Generates a Solution/Workspace file based on the version of MSVS that is being used"""
1125 if 'MSVS_VERSION' in env:
1126 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1127 if version_num >= 7.0:
1128 g = _GenerateV7DSW(dswfile, source, env)
1131 g = _GenerateV6DSW(dswfile, source, env)
1135 ##############################################################################
1136 # Above here are the classes and functions for generation of
1137 # DSP/DSW/SLN/VCPROJ files.
1138 ##############################################################################
1140 def GetMSVSProjectSuffix(target, source, env, for_signature):
1141 return env['MSVS']['PROJECTSUFFIX']
1143 def GetMSVSSolutionSuffix(target, source, env, for_signature):
1144 return env['MSVS']['SOLUTIONSUFFIX']
1146 def GenerateProject(target, source, env):
1147 # generate the dsp file, according to the version of MSVS.
1148 builddspfile = target[0]
1149 dspfile = builddspfile.srcnode()
1151 # this detects whether or not we're using a VariantDir
1152 if not dspfile is builddspfile:
1154 bdsp = open(str(builddspfile), "w+")
1155 except IOError, detail:
1156 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1159 bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
1161 GenerateDSP(dspfile, source, env)
1163 if env.get('auto_build_solution', 1):
1164 builddswfile = target[1]
1165 dswfile = builddswfile.srcnode()
1167 if not dswfile is builddswfile:
1170 bdsw = open(str(builddswfile), "w+")
1171 except IOError, detail:
1172 print 'Unable to open "' + str(dspfile) + '" for writing:',detail,'\n'
1175 bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
1177 GenerateDSW(dswfile, source, env)
1179 def GenerateSolution(target, source, env):
1180 GenerateDSW(target[0], source, env)
1182 def projectEmitter(target, source, env):
1183 """Sets up the DSP dependencies."""
1185 # todo: Not sure what sets source to what user has passed as target,
1186 # but this is what happens. When that is fixed, we also won't have
1187 # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
1188 if source[0] == target[0]:
1191 # make sure the suffix is correct for the version of MSVS we're running.
1192 (base, suff) = SCons.Util.splitext(str(target[0]))
1193 suff = env.subst('$MSVSPROJECTSUFFIX')
1194 target[0] = base + suff
1197 source = 'prj_inputs:'
1198 source = source + env.subst('$MSVSSCONSCOM', 1)
1199 source = source + env.subst('$MSVSENCODING', 1)
1201 if 'buildtarget' in env and env['buildtarget'] != None:
1202 if SCons.Util.is_String(env['buildtarget']):
1203 source = source + ' "%s"' % env['buildtarget']
1204 elif SCons.Util.is_List(env['buildtarget']):
1205 for bt in env['buildtarget']:
1206 if SCons.Util.is_String(bt):
1207 source = source + ' "%s"' % bt
1209 try: source = source + ' "%s"' % bt.get_abspath()
1210 except AttributeError: raise SCons.Errors.InternalError, \
1211 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1213 try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
1214 except AttributeError: raise SCons.Errors.InternalError, \
1215 "buildtarget can be a string, a node, a list of strings or nodes, or None"
1217 if 'outdir' in env and env['outdir'] != None:
1218 if SCons.Util.is_String(env['outdir']):
1219 source = source + ' "%s"' % env['outdir']
1220 elif SCons.Util.is_List(env['outdir']):
1221 for s in env['outdir']:
1222 if SCons.Util.is_String(s):
1223 source = source + ' "%s"' % s
1225 try: source = source + ' "%s"' % s.get_abspath()
1226 except AttributeError: raise SCons.Errors.InternalError, \
1227 "outdir can be a string, a node, a list of strings or nodes, or None"
1229 try: source = source + ' "%s"' % env['outdir'].get_abspath()
1230 except AttributeError: raise SCons.Errors.InternalError, \
1231 "outdir can be a string, a node, a list of strings or nodes, or None"
1234 if SCons.Util.is_String(env['name']):
1235 source = source + ' "%s"' % env['name']
1237 raise SCons.Errors.InternalError, "name must be a string"
1239 if 'variant' in env:
1240 if SCons.Util.is_String(env['variant']):
1241 source = source + ' "%s"' % env['variant']
1242 elif SCons.Util.is_List(env['variant']):
1243 for variant in env['variant']:
1244 if SCons.Util.is_String(variant):
1245 source = source + ' "%s"' % variant
1247 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1249 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1251 raise SCons.Errors.InternalError, "variant must be specified"
1253 for s in _DSPGenerator.srcargs:
1255 if SCons.Util.is_String(env[s]):
1256 source = source + ' "%s' % env[s]
1257 elif SCons.Util.is_List(env[s]):
1259 if SCons.Util.is_String(t):
1260 source = source + ' "%s"' % t
1262 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1264 raise SCons.Errors.InternalError, s + " must be a string or a list of strings"
1266 source = source + ' "%s"' % str(target[0])
1267 source = [SCons.Node.Python.Value(source)]
1269 targetlist = [target[0]]
1272 if env.get('auto_build_solution', 1):
1273 env['projects'] = targetlist
1274 t, s = solutionEmitter(target, target, env)
1275 targetlist = targetlist + t
1277 return (targetlist, sourcelist)
1279 def solutionEmitter(target, source, env):
1280 """Sets up the DSW dependencies."""
1282 # todo: Not sure what sets source to what user has passed as target,
1283 # but this is what happens. When that is fixed, we also won't have
1284 # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
1285 if source[0] == target[0]:
1288 # make sure the suffix is correct for the version of MSVS we're running.
1289 (base, suff) = SCons.Util.splitext(str(target[0]))
1290 suff = env.subst('$MSVSSOLUTIONSUFFIX')
1291 target[0] = base + suff
1294 source = 'sln_inputs:'
1297 if SCons.Util.is_String(env['name']):
1298 source = source + ' "%s"' % env['name']
1300 raise SCons.Errors.InternalError, "name must be a string"
1302 if 'variant' in env:
1303 if SCons.Util.is_String(env['variant']):
1304 source = source + ' "%s"' % env['variant']
1305 elif SCons.Util.is_List(env['variant']):
1306 for variant in env['variant']:
1307 if SCons.Util.is_String(variant):
1308 source = source + ' "%s"' % variant
1310 raise SCons.Errors.InternalError, "name must be a string or a list of strings"
1312 raise SCons.Errors.InternalError, "variant must be a string or a list of strings"
1314 raise SCons.Errors.InternalError, "variant must be specified"
1316 if 'slnguid' in env:
1317 if SCons.Util.is_String(env['slnguid']):
1318 source = source + ' "%s"' % env['slnguid']
1320 raise SCons.Errors.InternalError, "slnguid must be a string"
1322 if 'projects' in env:
1323 if SCons.Util.is_String(env['projects']):
1324 source = source + ' "%s"' % env['projects']
1325 elif SCons.Util.is_List(env['projects']):
1326 for t in env['projects']:
1327 if SCons.Util.is_String(t):
1328 source = source + ' "%s"' % t
1330 source = source + ' "%s"' % str(target[0])
1331 source = [SCons.Node.Python.Value(source)]
1333 return ([target[0]], source)
1335 projectAction = SCons.Action.Action(GenerateProject, None)
1337 solutionAction = SCons.Action.Action(GenerateSolution, None)
1339 projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
1340 suffix = '$MSVSPROJECTSUFFIX',
1341 emitter = projectEmitter)
1343 solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
1344 suffix = '$MSVSSOLUTIONSUFFIX',
1345 emitter = solutionEmitter)
1347 default_MSVS_SConscript = None
1350 """Add Builders and construction variables for Microsoft Visual
1351 Studio project files to an Environment."""
1353 env['BUILDERS']['MSVSProject']
1355 env['BUILDERS']['MSVSProject'] = projectBuilder
1358 env['BUILDERS']['MSVSSolution']
1360 env['BUILDERS']['MSVSSolution'] = solutionBuilder
1362 env['MSVSPROJECTCOM'] = projectAction
1363 env['MSVSSOLUTIONCOM'] = solutionAction
1365 if SCons.Script.call_stack:
1366 # XXX Need to find a way to abstract this; the build engine
1367 # shouldn't depend on anything in SCons.Script.
1368 env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
1370 global default_MSVS_SConscript
1371 if default_MSVS_SConscript is None:
1372 default_MSVS_SConscript = env.File('SConstruct')
1373 env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
1375 env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
1376 env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
1377 env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
1378 env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1379 env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
1380 env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'
1381 env['MSVSENCODING'] = 'Windows-1252'
1383 # Set-up ms tools paths for default version
1384 msvc_setup_env_once(env)
1386 if 'MSVS_VERSION' in env:
1387 version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
1389 (version_num, suite) = (7.0, None) # guess at a default
1390 if 'MSVS' not in env:
1392 if (version_num < 7.0):
1393 env['MSVS']['PROJECTSUFFIX'] = '.dsp'
1394 env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
1396 env['MSVS']['PROJECTSUFFIX'] = '.vcproj'
1397 env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
1399 env['GET_MSVSPROJECTSUFFIX'] = GetMSVSProjectSuffix
1400 env['GET_MSVSSOLUTIONSUFFIX'] = GetMSVSSolutionSuffix
1401 env['MSVSPROJECTSUFFIX'] = '${GET_MSVSPROJECTSUFFIX}'
1402 env['MSVSSOLUTIONSUFFIX'] = '${GET_MSVSSOLUTIONSUFFIX}'
1403 env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
1406 return msvc_exists()
1410 # indent-tabs-mode:nil
1412 # vim: set expandtab tabstop=4 shiftwidth=4: