1#
2#  This file is part of Bakefile (http://www.bakefile.org)
3#
4#  Copyright (C) 2006-2008 Vaclav Slavik, Kevin Powell, Steven Van Ingelgem,
5#                          Kevin Ollivier, Aleksander Jaromin
6#
7#  Permission is hereby granted, free of charge, to any person obtaining a copy
8#  of this software and associated documentation files (the "Software"), to
9#  deal in the Software without restriction, including without limitation the
10#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11#  sell copies of the Software, and to permit persons to whom the Software is
12#  furnished to do so, subject to the following conditions:
13#
14#  The above copyright notice and this permission notice shall be included in
15#  all copies or substantial portions of the Software.
16#
17#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23#  IN THE SOFTWARE.
24#
25#  $Id: msvs200xprj.py 1345 2011-02-01 14:02:34Z vaclavslavik $
26#
27#  MS Visual Studio 2003/2005 project files generator script
28#
29
30import os, os.path
31import errors, utils
32from xml.dom.minidom import *
33
34import mk
35import msvc_common
36from msvc_common import *
37
38# ------------------------------------------------------------------------
39#   misc .vcproj constants:
40# ------------------------------------------------------------------------
41
42# compiler options
43
44rtMultiThreaded         = '0'
45rtMultiThreadedDebug    = '1'
46rtMultiThreadedDLL      = '2'
47rtMultiThreadedDebugDLL = '3'
48rtSingleThreaded        = '4'
49rtSingleThreadedDebug   = '5'
50
51debugDisabled           = '0'
52debugOldStyleInfo       = '1'
53debugLineInfoOnly       = '2'
54debugEnabled            = '3'
55debugEditAndContinue    = '4'
56
57runtimeBasicCheckNone       = '0'
58runtimeCheckStackFrame      = '1'
59runtimeCheckUninitVariables = '2'
60runtimeBasicCheckAll        = '3'
61
62optimizeDisabled        = '0'
63optimizeMinSpace        = '1'
64optimizeMaxSpeed        = '2'
65optimizeFull            = '3'
66optimizeCustom          = '4'
67
68pchNone                 = '0'
69pchCreateUsingSpecific  = '1'
70
71# linker options
72
73linkIncrementalDefault  = '0'
74linkIncrementalNo       = '1'
75linkIncrementalYes      = '2'
76
77optReferencesDefault    = '0'
78optNoReferences         = '1'
79optReferences           = '2'
80
81machineX86              = '1'
82machineARM              = '3'
83machineAMD64            = '17'
84
85
86# ------------------------------------------------------------------------
87#   extended minidom classes to create XML file with specified attributes
88#   order (VC2003 projet file must have attribute "Name" as first)
89# ------------------------------------------------------------------------
90
91class ElementSorted(Element):
92    def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE,
93                 prefix=None, localName=None):
94        Element.__init__(self, tagName, namespaceURI, prefix, localName)
95        self.order = []
96
97    def setAttribute(self, attname, value):
98        self.order.append(attname)
99        Element.setAttribute(self, attname, value)
100
101    def writexml(self, writer, indent="", addindent="", newl=""):
102        # This specialization does two things differently:
103        # 1) order of attributes is preserved
104        # 2) attributes are placed each on its own line and indented
105        #    so that the output looks more like MSVC's native files
106        writer.write("%s<%s" % (indent, self.tagName))
107
108        attrs = self._get_attributes()
109
110        for attr in self.order:
111            writer.write("\n%s%s%s=\"%s\"" % (
112                         indent,
113                         addindent,
114                         attr,
115                         attrs[attr].value.replace('&', '&amp;').
116                                           replace('<', '&lt;').
117                                           replace('>', '&gt;').
118                                           replace('"', '&quot;')))
119
120        if self.childNodes:
121            if len(self.order) == 0:
122                writer.write(">%s" % newl)
123            else:
124                if _MSVS_VCPROJ_VERSION == "7.10":
125                    writer.write(">%s" % newl)
126                else:
127                    writer.write("%s%s%s>%s" % (newl, indent, addindent, newl))
128            for node in self.childNodes:
129                node.writexml(writer, indent + addindent, addindent, newl)
130            writer.write("%s</%s>%s" % (indent, self.tagName, newl))
131        else:
132            # <File> is serialized differently, see the discussion in
133            # http://www.bakefile.org/ticket/210
134            if self.tagName == "File":
135                if _MSVS_VCPROJ_VERSION == "7.10":
136                    writer.write(">%s%s</%s>%s" % (newl, indent, self.tagName, newl))
137                else:
138                    writer.write("%s%s%s>%s%s</%s>%s" % (newl, indent, addindent, newl, indent, self.tagName, newl))
139            else:
140                if _MSVS_VCPROJ_VERSION == "7.10":
141                    writer.write("/>%s" % newl)
142                else:
143                    writer.write("%s%s/>%s" % (newl, indent, newl))
144
145
146class DocumentSorted(Document):
147    def createElement(self, tagName):
148        e = ElementSorted(tagName)
149        e.ownerDocument = self
150        return e
151
152
153class VerbatimFragment(Text):
154    def __init__(self, markup):
155        self.markup = markup
156    def writexml(self, writer, indent="", addindent="", newl=""):
157        writer.write(indent)
158        writer.write(self.markup)
159        writer.write(newl)
160
161
162# ------------------------------------------------------------------------
163#   helpers
164# ------------------------------------------------------------------------
165
166# this GUID is used by all solutions in the Project() entry:
167GUID_SOLUTION = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"
168
169# UUID for Bakefile namespace when generating GUIDs
170NAMESPACE_BAKEFILE_PROJ = "{b0c737d9-df87-4499-b156-418baa078a12}"
171NAMESPACE_BAKEFILE_FILTERS = "{0e8f53b3-f09d-40b1-b248-66f80b72e654}"
172
173def mk_uuid(namespace, seed):
174    # NB: we want to have the GUID strings be repeatable, so generate them
175    #     from a repeatable seed
176    from uuid import uuid5, UUID
177
178    guid = uuid5(UUID(namespace), seed)
179    return '{%s}' % str(guid).upper() # MSVS uses upper-case strings for GUIDs
180
181def mk_proj_uuid(basename, proj_id):
182    return mk_uuid(NAMESPACE_BAKEFILE_PROJ, '%s/%s' % (basename, proj_id))
183
184def mk_filter_uuid(name, wildcards):
185    return mk_uuid(NAMESPACE_BAKEFILE_FILTERS, '%s:%s' % (name, wildcards))
186
187class MsvsFilesGroup(FilesGroup):
188    def __init__(self, name, files=None, extensions=None, uuid=None):
189        assert files != None or extensions != None
190        if files == None:
191            files = ' '.join(['*.%s' % x for x in extensions.split(';')])
192        if uuid == None:
193            uuid = mk_filter_uuid(name, files)
194
195        FilesGroup.__init__(self, name, files)
196        self.uuid = uuid
197        self.extensions = extensions
198
199SOURCE_FILES_EXTENSIONS = [
200            'cpp','c','cc','cxx','def','odl','idl','hpj','bat','asm','asmx']
201
202DEFAULT_FILE_GROUPS = [
203    MsvsFilesGroup('Source Files',
204                   extensions=';'.join(SOURCE_FILES_EXTENSIONS),
205                   uuid='{4FC737F1-C7A5-4376-A066-2A32D752A2FF}'),
206    MsvsFilesGroup('Header Files',
207                   extensions='h;hpp;hxx;hm;inl;inc;xsd',
208                   uuid='{93995380-89BD-4b04-88EB-625FBE52EBFB}'),
209    MsvsFilesGroup('Resource Files',
210                   extensions='rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav',
211                   uuid='{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}')
212]
213
214def mk_list(list):
215    # remove empty items from the list:
216    return ";".join([x for x in list.split(";") if len(x) > 0])
217
218def mk_listsp(list):
219    # remove empty items from the list:
220    return ";".join([x for x in list.split(" ") if len(x) > 0])
221
222def bool2vcstr(value):
223    """Returns "true"/"false" or "TRUE"/"FALSE" string depending on format
224       version."""
225    if value:
226        s = 'true'
227    else:
228        s = 'false'
229    if _MSVS_VCPROJ_VERSION == "7.10":
230        return s.upper()
231    else:
232        return s
233
234
235def isSourceFile(filename):
236    """Is this file a source file (and not e.g. a header)?"""
237    ext = filename.split('.')[-1]
238    return ext in SOURCE_FILES_EXTENSIONS
239
240
241def findConflictingNames(sources):
242    """Finds source files that have conflicting basenames, i.e. that would be
243       compiled into the same file without special handling."""
244    all_bases = {}
245    for s in sources:
246        base = s.split('\\')[-1]
247        all_bases.setdefault(base, [])
248        all_bases[base].append(s)
249    conflict_bases = [i for i in all_bases if len(all_bases[i]) > 1]
250    conflict_names = []
251    for x in conflict_bases:
252        conflict_names += all_bases[x]
253    return conflict_names
254
255
256# ------------------------------------------------------------------------
257#                              Generator class
258# ------------------------------------------------------------------------
259
260class ProjectGeneratorMsvc9:
261
262    app_type_code = { 'console' : '1', 'gui' : '2' }
263
264    def __init__(self):
265        self.basename, ext = os.path.splitext(os.path.basename(FILE))
266        self.dirname = os.path.dirname(FILE)
267        if ext.lower() == '.vcproj':
268            self.onlyProject = True
269        else:
270            if ext.lower() != '.sln' and ext != '':
271                print 'warning: ignoring extension "%s"' % ext[1:] # drop dot
272            self.onlyProject = False
273
274        self.fragments = {}
275        for f in mk.fragments:
276            prj, filename = f.location.split(':')
277            filename = filename.replace('/', '\\')
278            self.fragments[(prj, filename)] = f.content
279
280
281    # --------------------------------------------------------------------
282    #   basic configuration
283    # --------------------------------------------------------------------
284
285    def getSolutionExtension(self):
286        return 'sln'
287    def getProjectExtension(self):
288        return 'vcproj'
289
290    # --------------------------------------------------------------------
291    #   helpers
292    # --------------------------------------------------------------------
293
294    def splitConfigName(self, config):
295        idx = config.index('|')
296        platform = config[:idx]
297        name = config[idx+2:]
298        if name == '':
299            name = 'Default'
300        return (platform, name)
301
302    def mkConfigName(self, config):
303        # config name always contains the platform followed by "| " at the
304        # beginning of the string, so extract it and transform into
305        # "ConfigName|Platform":
306        platform, name = self.splitConfigName(config)
307        return '%s|%s' % (name, platform)
308
309    def sortConfigsForSLN(self, configs):
310        # .sln files have configs grouped, all platforms for one config
311        # are together, but our "natural" sort order is different:
312        sortedConfigs = []
313        todo = [c for c in sortedConfigKeys(configs)]
314        while len(todo) > 0:
315            c = todo.pop(0)
316            if c in sortedConfigs:
317                continue
318            plat, nm = self.splitConfigName(c)
319            sortedConfigs.append(c)
320            for c2 in todo:
321                if c2 in sortedConfigs:
322                    continue
323                plat2, nm2 = self.splitConfigName(c2)
324                if nm == nm2:
325                    sortedConfigs.append(c2)
326        return sortedConfigs
327
328    def isEmbeddedConfig(self, config):
329        """Returns true if given config targets embedded device."""
330        cfg = configs[config][0]['MSVS_PLATFORM']
331        return cfg == 'pocketpc2003'
332
333    def isX64Config(self, config):
334        """Returns true if given config targets a 64bit system."""
335        cfg = configs[config][0]['MSVS_PLATFORM']
336        return cfg == 'win64'
337
338
339    def assignGUIDs(self, sln_targets):
340        """Returns the dictionary containing GUIDs indexed by target ids."""
341        guid_dict = {}
342        for t in sln_targets:
343            if '_msvc_guid' in t.__dict__:
344                guid_dict[t.id] = '{%s}' % t._msvc_guid.upper()
345            else:
346                guid_dict[t.id] = mk_proj_uuid(self.basename, t.id)
347        return guid_dict
348
349    def createVCProjList(self, sln_targets, guid_dict, vcproj_list, deps_translation):
350        """Fills the project list and also returns the SLN projects sections as
351           a side effect."""
352        sln = ''
353        single_target = (len(sln_targets) == 1)
354        for t in sln_targets:
355            deps = ''
356            if 'MSVS_PROJECT_FILE' in t.__dict__:
357                vcproj_name = t.MSVS_PROJECT_FILE
358            elif single_target:
359                vcproj_name = self.basename + '.' + self.getProjectExtension()
360            else:
361                vcproj_name = '%s_%s.%s' % (self.basename, t.id, self.getProjectExtension())
362            deplist = t._deps.split()
363
364            # add external project dependencies:
365            for d in t._dsp_deps.split():
366                deplist.append(d.split(':')[0])
367
368            # create the dependencies section, if any:
369            deps_str = ""
370            if len(deplist) != 0:
371                deps_str = "\tProjectSection(ProjectDependencies) = postProject\n"
372
373                for d in deplist:
374                    if d in deps_translation:
375                        d2 = deps_translation[d]
376                    else:
377                        d2 = d
378                    guid = guid_dict[d2]
379                    deps_str += "\t\t%s = %s\n" % (guid, guid)
380                deps_str += "\tEndProjectSection\n"
381
382            guid = guid_dict[t.id]
383
384            # create the project section:
385            sln += 'Project("%s") = "%s", "%s", "%s"\n' % (
386                        GUID_SOLUTION,
387                        t.id,
388                        vcproj_name,
389                        guid
390                    )
391            sln += deps_str
392            sln += 'EndProject\n'
393
394            vcproj_file = (t,
395                           os.path.join(self.dirname, vcproj_name),
396                           os.path.basename(os.path.splitext(vcproj_name)[0]),
397                           guid)
398
399            if vcproj_file not in vcproj_list:
400                vcproj_list.append(vcproj_file)
401
402        return sln
403
404
405    # --------------------------------------------------------------------
406    #   Solution file (.sln)
407    # --------------------------------------------------------------------
408
409    def makeSlnHeader(self):
410        if _MSVS_SLN_VERSION == "8.00":
411            return """\
412Microsoft Visual Studio Solution File, Format Version 8.00
413"""
414        elif _MSVS_SLN_VERSION == "9.00":
415            return """\
416Microsoft Visual Studio Solution File, Format Version 9.00
417# Visual Studio 2005
418"""
419        elif _MSVS_SLN_VERSION == "10.00":
420            return """\
421Microsoft Visual Studio Solution File, Format Version 10.00
422# Visual Studio 2008
423"""
424        else:
425            import errors
426            raise errors.Error("unexpected MSVS format version")
427
428    def genSln(self, sln_targets, guid_dict, vcproj_list, deps_translation):
429        if len(sln_targets) == 0:
430            return
431
432        sln = self.makeSlnHeader()
433
434        sln += self.createVCProjList(sln_targets, guid_dict, vcproj_list, deps_translation)
435
436        sortedConfigs = self.sortConfigsForSLN(configs)
437
438        # generate configurations listing:
439        sln += "Global\n"
440        sln += "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"
441        for c in sortedConfigs:
442            cfg = self.mkConfigName(c)
443            sln += "\t\t%s = %s\n" % (cfg,cfg)
444        sln += "\tEndGlobalSection\n"
445
446        # ...and configurations binding to vcproj configurations:
447        sln += "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"
448
449        for t in sln_targets:
450            guid = guid_dict[t.id]
451            for c in sortedConfigs:
452                cfg = self.mkConfigName(c)
453                txt = "\t\t%(guid)s.%(cfg)s.%%s = %(cfg)s\n" % {'guid':guid, 'cfg':cfg}
454                sln += txt % 'ActiveCfg'
455                if c in t.configs:
456                    sln += txt % 'Build.0'
457                    if self.isEmbeddedConfig(c):
458                        sln += txt % 'Deploy.0'
459
460
461        sln += "\tEndGlobalSection\n"
462        sln += "\tGlobalSection(SolutionProperties) = preSolution\n"
463        sln += "\t\tHideSolutionNode = FALSE\n"
464        sln += "\tEndGlobalSection\n"
465        sln += "EndGlobal\n"
466
467        writer.writeFile('%s.%s' % (
468            os.path.join(self.dirname, self.basename),
469            self.getSolutionExtension()
470            ), sln)
471
472
473    def genWorkspaces(self):
474        vcproj_list = []
475
476        # find all projects. Beware ugly hack here: MSVC6PRJ_MERGED_TARGETS is
477        # used to create fake targets as a merge of two (mutually exclusive)
478        # targets. This is sometimes useful, e.g. when you want to build both
479        # DLL and static lib of something.
480        deps_translation = {}
481        projects = [t for t in targets if t._kind == 'project']
482        for mergeInfo in MSVC6PRJ_MERGED_TARGETS.split():
483            split1 = mergeInfo.split('=')
484            split2 = split1[1].split('+')
485            tgR = split1[0]
486            tg1 = split2[0]
487            tg2 = split2[1]
488
489            # the targets may be disabled by some (weak) condition:
490            if tg1 not in targets and tg2 not in targets:
491                continue
492
493            t = targets[tg1]
494            for c in targets[tg2].configs:
495                assert c not in t.configs # otherwise not mutually exclusive
496                t.configs[c] = targets[tg2].configs[c]
497            t.id = tgR
498            projects.remove(targets[tg2])
499            targets.append(tgR, t)
500            del targets[tg1]
501            del targets[tg2]
502            deps_translation[tg1] = tgR
503            deps_translation[tg2] = tgR
504
505        guid_dict = self.assignGUIDs(projects)
506        if self.onlyProject:
507            self.createVCProjList(projects, guid_dict, vcproj_list, deps_translation)
508            if len(vcproj_list) != 1:
509                raise errors.Error("Single project name specified for multiple targets.")
510        else:
511            self.genSln(projects, guid_dict, vcproj_list, deps_translation)
512        for t, filename, prjname, guid in vcproj_list:
513            self.genVCProj(t, filename, prjname, guid)
514
515        # warn about <action> targets that we can't handle (yet):
516        for t in [t for t in targets if t._kind == 'action']:
517            print "warning: ignoring action target '%s'" % t.id
518
519
520    # ------------------------------------------------------------------------
521    #   Project files (.vcproj)
522    # ------------------------------------------------------------------------
523
524    # some helpers to build parts of the project file that change if you are
525    # doing PC vs WinCE
526    def buildConfElement(self, doc, cfg, c, t):
527        conf_name = self.mkConfigName(c)
528        conf_el = doc.createElement("Configuration")
529        conf_el.setAttribute("Name", conf_name)
530        conf_el.setAttribute("OutputDirectory", "%s" % cfg._targetdir[:-1])
531        conf_el.setAttribute("IntermediateDirectory", "%s\\%s" % (cfg._builddir, t.id) )
532        conf_el.setAttribute("ConfigurationType", "%s" % cfg._type_code)
533        conf_el.setAttribute("UseOfMFC", "0")
534        conf_el.setAttribute("ATLMinimizesCRunTimeLibraryUsage", bool2vcstr(False))
535
536        # allow specifying CharacterSet value, it's not really clear what is
537        # the difference between specifying it as 0 (not specified), 2 (MBCS)
538        # or not using it at all, let the user decide
539        if globals().has_key('MSVS_CHARACTER_SET'):
540            conf_el.setAttribute("CharacterSet", MSVS_CHARACTER_SET)
541        # VC++ 2008 needs CharacterSet set, definining _UNICODE is not enough,
542        # see http://comments.gmane.org/gmane.comp.sysutils.bakefile.devel/1145
543        elif "_UNICODE" in cfg._defines.split(";"):
544            conf_el.setAttribute("CharacterSet", "1")
545
546        return conf_el
547
548
549    def buildBscMakeToolElement(self, tool, prjname, cfg, c, t):
550        tool.setAttribute("OutputFile", "%s%s.bsc" % (cfg._targetdir, prjname))
551        tool.setAttribute("SuppressStartupBanner", bool2vcstr(True))
552
553
554    def buildCompilerToolElement(self, tool, prjname, cfg, c, t):
555        # the order of attributes here is the same as used in the projects
556        # created by MSVS itself
557
558        if _MSVS_VCPROJ_VERSION not in ["7.10", "8.00"]:
559            extra_cppflags = "/MP" # parallel compilation
560            if cfg._cppflags:
561                extra_cppflags = "%s %s" % (extra_cppflags, cfg._cppflags)
562        else:
563            extra_cppflags = cfg._cppflags
564        if extra_cppflags:
565            tool.setAttribute("AdditionalOptions", extra_cppflags)
566
567        tool.setAttribute("Optimization", cfg._optimize)
568        tool.setAttribute("AdditionalIncludeDirectories", mk_list(cfg._include_paths))
569        tool.setAttribute("PreprocessorDefinitions", mk_list(cfg._defines))
570
571        if cfg._optimize == "0" and cfg._rtl_dbg == 'on':
572            # VC2008 projects use /MP switch, which is incompatible with
573            # MinimalRebuild; /MP is more useful, so omit MinimalRebuild:
574            if _MSVS_VCPROJ_VERSION in ["7.10", "8.00"]:
575                tool.setAttribute("MinimalRebuild", bool2vcstr(True))
576
577        # this property type has changed from int to bool since VC7
578        eh = cfg._cxx_exceptions == 'on'
579        if _MSVS_VCPROJ_VERSION == "7.10":
580            eh = bool2vcstr(eh)     # True/False -> 'TRUE'/'FALSE'
581        else:
582            eh = str(int(eh))       # True/False -> '1'/'0'
583        tool.setAttribute("ExceptionHandling", eh)
584
585        if cfg._rtl_dbg == 'on':
586            tool.setAttribute("BasicRuntimeChecks", runtimeBasicCheckAll)
587
588        # choose runtime library
589        if cfg._rtl_threading == 'multi':
590            if cfg._rtl_dbg == 'on':
591                if cfg._rtl_type == 'static':
592                    rtl = rtMultiThreadedDebug
593                else:
594                    rtl = rtMultiThreadedDebugDLL
595            else: # debug off
596                if cfg._rtl_type == 'static':
597                    rtl = rtMultiThreaded
598                else:
599                    rtl = rtMultiThreadedDLL
600        else: # single-threaded
601            if cfg._rtl_type == 'dynamic':
602                print "warning: single-threaded dynamic runtime doesn't exist, using static"
603            if cfg._rtl_dbg == 'on':
604                rtl = rtSingleThreadedDebug
605            else:
606                rtl = rtSingleThreaded
607        tool.setAttribute("RuntimeLibrary", rtl)
608
609        if cfg._optimize == "0":
610            tool.setAttribute("BufferSecurityCheck",bool2vcstr(True))
611
612        if cfg._cxx_rtti == 'on':
613            tool.setAttribute("RuntimeTypeInfo", bool2vcstr(True))
614        else:
615            tool.setAttribute("RuntimeTypeInfo", bool2vcstr(False))
616
617        # the values of this enum changed in VC8 where pchGenerateAuto simply
618        # disappeared (and so the value of subsequent element shifted), so
619        # define separate constants for VC7 ...
620        if _MSVS_VCPROJ_VERSION == "7.10":
621            pchGenerateAuto     = '2'
622            pchUseUsingSpecific = '3'
623        else:
624        # ... and for the later versions
625            pchUseUsingSpecific = '2'
626
627        if cfg._pch_use_pch == '1':
628            do_use_pch = True
629            if cfg._pch_generator:
630                tool.setAttribute("UsePrecompiledHeader", pchUseUsingSpecific)
631            else:
632                if _MSVS_VCPROJ_VERSION == "7.10":
633                    tool.setAttribute("UsePrecompiledHeader", pchGenerateAuto)
634                else:
635                    # automatic PCH support (/YX option) was removed in VC8, so
636                    # disable the use of PCH completely when this option is
637                    # specified (what else can we do?)
638                    #
639                    # FIXME: instead, we could pick a source file that includes
640                    #        _pch_header (if specified) at random and use that;
641                    #        or at least issue a warning
642                    do_use_pch = False
643
644            if do_use_pch:
645                tool.setAttribute("PrecompiledHeaderThrough", cfg._pch_header)
646                if cfg._pch_file:
647                    tool.setAttribute("PrecompiledHeaderFile",
648                                      "%s\\%s" % (cfg._builddir, cfg._pch_file))
649
650
651        tool.setAttribute("ObjectFile", "%s\\%s\\" % (cfg._builddir, t.id) )
652        tool.setAttribute("ProgramDataBaseFileName", cfg._pdbfile)
653
654        warnings_map = { 'no':'0', 'default':'3', 'max':'4'}
655        if warnings_map.has_key(cfg._warnings):
656            tool.setAttribute("WarningLevel", warnings_map[cfg._warnings])
657        else:
658            tool.setAttribute("WarningLevel", warnings_map['default'])
659
660        tool.setAttribute("SuppressStartupBanner", bool2vcstr(True))
661
662        # Detect64BitPortabilityProblems is deprecated in VS2008 and will
663        # be removed in the future:
664        if _MSVS_VCPROJ_VERSION in ["7.10", "8.00"]:
665            tool.setAttribute("Detect64BitPortabilityProblems", bool2vcstr(True))
666
667        if cfg._debug == '1':
668            if cfg._debug_edit_and_continue == '1':
669                tool.setAttribute("DebugInformationFormat", debugEditAndContinue)
670            else:
671                tool.setAttribute("DebugInformationFormat", debugEnabled)
672        else:
673            tool.setAttribute("DebugInformationFormat", debugDisabled)
674
675
676    def buildLinkerToolElement(self, tool, prjname, cfg, c, t):
677        ldlibs = cfg._ldlibs
678        if cfg._cxx_rtti == 'on' and self.isEmbeddedConfig(c):
679            ldlibs += ' ccrtrtti.lib'
680
681        tool.setAttribute("AdditionalOptions", cfg._ldflags)
682        tool.setAttribute("AdditionalDependencies", ldlibs)
683        tool.setAttribute("OutputFile", "%s%s" % (cfg._targetdir, cfg._targetname))
684
685        if cfg._rtl_dbg == 'on':
686            tool.setAttribute("LinkIncremental", linkIncrementalYes)
687        else:
688            tool.setAttribute("LinkIncremental", linkIncrementalNo)
689
690        if cfg._type_nick == 'dll':
691            if cfg._importlib != "":
692                implib = cfg._importlib
693                tool.setAttribute("ImportLibrary",
694                                 "%s%s" % (cfg._targetdir, implib))
695
696        tool.setAttribute("SuppressStartupBanner", bool2vcstr(True))
697        tool.setAttribute("AdditionalLibraryDirectories", mk_list(cfg._lib_paths))
698        if _MSVS_VCPROJ_VERSION != "7.10":
699            tool.setAttribute("GenerateManifest", bool2vcstr(True))
700
701        if cfg._debug == '1':
702            tool.setAttribute("GenerateDebugInformation", bool2vcstr(True))
703
704        tool.setAttribute("ProgramDatabaseFile", cfg._pdbfile)
705
706        if cfg._type_nick != 'dll':
707            tool.setAttribute("SubSystem",
708                             "%s" % self.app_type_code[cfg._type_nick])
709
710        if self.isEmbeddedConfig(c):
711            tool.setAttribute("TargetMachine", machineARM)
712            tool.setAttribute("DelayLoadDLLs", "$(NOINHERIT)")
713            if cfg._rtl_dbg == 'off':
714                tool.setAttribute("OptimizeReferences", optReferences)
715                tool.setAttribute("EnableCOMDATFolding", "2")
716        else:
717            if self.isX64Config(c):
718                tool.setAttribute("TargetMachine", machineAMD64)
719            else:
720                tool.setAttribute("TargetMachine", machineX86)
721            # optimize linking unless we're using debug RTL (in which case we
722            # don't build production version anyhow) or we don't use /debug at
723            # all (in which case it's optimized by default)
724            if cfg._rtl_dbg == 'off' and cfg._debug == '1':
725                tool.setAttribute("OptimizeReferences", optReferences)
726                tool.setAttribute("EnableCOMDATFolding", "2")
727
728
729    def buildLibrarianToolElement(self, tool, prjname, cfg, c, t):
730        tool.setAttribute("OutputFile","%s%s" % (cfg._targetdir, cfg._targetname) )
731        tool.setAttribute("SuppressStartupBanner", bool2vcstr(True))
732
733
734    def buildResourceCompilerToolElement(self, tool, prjname, cfg, c, t):
735        tool.setAttribute("PreprocessorDefinitions", mk_list(cfg._res_defines))
736        tool.setAttribute("Culture", "1033")
737        tool.setAttribute("AdditionalIncludeDirectories", mk_list(cfg._res_include_paths))
738
739
740    def buildIdlToolElement(self, tool, prjname, cfg, c, t):
741        # MIDL compiler has a bug in it that prevents quoted values in
742        # PreprocessorDefinitions from being passed correctly to the compiler.
743        # We work around it by using AdditionalOptions.
744        # See http://www.vtk.org/Bug/view.php?id=8165 for the gory details.
745        all_defines = [x for x in cfg._defines.split(";") if len(x) > 0]
746        extras = []
747        simple_defines = []
748        for d in all_defines:
749            if '"' in d:
750                extras.append("/D %s" % d)
751            else:
752                simple_defines.append(d)
753        if extras:
754            tool.setAttribute("AdditionalOptions", " ".join(extras))
755        tool.setAttribute("PreprocessorDefinitions", ";".join(simple_defines))
756        tool.setAttribute("AdditionalIncludeDirectories", mk_list(cfg._include_paths))
757
758
759    def buildPlatformsElement(self, doc):
760        #Platforms Node
761        plats_el = doc.createElement("Platforms")
762        for p in MSVS_PLATFORMS_DESC.split(','):
763            plat_el = doc.createElement("Platform")
764            plat_el.setAttribute("Name", p)
765            plats_el.appendChild(plat_el)
766        return plats_el
767
768    def buildToolFilesElement(self, doc):
769        #ToolFiles Node
770        tf_el = doc.createElement("ToolFiles")
771        tf_el.appendChild(doc.createTextNode(""))
772        return tf_el
773
774    def buildAllConfigurations(self, doc, prjname, t):
775        #Configurations Node
776        confs_el = doc.createElement("Configurations")
777        for c in sortedConfigKeys(t.configs):
778            cfg = t.configs[c]
779            confs_el.appendChild(self.buildSingleConfiguration(doc, prjname, cfg, c, t))
780        return confs_el
781
782    def buildSingleConfiguration(self, doc, prjname, cfg, c, t):
783        conf_el = self.buildConfElement(doc, cfg, c, t)
784
785        # this dict is indexed by the names of the tools which need special
786        # treatment, i.e. should be non-empty in the generated file: the value
787        # of the corresponding dict element is our method which is called with
788        # tool itself as well as prjname, cfg, c and t as parameters and should
789        # fill in the tool with contents
790        #
791        # NB: order here doesn't matter so we sort tools alphabetically, the
792        #     order in the file is determined by the tools list below
793        toolHandlers = {
794                         'VCBscMakeTool'    : self.buildBscMakeToolElement,
795                         'VCCLCompilerTool' : self.buildCompilerToolElement,
796                         'VCMIDLTool'       : self.buildIdlToolElement,
797                         'VCLibrarianTool'  : self.buildLibrarianToolElement,
798                         'VCLinkerTool'     : self.buildLinkerToolElement,
799                         'VCResourceCompilerTool'
800                                            : self.buildResourceCompilerToolElement,
801                       }
802
803        if cfg._type_nick == 'lib':
804            linkTool = 'VCLibrarianTool'
805        else:
806            linkTool = 'VCLinkerTool'
807
808        # this list contains the tools in the order appropriate for the current
809        # project file version
810        if _MSVS_VCPROJ_VERSION == "7.10":
811            tools = [
812                        'VCCLCompilerTool',
813                        'VCCustomBuildTool',
814                        linkTool,
815                        'VCMIDLTool',
816                        'VCPostBuildEventTool',
817                        'VCPreBuildEventTool',
818                        'VCPreLinkEventTool',
819                        'VCResourceCompilerTool',
820                        'VCWebServiceProxyGeneratorTool',
821                        'VCXMLDataGeneratorTool',
822                        'VCWebDeploymentTool',
823                        'VCManagedWrapperGeneratorTool',
824                        'VCAuxiliaryManagedWrapperGeneratorTool',
825                    ]
826        else:
827            tools = [
828                        'VCPreBuildEventTool',
829                        'VCCustomBuildTool',
830                        'VCXMLDataGeneratorTool',
831                        'VCWebServiceProxyGeneratorTool',
832                        'VCMIDLTool',
833                        'VCCLCompilerTool',
834                        'VCManagedResourceCompilerTool',
835                        'VCResourceCompilerTool',
836                        'VCPreLinkEventTool',
837                        linkTool,
838                        'VCALinkTool',
839                        'VCManifestTool',
840                        'VCXDCMakeTool',
841                        'VCBscMakeTool',
842                        'VCFxCopTool',
843                        'VCAppVerifierTool',
844                        'VCWebDeploymentTool',
845                        'VCPostBuildEventTool',
846                    ]
847
848            if cfg._type_nick not in ['gui', 'console']:
849                # these tools only make sense for the applications, not libraries
850                tools.remove('VCAppVerifierTool')
851                tools.remove('VCWebDeploymentTool')
852            else:
853                if _MSVS_VCPROJ_VERSION != "8.00":
854                    # this tool was removed in VS 2008
855                    tools.remove('VCWebDeploymentTool')
856
857        # add all the tools
858        for tool in tools:
859            node = doc.createElement("Tool")
860            node.setAttribute("Name", tool)
861            if tool in toolHandlers:
862                toolHandlers[tool](node, prjname, cfg, c, t)
863            conf_el.appendChild(node)
864
865
866        # additional tools
867        if self.isEmbeddedConfig(c):
868            self.buildEmbeddedTools(doc, conf_el)
869
870        return conf_el
871
872    def buildCustomBuildElement(self, doc, data):
873        # parse MSVC6-style custom build element code (FIXME: this is for
874        # compatibility, will be replaced with <action> handling in the
875        # future):
876        lines = data.split('\n')
877        description = lines[0]
878        assert lines[1].startswith('InputPath=')
879        assert lines[2] == ''
880        delim = lines[3].index(' : ')
881        output = lines[3][:delim].strip(' "')
882        deps = lines[3][delim+3:].strip()
883        cmdline = lines[4].strip()
884
885        # build the output:
886        el = doc.createElement("Tool")
887        el.setAttribute("Name", "VCCustomBuildTool")
888        el.setAttribute('Description', description)
889        el.setAttribute('CommandLine', cmdline)
890        el.setAttribute('Outputs', output)
891        if len(deps) > 0:
892            # FIXME: remove this hack once custom build steps are
893            #        implemented properly
894            # these are VC6-specific things that VC7+ doesn't recognize
895            if deps.startswith('"$(SOURCE)" '):
896                deps = deps[len('"$(SOURCE)" '):]
897            elif deps.startswith('$(SOURCE) '):
898                deps = deps[len('$(SOURCE) '):]
899
900            if _MSVS_SLN_VERSION == "10.00":
901                el.setAttribute('AdditionalDependencies', mk_listsp(deps))
902            else:
903                el.setAttribute('AdditionalDependencies', deps)
904
905        return el
906
907
908    def buildEmbeddedTools(self, doc, conf_el):
909        deployment_tool_el = doc.createElement("DeploymentTool")
910        deployment_tool_el.setAttribute("ForceDirty", "-1")
911        deployment_tool_el.setAttribute("RemoteDirectory", "")
912        deployment_tool_el.setAttribute("RegisterOutput", "0")
913        deployment_tool_el.setAttribute("AdditionalFiles", "")
914        conf_el.appendChild(deployment_tool_el)
915
916        debugger_tool_el = doc.createElement("DebuggerTool")
917        conf_el.appendChild(debugger_tool_el)
918
919    def genVCProj(self, t, filename, prjname, guid):
920        #start a new xml document
921        doc = DocumentSorted()
922        top_el = doc.createElement("VisualStudioProject")
923        doc.appendChild(top_el)
924        comment = doc.createComment("""
925
926  This project was generated by
927  Bakefile %s (http://www.bakefile.org)
928  Do not modify, all changes will be overwritten!
929
930""" % BAKEFILE_VERSION)
931        doc.insertBefore(comment, top_el)
932
933        #fill in the attributes of the top element
934        top_el.setAttribute("ProjectType", "Visual C++")
935        top_el.setAttribute("Version", _MSVS_VCPROJ_VERSION)
936        top_el.setAttribute("Name", t.id)
937        top_el.setAttribute("ProjectGUID", "%s" % guid)
938
939        top_el.appendChild(self.buildPlatformsElement(doc))
940
941        if _MSVS_VCPROJ_VERSION != "7.10":
942            top_el.appendChild(self.buildToolFilesElement(doc))
943
944        top_el.appendChild(self.buildAllConfigurations(doc, prjname, t))
945
946        # MSVC insists on having these tags even though they are empty and
947        # moreover, it wants to have "<References>\n</References>" and not
948        # just "<References/>" so we need to add a dummy text element in the
949        # middle to make it closer, although still not perfect because it
950        # results in an extra one line which native project files don't have
951        # (TODO: is there a way to avoid this extra blank line?)
952        refs_el = doc.createElement("References")
953        refs_el.appendChild(doc.createTextNode(""))
954        top_el.appendChild(refs_el)
955
956        files_el = doc.createElement("Files")
957
958        #munge the source files around so we can write them to the file
959        sources, groups, files, filesWithCustomBuild = \
960            organizeFilesIntoGroups(t, DEFAULT_FILE_GROUPS, groupClass=MsvsFilesGroup)
961
962        conflictingNames = findConflictingNames(sources.keys())
963
964        ##define a local helper function for building the files area
965        def makeFileConfig(t, cfg, c, src, group, sources, pchExcluded, conflictingNames):
966            conf_name = self.mkConfigName(c)
967            file_conf_el = doc.createElement("FileConfiguration")
968            file_conf_el.setAttribute("Name", conf_name)
969
970            if sources[src] != None and c not in sources[src]:
971                file_conf_el.setAttribute("ExcludedFromBuild", bool2vcstr(True))
972                tool_el = None
973            elif (src in filesWithCustomBuild.keys() and
974                c in filesWithCustomBuild[src].keys()):
975                #custom build for this file for this config
976                data = filesWithCustomBuild[src][c]
977                if data != '':
978                    tool_el = self.buildCustomBuildElement(doc, data)
979                else:
980                    tool_el = None
981            elif (cfg._pch_use_pch and
982                  (src == cfg._pch_generator or src in pchExcluded)):
983                tool_el = doc.createElement("Tool")
984                tool_el.setAttribute("Name", "VCCLCompilerTool")
985                if src == cfg._pch_generator:
986                    tool_el.setAttribute("UsePrecompiledHeader", pchCreateUsingSpecific)
987                elif src in pchExcluded:
988                    tool_el.setAttribute("UsePrecompiledHeader", pchNone)
989            elif src in conflictingNames and isSourceFile(src):
990                # allow source files with same name but in different directory
991                # (see bug #92):
992                obj = os.path.splitext(utils.makeUniqueName(src, conflictingNames))[0] + '.obj'
993                tool_el = doc.createElement("Tool")
994                tool_el.setAttribute("Name", "VCCLCompilerTool")
995                tool_el.setAttribute("ObjectFile", "%s\\%s\\%s" % (cfg._builddir, t.id, obj))
996            else:
997                tool_el = None
998                file_conf_el = None
999
1000            if tool_el != None:
1001                file_conf_el.appendChild(tool_el)
1002            return file_conf_el
1003
1004        pchExcluded = t._pch_excluded.split()
1005
1006        for group in [g for g in groups if g.name in files]:
1007            lst = files[group.name]
1008            sortByBasename(lst)
1009            if len(lst) == 0: continue
1010
1011            filt_el = doc.createElement("Filter")
1012            filt_el.setAttribute("Name", group.name)
1013            if group.extensions != None:
1014                filt_el.setAttribute("Filter", group.extensions)
1015            filt_el.setAttribute("UniqueIdentifier", group.uuid)
1016
1017            for src in lst:
1018                file_el = doc.createElement("File")
1019                relpath = "%s\\%s" % (SRCDIR.replace('/','\\'), src)
1020                if relpath.startswith(".\\..\\"):
1021                    relpath = relpath[2:]
1022                file_el.setAttribute("RelativePath", relpath)
1023
1024                for c in sortedConfigKeys(t.configs):
1025                    cfg = t.configs[c]
1026                    file_conf_el = makeFileConfig(t, cfg, c, src, group.name,
1027                                                  sources,
1028                                                  pchExcluded,
1029                                                  conflictingNames
1030                                                  )
1031                    if ( file_conf_el != None ):
1032                        file_el.appendChild(file_conf_el)
1033
1034                if (t.id, src) in self.fragments:
1035                    f = VerbatimFragment(self.fragments[(t.id, src)])
1036                    file_el.appendChild(f)
1037
1038                filt_el.appendChild(file_el)
1039
1040            files_el.appendChild(filt_el)
1041
1042        top_el.appendChild(files_el)
1043
1044        # see comment for refs_el above
1045        globals_el = doc.createElement("Globals")
1046        globals_el.appendChild(doc.createTextNode(""))
1047        top_el.appendChild(globals_el)
1048
1049        vcprojText = doc.toprettyxml(encoding="Windows-1252")
1050
1051        writer.writeFile(filename, vcprojText)
1052
1053def run():
1054    msvc_common.__dict__.update(globals())
1055    generator = ProjectGeneratorMsvc9()
1056    generator.genWorkspaces()
1057