1#
2#  This file is part of Bakefile (http://www.bakefile.org)
3#
4#  Copyright (C) 2003-2007 Vaclav Slavik
5#
6#  Permission is hereby granted, free of charge, to any person obtaining a copy
7#  of this software and associated documentation files (the "Software"), to
8#  deal in the Software without restriction, including without limitation the
9#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10#  sell copies of the Software, and to permit persons to whom the Software is
11#  furnished to do so, subject to the following conditions:
12#
13#  The above copyright notice and this permission notice shall be included in
14#  all copies or substantial portions of the Software.
15#
16#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22#  IN THE SOFTWARE.
23#
24#  $Id: msvc6prj.py 1295 2009-03-16 08:55:50Z vaclavslavik $
25#
26#  MS Visual C++ projects generator script
27#
28
29import os, os.path
30import errors, utils
31
32import msvc_common
33from msvc_common import *
34
35
36DEFAULT_FILE_GROUPS = [
37    FilesGroup('Source Files',
38               '*.cpp *.c *.cxx *.rc *.def *.r *.odl *.idl *.hpj *.bat'),
39    FilesGroup('Header Files',
40               '*.h *.hpp *.hxx *.hm *.inl'),
41    FilesGroup('Resource Files',
42               '*.ico *.cur *.bmp *.dlg *.rc2 *.rct *.bin *.rgs *.gif *.jpg *.jpeg *.jpe')
43]
44
45# ------------------------------------------------------------------------
46#                              Generator class
47# ------------------------------------------------------------------------
48
49class ProjectGeneratorMsvc6:
50
51    def __init__(self):
52        self.basename = os.path.splitext(os.path.basename(FILE))[0]
53        self.dirname = os.path.dirname(FILE)
54
55    # --------------------------------------------------------------------
56    #   basic configuration
57    # --------------------------------------------------------------------
58
59    def getSolutionExtension(self):
60        return 'dsw'
61    def getProjectExtension(self):
62        return 'dsp'
63    def getMakefileExtension(self):
64        return 'mak'
65
66    # --------------------------------------------------------------------
67    #   helpers
68    # --------------------------------------------------------------------
69
70    def mkConfigName(self, target, config):
71        return '%s - Win32 %s' % (target, config)
72
73    def makeDependency(self, prj_id):
74        return """\
75Begin Project Dependency
76Project_Dep_Name %s
77End Project Dependency
78""" % prj_id
79
80
81    # --------------------------------------------------------------------
82    #   DSW file
83    # --------------------------------------------------------------------
84
85    def makeDswHeader(self):
86        return """\
87Microsoft Developer Studio Workspace File, Format Version 6.00
88# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
89
90###############################################################################"""
91
92    def genDSW(self, dsw_targets, dsp_list, deps_translation):
93        dsw = self.makeDswHeader()
94        project = """
95Project: "%s"=%s.""" + self.getProjectExtension() + """ - Package Owner=<4>
96
97Package=<5>
98{{{
99}}}
100
101Package=<4>
102{{{
103%s}}}
104
105###############################################################################
106"""
107
108        single_target = (len(dsw_targets) == 1)
109        for t in dsw_targets:
110            deps = ''
111            if single_target:
112                dsp_name = self.basename
113            else:
114                dsp_name = '%s_%s' % (self.basename, t.id)
115            deplist = t._deps.split()
116
117            # add external dsp dependencies:
118            for d in t._dsp_deps.split():
119                deplist.append(d.split(':')[0])
120            # write dependencies:
121            for d in deplist:
122                if d in deps_translation:
123                    d2 = deps_translation[d]
124                else:
125                    d2 = d
126                deps += self.makeDependency(d2)
127
128            dsw += project % (t.id, dsp_name, deps)
129            dspfile = (t,
130                       os.path.join(self.dirname,
131                                    dsp_name + '.' + self.getProjectExtension()),
132                       dsp_name)
133            if dspfile not in dsp_list:
134                dsp_list.append(dspfile)
135
136        # add external dsp deps (we put them after bakefile's own targets so
137        # that the active project when you first open the workspace is something
138        # meaningful and not a mere dependency):
139        extern_deps = []
140        for t in dsw_targets:
141            for d in t._dsp_deps.split():
142                if d not in extern_deps:
143                    extern_deps.append(d)
144        for d in extern_deps:
145            deps = ''
146            d_components = d.split(':')
147            if len(d_components) == 3:
148                for d_dep in d_components[2].split(','):
149                    deps += self.makeDependency(d_dep)
150            dsw += project % (d_components[0],
151                              os.path.splitext(d_components[1])[0], deps)
152
153        writer.writeFile('%s.%s' % (
154            os.path.join(self.dirname, self.basename),
155            self.getSolutionExtension()
156            ), dsw)
157
158
159    def genWorkspaces(self):
160        dsp_list = []
161
162        # find all projects. Beware ugly hack here: MSVC6PRJ_MERGED_TARGETS is
163        # used to create fake targets as a merge of two (mutually exclusive)
164        # targets. This is sometimes useful, e.g. when you want to build both
165        # DLL and static lib of something.
166        deps_translation = {}
167        projects = [t for t in targets if t._kind == 'project']
168        for mergeInfo in MSVC6PRJ_MERGED_TARGETS.split():
169            split1 = mergeInfo.split('=')
170            split2 = split1[1].split('+')
171            tgR = split1[0]
172            tg1 = split2[0]
173            tg2 = split2[1]
174
175            # the targets may be disabled by some (weak) condition:
176            if tg1 not in targets and tg2 not in targets:
177                continue
178
179            t = targets[tg1]
180            for c in targets[tg2].configs:
181                assert c not in t.configs # otherwise not mutually exclusive
182                t.configs[c] = targets[tg2].configs[c]
183            t.id = tgR
184            projects.remove(targets[tg2])
185            targets.append(tgR, t)
186            del targets[tg1]
187            del targets[tg2]
188            deps_translation[tg1] = tgR
189            deps_translation[tg2] = tgR
190
191        self.genDSW(projects, dsp_list, deps_translation)
192        for t, filename, prjname in dsp_list:
193            self.genDSP(t, filename, prjname)
194
195        # warn about <action> targets that we can't handle (yet):
196        for t in [t for t in targets if t._kind == 'action']:
197            print "warning: ignoring action target '%s'" % t.id
198
199
200    # ------------------------------------------------------------------------
201    #   DSP files
202    # ------------------------------------------------------------------------
203
204    def mkFlags(self, keyword, lines):
205        result = []
206        splitted = lines.splitlines();
207        splitted2 = [fixFlagsQuoting(' '.join(x.split())) for x in splitted]
208        for l in splitted2:
209            result.append('# %s BASE %s' % (keyword, l))
210        for l in splitted2:
211            result.append('# %s %s' % (keyword, l))
212        return '\n'.join(result)+'\n'
213
214
215    def makeDspHeader(self, id):
216        return """\
217# Microsoft Developer Studio Project File - Name="%s" - Package Owner=<4>
218# Microsoft Developer Studio Generated Build File, Format Version 6.00
219# ** DO NOT EDIT **
220
221""" % id
222
223    def makeBeginProject(self, t):
224        txt = """\
225# PROP AllowPerConfigDependencies 0
226# PROP Scc_ProjName ""
227# PROP Scc_LocalPath ""
228CPP=cl.exe
229"""
230        if t._type_nick in ['gui','dll']:
231            txt += 'MTL=midl.exe\n'
232        txt += 'RSC=rc.exe\n'
233        return txt
234
235    def makeCpuPlatformID(self, cfg):
236        return ''
237
238    def makeSettingsCPP(self, cfg):
239        return self.mkFlags('ADD',
240                            'CPP /nologo %s %s /c' % (cfg._cppflags, cfg._defines))
241    def makeSettingsMTL(self, cfg):
242        return self.mkFlags('ADD',
243                            'MTL /nologo %s /mktyplib203 /win32' % cfg._defines)
244
245    def makeSettingsRSC(self, cfg):
246        return self.mkFlags('ADD', 'RSC %s' % cfg._win32rc_flags)
247
248    def makeSettingsBSC(self, cfg):
249        return """\
250BSC32=bscmake.exe
251# ADD BASE BSC32 /nologo
252# ADD BSC32 /nologo
253"""
254    def makeSettingsLIB(self, cfg):
255            txt = 'LIB32=link.exe -lib\n'
256            txt += self.mkFlags('ADD','LIB32 /nologo %s' % cfg._outflag)
257            return txt
258
259    def makeSettingsLINK(self, cfg):
260            txt = 'LINK32=link.exe\n'
261            txt += self.mkFlags('ADD','LINK32 %s /nologo %s' % (cfg._ldlibs, cfg._ldflags))
262            return txt
263
264    def makeSettingsCPP_MTL_RSC_BSC_LINK(self, cfg):
265        txt = self.makeSettingsCPP(cfg)
266        if cfg._type_nick in ['gui','dll']:
267            txt += self.makeSettingsMTL(cfg)
268        txt += self.makeSettingsRSC(cfg)
269        txt += self.makeSettingsBSC(cfg)
270        if cfg._type_nick != 'lib':
271            txt += self.makeSettingsLINK(cfg)
272        else:
273            txt += self.makeSettingsLIB(cfg)
274        return txt
275
276    def genDSP(self, t, filename, prjname):
277        # Create header and list of configurations:
278
279        default_cfg = sortedConfigKeys(t.configs)[-1]
280        dsp = self.makeDspHeader(prjname)
281        targ_types = []
282        for c in t.configs:
283            targ = '%s %s' % (t.configs[c]._type, t.configs[c]._type_code)
284            if targ not in targ_types:
285                targ_types.append(targ)
286        for tt in targ_types:
287            dsp += '# TARGTYPE %s\n' % tt
288
289        dsp += '\nCFG=%s\n' % self.mkConfigName(t.id, default_cfg)
290        dsp += """\
291!MESSAGE This is not a valid makefile. To build this project using NMAKE,
292!MESSAGE use the Export Makefile command and run
293!MESSAGE
294!MESSAGE NMAKE /f "%s.%s".
295!MESSAGE
296!MESSAGE You can specify a configuration when running NMAKE
297!MESSAGE by defining the macro CFG on the command line. For example:
298!MESSAGE
299!MESSAGE NMAKE /f "%s.%s" CFG="%s"
300!MESSAGE
301!MESSAGE Possible choices for configuration are:
302!MESSAGE
303""" % (prjname, self.getMakefileExtension(),
304       prjname, self.getMakefileExtension(),
305       self.mkConfigName(t.id, default_cfg))
306        for c in sortedConfigKeys(t.configs):
307            dsp += '!MESSAGE "%s" (based on %s)\n' % (self.mkConfigName(t.id, c), t.configs[c]._type)
308        dsp += """\
309!MESSAGE
310
311# Begin Project
312"""
313        dsp += self.makeBeginProject(t)
314        dsp += '\n'
315
316        # Output settings for all configurations:
317        flags = []
318        for c in sortedConfigKeys(t.configs):
319            cfg = t.configs[c]
320            fl = '  "$(CFG)" == "%s"' % self.mkConfigName(t.id, c) + '\n\n'
321            fl += self.mkFlags('PROP',"""\
322Use_MFC 0
323Use_Debug_Libraries """ + cfg._debug + """
324Output_Dir "%s"
325Intermediate_Dir "%s\\%s"
326%sTarget_Dir ""
327""" % (cfg._targetdir[:-1], cfg._builddir, t.id,
328       self.makeCpuPlatformID(cfg)))
329            fl += self.makeSettingsCPP_MTL_RSC_BSC_LINK(cfg)
330            fl += '\n'
331            flags.append(fl)
332        dsp += '!IF' + '!ELSEIF'.join(flags) + '!ENDIF'
333
334        dsp += '\n\n# Begin Target\n\n'
335
336        # Output list of configs one more:
337        for c in sortedConfigKeys(t.configs):
338            dsp += '# Name "%s"\n' % self.mkConfigName(t.id, c)
339
340        # Write source files:
341
342        sources, groups, files, filesWithCustomBuild = \
343            organizeFilesIntoGroups(t, DEFAULT_FILE_GROUPS)
344
345        # (some files-related settings:)
346        pchExcluded = t._pch_excluded.split()
347
348        # (write them)
349        for group in [g.name for g in groups if g.name in files]:
350            lst = files[group]
351            sortByBasename(lst)
352            if len(lst) == 0: continue
353            if group != None:
354                dsp += """\
355# Begin Group "%s"
356
357# PROP Default_Filter ""
358""" % group
359            for src in lst:
360                dsp += """\
361# Begin Source File
362
363SOURCE=%s\\%s
364""" % (SRCDIR.replace('/','\\'),src)
365                file_flags = ''
366                if src == t._pch_generator:
367                    file_flags += '# ADD BASE CPP /Yc"%s"\n' % t._pch_header
368                    file_flags += '# ADD CPP /Yc"%s"\n' % t._pch_header
369                if src in pchExcluded:
370                    file_flags += '# SUBTRACT CPP /YX /Yc /Yu\n'
371
372                # the file is either disabled in some configurations or has
373                # custom build step associated with it:
374                if sources[src] != None or src in filesWithCustomBuild:
375                    flags = []
376                    old_file_flags = file_flags
377                    for c in sortedConfigKeys(t.configs):
378                        if sources[src] != None and c not in sources[src]:
379                            file_flags += '# PROP Exclude_From_Build 1\n'
380                        if src in filesWithCustomBuild:
381                            file_flags += \
382                              '# Begin Custom Build - %s\n\n# End Custom Build\n' %\
383                               filesWithCustomBuild[src][c]
384                        flags.append('  "$(CFG)" == "%s"' % self.mkConfigName(t.id, c) +
385                                     '\n\n' + file_flags + '\n')
386                        file_flags = old_file_flags
387                    dsp += '\n!IF' + '!ELSEIF'.join(flags) + '!ENDIF\n\n'
388                else:
389                    dsp += file_flags
390                dsp += '# End Source File\n'
391            if group != None:
392                dsp += '# End Group\n'
393
394        # Write footer:
395        dsp += """\
396# End Target
397# End Project
398"""
399
400        writer.writeFile(filename, dsp)
401
402def run():
403    msvc_common.__dict__.update(globals())
404    generator = ProjectGeneratorMsvc6()
405    generator.genWorkspaces()
406