Â#985
[tridge/waf-svn.git/.git] / waflib / extras / msvs.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # Avalanche Studios 2009-2011
4 # Thomas Nagy 2011
5
6 """
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10
11 1. Redistributions of source code must retain the above copyright
12    notice, this list of conditions and the following disclaimer.
13
14 2. Redistributions in binary form must reproduce the above copyright
15    notice, this list of conditions and the following disclaimer in the
16    documentation and/or other materials provided with the distribution.
17
18 3. The name of the author may not be used to endorse or promote products
19    derived from this software without specific prior written permission.
20
21 THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
22 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
25 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 POSSIBILITY OF SUCH DAMAGE.
32 """
33
34 """
35 To add this tool to your project:
36 def options(conf):
37         opt.load('msvs')
38
39 It can be a good idea to add the sync_exec tool too.
40
41 To generate solution files:
42 $ waf configure msvs
43
44 To customize the outputs, provide subclasses in your wscript files:
45
46 from waflib.extras import msvs
47 class vsnode_target(msvs.vsnode_target):
48     def get_build_command(self, props):
49                 # likely to be required
50         return "waf.bat build"
51         def collect_source(self):
52                 # likely to be required
53                 ...
54 class msvs_bar(msvs.msvs_generator):
55     def init(self):
56         msvs.msvs_generator.init(self)
57         self.vsnode_target = vsnode_target
58
59 ASSUMPTIONS:
60 * a project can be either a directory or a target, vcxproj files are written only for targets that have source files
61 * each project is a vcxproj file, therefore the project uuid needs only to be a hash of the absolute path
62
63 """
64
65 import os, re
66 import uuid # requires python 2.5
67 from waflib.Build import BuildContext
68 from waflib import Utils, TaskGen, Logs, Task, Context
69
70 HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
71
72 PROJECT_TEMPLATE = r'''<?xml version="1.0" encoding="Windows-1252"?>
73 <Project DefaultTargets="Build" ToolsVersion="4.0"
74         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
75
76         <ItemGroup Label="ProjectConfigurations">
77                 ${for b in project.build_properties}
78                 <ProjectConfiguration Include="${b.configuration}|${b.platform}">
79                         <Configuration>${b.configuration}</Configuration>
80                         <Platform>${b.platform}</Platform>
81                 </ProjectConfiguration>
82                 ${endfor}
83         </ItemGroup>
84
85         <PropertyGroup Label="Globals">
86                 <ProjectGuid>{${project.uuid}}</ProjectGuid>
87                 <Keyword>MakeFileProj</Keyword>
88         </PropertyGroup>
89         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
90
91         ${for b in project.build_properties}
92         <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'" Label="Configuration">
93                 <ConfigurationType>Makefile</ConfigurationType>
94                 <OutDir>${b.outdir}</OutDir>
95         </PropertyGroup>
96         ${endfor}
97
98         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
99         <ImportGroup Label="ExtensionSettings">
100         </ImportGroup>
101
102         ${for b in project.build_properties}
103         <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
104                 <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
105         </ImportGroup>
106         ${endfor}
107
108         ${for b in project.build_properties}
109         <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
110                 <NMakeBuildCommandLine>${xml:project.get_build_command(b)}</NMakeBuildCommandLine>
111                 <NMakeReBuildCommandLine>${xml:project.get_rebuild_command(b)}</NMakeReBuildCommandLine>
112                 <NMakeCleanCommandLine>${xml:project.get_clean_command(b)}</NMakeCleanCommandLine>
113                 <NMakeIncludeSearchPath>${xml:b.includes_search_path}</NMakeIncludeSearchPath>
114                 <NMakePreprocessorDefinitions>${xml:b.preprocessor_definitions};$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
115                 <IncludePath></IncludePath>
116                 <ExecutablePath>$(ExecutablePath)</ExecutablePath>
117
118                 ${if getattr(b, 'output_file', None)}
119                 <NMakeOutput>${xml:b.output_file}</NMakeOutput>
120                 ${endif}
121                 ${if getattr(b, 'deploy_dir', None)}
122                 <RemoteRoot>${xml:b.deploy_dir}</RemoteRoot>
123                 ${endif}
124         </PropertyGroup>
125         ${endfor}
126
127         ${for b in project.build_properties}
128                 ${if getattr(b, 'deploy_dir', None)}
129         <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
130                 <Deploy>
131                         <DeploymentType>CopyToHardDrive</DeploymentType>
132                 </Deploy>
133         </ItemDefinitionGroup>
134                 ${endif}
135         ${endfor}
136
137         <ItemGroup>
138                 ${for x in project.source}
139                 <${project.get_key(x)} Include='${x.abspath()}' />
140                 ${endfor}
141         </ItemGroup>
142         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
143         <ImportGroup Label="ExtensionTargets">
144         </ImportGroup>
145 </Project>
146 '''
147
148 PROJECT_2008_TEMPLATE = r'''<?xml version="1.0" encoding="Windows-1252"?>
149 <VisualStudioProject
150         ProjectType="Visual C++"
151         Version="9,00"
152         Name="${xml: project.name}"
153         ProjectGUID="{${project.uuid}}"
154         RootNamespace=""
155         Keyword="MakeFileProj"
156         TargetFrameworkVersion="196613"
157         >
158         <Platforms>
159                 ${if project.build_properties}
160                 ${for b in project.build_properties}
161                 <Platform
162                         Name="${xml: b.platform}"
163                 />
164                 ${endfor}
165                 ${else}
166                 <!-- This is a default platform, VisualStudioProject must have one -->
167                 <Platform
168                         Name="Win32"
169                         />
170                 ${endif}
171         </Platforms>
172         <ToolFiles>
173         </ToolFiles>
174         <Configurations>
175                 ${if project.build_properties}
176                 ${for b in project.build_properties}
177                 <Configuration
178                         Name="${xml: b.configuration}|${xml: b.platform}"
179                         OutputDirectory="${xml: b.outdir}"
180                         ConfigurationType="0"
181                         InheritedPropertySheets="">
182                         <Tool
183                                 Name="VCNMakeTool"
184                                 BuildCommandLine="${xml: project.get_build_command(b)}"
185                                 ReBuildCommandLine="${xml: project.get_rebuild_command(b)}"
186                                 CleanCommandLine="${xml: project.get_clean_command(b)}"
187                                 ${if getattr(b, 'output_file', None)}
188                                 OutPut="${xml: b.output_file}"
189                                 ${endif}
190                                 PreprocessorDefinitions="${xml: b.preprocessor_definitions}"
191                                 IncludeSearchPath="${xml: b.includes_search_path}"
192                                 ForceIncludes=""
193                                 ForcedUsingAssemblies=""
194                                 CompileAsManaged=""
195                         />
196                 </Configuration>
197                 ${endfor}
198                 ${else}
199                 <!-- This is a default configuration, VisualStudioProject must have one -->
200                 <Configuration
201                         Name="Release|Win32"
202                         >
203                 </Configuration>
204                 ${endif}
205         </Configurations>
206         <References>
207         </References>
208         <Files>
209                 ${for x in project.source}
210                 <File
211                         RelativePath="${x.abspath()}"
212                         FileType="${project.get_key(x)}"
213                         >
214                 </File>
215                 ${endfor}
216         </Files>
217 </VisualStudioProject>
218 '''
219
220 FILTER_TEMPLATE = '''<?xml version="1.0" encoding="Windows-1252"?>
221 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
222         <ItemGroup>
223                 ${for x in project.source}
224                         <${project.get_key(x)} Include="${x.abspath()}">
225                                 <Filter>${x.parent.path_from(project.path)}</Filter>
226                         </${project.get_key(x)}>
227                 ${endfor}
228         </ItemGroup>
229         <ItemGroup>
230                 ${for x in project.dirs()}
231                         <Filter Include="${x.path_from(project.path)}">
232                                 <UniqueIdentifier>{${project.make_uuid(x.abspath())}}</UniqueIdentifier>
233                         </Filter>
234                 ${endfor}
235         </ItemGroup>
236 </Project>
237 '''
238
239 SOLUTION_TEMPLATE = '''Microsoft Visual Studio Solution File, Format Version ${project.numver}
240 # Visual Studio ${project.vsver}
241 ${for p in project.all_projects}
242 Project("{${p.ptype()}}") = "${p.name}", "${p.title}", "{${p.uuid}}"
243 EndProject${endfor}
244 Global
245         GlobalSection(SolutionConfigurationPlatforms) = preSolution
246                 ${if project.all_projects}
247                 ${for (configuration, platform) in project.all_projects[0].ctx.project_configurations()}
248                 ${configuration}|${platform} = ${configuration}|${platform}
249                 ${endfor}
250                 ${endif}
251         EndGlobalSection
252         GlobalSection(ProjectConfigurationPlatforms) = postSolution
253                 ${for p in project.all_projects}
254                         ${if hasattr(p, 'source')}
255                         ${for b in p.build_properties}
256                 {${p.uuid}}.${b.configuration}|${b.platform}.ActiveCfg = ${b.configuration}|${b.platform}
257                         ${if getattr(p, 'is_active', None)}
258                 {${p.uuid}}.${b.configuration}|${b.platform}.Build.0 = ${b.configuration}|${b.platform}
259                         ${endif}
260                         ${endfor}
261                         ${endif}
262                 ${endfor}
263         EndGlobalSection
264         GlobalSection(SolutionProperties) = preSolution
265                 HideSolutionNode = FALSE
266         EndGlobalSection
267         GlobalSection(NestedProjects) = preSolution
268         ${for p in project.all_projects}
269                 ${if p.parent}
270                 {${p.uuid}} = {${p.parent.uuid}}
271                 ${endif}
272         ${endfor}
273         EndGlobalSection
274 EndGlobal
275 '''
276
277 COMPILE_TEMPLATE = '''def f(project):
278         lst = []
279         def xml_escape(value):
280                 return value.replace("&", "&amp;").replace('"', "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
281
282         %s
283
284         #f = open('cmd.txt', 'w')
285         #f.write(str(lst))
286         #f.close()
287         return ''.join(lst)
288 '''
289 reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<code>[^}]*?)\})", re.M)
290 def compile_template(line):
291         """
292         Compile a template expression into a python function (like jsps, but way shorter)
293         """
294         extr = []
295         def repl(match):
296                 g = match.group
297                 if g('dollar'): return "$"
298                 elif g('subst'):
299                         extr.append(g('code'))
300                         return "<<|@|>>"
301                 return None
302
303         line2 = reg_act.sub(repl, line)
304         params = line2.split('<<|@|>>')
305         assert(extr)
306
307
308         indent = 0
309         buf = []
310         dvars = []
311         app = buf.append
312
313         def app(txt):
314                 buf.append(indent * '\t' + txt)
315
316         for x in range(len(extr)):
317                 if params[x]:
318                         app("lst.append(%r)" % params[x])
319
320                 f = extr[x]
321                 if f.startswith('if') or f.startswith('for'):
322                         app(f + ':')
323                         indent += 1
324                 elif f.startswith('py:'):
325                         app(f[3:])
326                 elif f.startswith('endif') or f.startswith('endfor'):
327                         indent -= 1
328                 elif f.startswith('else') or f.startswith('elif'):
329                         indent -= 1
330                         app(f + ':')
331                         indent += 1
332                 elif f.startswith('xml:'):
333                         app('lst.append(xml_escape(%s))' % f[4:])
334                 else:
335                         #app('lst.append((%s) or "cannot find %s")' % (f, f))
336                         app('lst.append(%s)' % f)
337
338         if extr:
339                 if params[-1]:
340                         app("lst.append(%r)" % params[-1])
341
342         fun = COMPILE_TEMPLATE % "\n\t".join(buf)
343         #print fun
344         return Task.funex(fun)
345
346 re_blank = re.compile('\n\s*\n', re.M)
347 def rm_blank_lines(txt):
348         txt = re_blank.sub('\n', txt)
349         return txt
350
351
352 def make_uuid(v, prefix = None):
353         """
354         simple utility function
355         """
356         if isinstance(v, dict):
357                 keys = list(v.keys())
358                 keys.sort()
359                 tmp = str([(k, v[k]) for k in keys])
360         else:
361                 tmp = str(v)
362         d = Utils.md5(tmp.encode()).hexdigest().upper()
363         if prefix:
364                 d = '%s%s' % (prefix, d[8:])
365         gid = uuid.UUID(d, version = 4)
366         return str(gid).upper()
367
368 class build_property(object):
369         pass
370
371 class vsnode(object):
372         """
373         Abstract class representing visual studio elements
374         We assume that all visual studio nodes have a uuid and a parent
375         """
376         def __init__(self, ctx):
377                 self.ctx = ctx # msvs context
378                 self.name = '' # string, mandatory
379                 self.vspath = '' # path in visual studio (name for dirs, absolute path for projects)
380                 self.uuid = '' # string, mandatory
381                 self.parent = None # parent node for visual studio nesting
382
383         def get_waf(self):
384                 """
385                 Override in subclasses...
386                 """
387                 return 'cd /d "%s" & waf.bat' % self.ctx.srcnode.abspath()
388
389         def ptype(self):
390                 """
391                 Return a special uuid for projects written in the solution file
392                 """
393                 pass
394
395         def write(self):
396                 """
397                 Write the project file, by default, do nothing
398                 """
399                 pass
400
401         def make_uuid(self, val):
402                 """
403                 Alias for creating uuid values easily (the templates cannot access global variables)
404                 """
405                 return make_uuid(val)
406
407 class vsnode_vsdir(vsnode):
408         """
409         Nodes representing visual studio folders (which do not match the filesystem tree!)
410         """
411         VS_GUID_SOLUTIONFOLDER = "2150E333-8FDC-42A3-9474-1A3956D46DE8"
412         def __init__(self, ctx, uuid, name, vspath=''):
413                 vsnode.__init__(self, ctx)
414                 self.title = self.name = name
415                 self.uuid = uuid
416                 self.vspath = vspath or name
417
418         def ptype(self):
419                 return self.VS_GUID_SOLUTIONFOLDER
420
421 class vsnode_project(vsnode):
422         """
423         Abstract class representing visual studio project elements
424         A project is assumed to be writable, and has a node representing the file to write to
425         """
426         VS_GUID_VCPROJ = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"
427         def ptype(self):
428                 return self.VS_GUID_VCPROJ
429
430         def __init__(self, ctx, node):
431                 vsnode.__init__(self, ctx)
432                 self.path = node
433                 self.uuid = make_uuid(node.abspath())
434                 self.name = node.name
435                 self.title = self.path.abspath()
436                 self.source = [] # list of node objects
437                 self.build_properties = [] # list of properties (nmake commands, output dir, etc)
438
439         def dirs(self):
440                 """
441                 Get the list of parent folders of the source files (header files included)
442                 for writing the filters
443                 """
444                 lst = []
445                 for x in self.source:
446                         if x.parent not in lst:
447                                 lst.append(x.parent)
448                 return lst
449
450         def write(self):
451                 Logs.warn('Creating %r' % self.path)
452
453                 # first write the project file
454                 template1 = compile_template(PROJECT_TEMPLATE)
455                 proj_str = template1(self)
456                 proj_str = rm_blank_lines(proj_str)
457                 self.path.write(proj_str)
458
459                 # then write the filter
460                 template2 = compile_template(FILTER_TEMPLATE)
461                 filter_str = template2(self)
462                 filter_str = rm_blank_lines(filter_str)
463                 tmp = self.path.parent.make_node(self.path.name + '.filters')
464                 tmp.write(filter_str)
465
466         def get_key(self, node):
467                 """
468                 required for writing the source files
469                 """
470                 name = node.name
471                 if name.endswith('.cpp') or name.endswith('.c'):
472                         return 'ClCompile'
473                 return 'ClInclude'
474
475         def collect_properties(self):
476                 """
477                 Returns a list of triplet (configuration, platform, output_directory)
478                 """
479                 ret = []
480                 for c in self.ctx.configurations:
481                         for p in self.ctx.platforms:
482                                 x = build_property()
483                                 x.outdir = ''
484
485                                 x.configuration = c
486                                 x.platform = p
487
488                                 x.preprocessor_definitions = ''
489                                 x.includes_search_path = ''
490
491                                 # can specify "deploy_dir" too
492                                 ret.append(x)
493                 self.build_properties = ret
494
495         def get_build_params(self, props):
496                 opt = '--execsolution=%s' % self.ctx.get_solution_node().abspath()
497                 return (self.get_waf(), opt)
498
499         def get_build_command(self, props):
500                 return "%s build %s" % self.get_build_params(props)
501
502         def get_clean_command(self, props):
503                 return "%s clean %s" % self.get_build_params(props)
504
505         def get_rebuild_command(self, props):
506                 return "%s clean build %s" % self.get_build_params(props)
507
508 class vsnode_alias(vsnode_project):
509         def __init__(self, ctx, node, name):
510                 vsnode_project.__init__(self, ctx, node)
511                 self.name = name
512                 self.output_file = ''
513
514 class vsnode_build_all(vsnode_alias):
515         """
516         Fake target used to emulate the behaviour of "make all" (starting one process by target is slow)
517         This is the only alias enabled by default
518         """
519         def __init__(self, ctx, node, name='build_all_projects'):
520                 vsnode_alias.__init__(self, ctx, node, name)
521                 self.is_active = True
522
523 class vsnode_install_all(vsnode_alias):
524         """
525         Fake target used to emulate the behaviour of "make install"
526         """
527         def __init__(self, ctx, node, name='install_all_projects'):
528                 vsnode_alias.__init__(self, ctx, node, name)
529
530         def get_build_command(self, props):
531                 return "%s build install %s" % self.get_build_params(props)
532
533         def get_clean_command(self, props):
534                 return "%s clean %s" % self.get_build_params(props)
535
536         def get_rebuild_command(self, props):
537                 return "%s clean build install %s" % self.get_build_params(props)
538
539 class vsnode_target(vsnode_project):
540         """
541         Visual studio project representing a targets (programs, libraries, etc) and bound
542         to a task generator
543         """
544         def __init__(self, ctx, tg):
545                 """
546                 A project is more or less equivalent to a file/folder
547                 """
548                 base = getattr(ctx, 'projects_dir', None) or tg.path
549                 node = base.make_node(tg.name + ctx.project_extension) # the project file as a Node
550                 vsnode_project.__init__(self, ctx, node)
551                 self.tg     = tg  # task generator
552
553         def get_build_params(self, props):
554                 """
555                 Override the default to add the target name
556                 """
557                 opt = '--execsolution=%s' % self.ctx.get_solution_node().abspath()
558                 if getattr(self, 'tg', None):
559                         opt += " --targets=%s" % self.tg.name
560                 return (self.get_waf(), opt)
561
562         def collect_source(self):
563                 tg = self.tg
564                 source_files = tg.to_nodes(getattr(tg, 'source', []))
565                 include_dirs = Utils.to_list(getattr(tg, 'includes', [])) + Utils.to_list(getattr(tg, 'export_includes', []))
566                 include_files = []
567                 for x in include_dirs:
568                         if isinstance(x, str):
569                                 x = tg.path.find_node(x)
570                         if x:
571                                 lst = [y for y in x.ant_glob(HEADERS_GLOB, flat=False)]
572                                 include_files.extend(lst)
573
574                 # remove duplicates
575                 self.source.extend(list(set(source_files + include_files)))
576                 self.source.sort(key=lambda x: x.abspath())
577
578         def collect_properties(self):
579                 """
580                 Visual studio projects are associated with platforms and configurations (for building especially)
581                 """
582                 super(vsnode_target, self).collect_properties()
583                 for x in self.build_properties:
584                         x.outdir = self.path.parent.abspath()
585                         x.preprocessor_definitions = ''
586                         x.includes_search_path = ''
587
588                         try:
589                                 tsk = self.tg.link_task
590                         except AttributeError:
591                                 pass
592                         else:
593                                 x.output_file = tsk.outputs[0].abspath()
594                                 x.preprocessor_definitions = ';'.join(tsk.env.DEFINES)
595                                 x.includes_search_path = ';'.join(self.tg.env.INCPATHS)
596
597 class msvs_generator(BuildContext):
598         cmd = 'msvs'
599         fun = 'build'
600
601         def init(self):
602                 """
603                 Some data that needs to be present
604                 """
605                 if not getattr(self, 'configurations', None):
606                         self.configurations = ['Release'] # LocalRelease, RemoteDebug, etc
607                 if not getattr(self, 'platforms', None):
608                         self.platforms = ['Win32']
609                 if not getattr(self, 'all_projects', None):
610                         self.all_projects = []
611                 if not getattr(self, 'project_extension', None):
612                         self.project_extension = '.vcxproj'
613                 if not getattr(self, 'projects_dir', None):
614                         self.projects_dir = self.srcnode.make_node('.depproj')
615                         self.projects_dir.mkdir()
616
617                 # bind the classes to the object, so that subclass can provide custom generators
618                 if not getattr(self, 'vsnode_vsdir', None):
619                         self.vsnode_vsdir = vsnode_vsdir
620                 if not getattr(self, 'vsnode_target', None):
621                         self.vsnode_target = vsnode_target
622                 if not getattr(self, 'vsnode_build_all', None):
623                         self.vsnode_build_all = vsnode_build_all
624                 if not getattr(self, 'vsnode_install_all', None):
625                         self.vsnode_install_all = vsnode_install_all
626
627                 self.numver = '11.0'
628                 self.vsver  = '2010'
629
630         def execute(self):
631                 """
632                 Entry point
633                 """
634                 self.restore()
635                 if not self.all_envs:
636                         self.load_envs()
637                 self.recurse([self.run_dir])
638
639                 # user initialization
640                 self.init()
641
642                 # two phases for creating the solution
643                 self.collect_projects() # add project objects into "self.all_projects"
644                 self.write_files() # write the corresponding project and solution files
645
646         def collect_projects(self):
647                 """
648                 Fill the list self.all_projects with project objects
649                 Fill the list of build targets
650                 """
651                 self.collect_targets()
652                 self.add_aliases()
653                 self.collect_dirs()
654                 self.all_projects.sort(key=lambda x: getattr(x, 'path', None) and x.path.abspath() or x.name)
655
656         def write_files(self):
657                 """
658                 Write the project and solution files from the data collected
659                 so far. It is unlikely that you will want to change this
660                 """
661                 for p in self.all_projects:
662                         p.write()
663
664                 # and finally write the solution file
665                 node = self.get_solution_node()
666                 node.parent.mkdir()
667                 Logs.warn('Creating %r' % node)
668                 template1 = compile_template(SOLUTION_TEMPLATE)
669                 sln_str = template1(self)
670                 sln_str = rm_blank_lines(sln_str)
671                 node.write(sln_str)
672
673         def get_solution_node(self):
674                 """
675                 The solution filename is required when writing the .vcproj files
676                 return self.solution_node and if it does not exist, make one
677                 """
678                 try:
679                         return self.solution_node
680                 except:
681                         pass
682
683                 solution_name = getattr(self, 'solution_name', None)
684                 if not solution_name:
685                         solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '.sln'
686                 if os.path.isabs(solution_name):
687                         self.solution_node = self.root.make_node(solution_name)
688                 else:
689                         self.solution_node = self.srcnode.make_node(solution_name)
690                 return self.solution_node
691
692         def project_configurations(self):
693                 """
694                 Helper that returns all the pairs (config,platform)
695                 """
696                 ret = []
697                 for c in self.configurations:
698                         for p in self.platforms:
699                                 ret.append((c, p))
700                 return ret
701
702         def collect_targets(self):
703                 """
704                 Process the list of task generators
705                 """
706                 for g in self.groups:
707                         for tg in g:
708                                 if not isinstance(tg, TaskGen.task_gen):
709                                         continue
710
711                                 tg.post()
712                                 if not getattr(tg, 'link_task', None):
713                                         continue
714
715                                 p = self.vsnode_target(self, tg)
716                                 p.collect_source() # delegate this processing
717                                 p.collect_properties()
718                                 self.all_projects.append(p)
719
720         def add_aliases(self):
721                 """
722                 Add a specific target that emulates the "make all" necessary for Visual studio when pressing F7
723                 We also add an alias for "make install" (disabled by default)
724                 """
725                 base = getattr(self, 'projects_dir', None) or tg.path
726
727                 node_project = base.make_node('build_all_projects' + self.project_extension) # Node
728                 p_build = self.vsnode_build_all(self, node_project)
729                 p_build.collect_properties()
730                 self.all_projects.append(p_build)
731
732                 node_project = base.make_node('install_all_projects' + self.project_extension) # Node
733                 p_install = self.vsnode_install_all(self, node_project)
734                 p_install.collect_properties()
735                 self.all_projects.append(p_install)
736
737                 n = self.vsnode_vsdir(self, make_uuid(self.srcnode.abspath() + 'build_aliases'), "build_aliases")
738                 p_build.parent = p_install.parent = n
739                 self.all_projects.append(n)
740
741         def collect_dirs(self):
742                 """
743                 Create the folder structure in the Visual studio project view
744                 """
745                 seen = set([])
746                 def make_parents(proj):
747                         # look at a project, try to make a parent
748                         if proj.iter_path in seen:
749                                 return
750                         seen.add(proj.iter_path)
751
752                         # create a project representing the folder "x"
753
754                         x = proj.iter_path
755                         n = self.vsnode_vsdir(self, make_uuid(x.abspath()), x.name)
756                         n.iter_path = x.parent
757                         proj.parent = n
758                         self.all_projects.append(n)
759
760                         # recurse up to the project directory
761                         if x.height() > self.srcnode.height() + 1:
762                                 make_parents(n)
763
764                 for p in self.all_projects[:]: # iterate over a copy of all projects
765                         if not getattr(p, 'tg', None):
766                                 # but only projects that have a task generator
767                                 continue
768
769                         # make a folder for each task generator
770                         p.iter_path = p.tg.path
771                         make_parents(p)
772
773 def wrap_2008(cls):
774         class dec(cls):
775                 def __init__(self, *k, **kw):
776                         cls.__init__(self, *k, **kw)
777                         self.project_template = PROJECT_2008_TEMPLATE
778                 def get_key(self, node):
779                         """
780                         required for writing the source files.
781                         If empty, visual studio uses the file extension,
782                         else values are:
783                                 0: C/C++ Code, 1: C++ Class, 2: C++ Header File, 3: C++ Form,
784                                 4: C++ Control, 5: Text File, 6: DEF File, 7: IDL File,
785                                 8: Makefile, 9: RGS File, 10: RC File, 11: RES File, 12: XSD File,
786                                 13: XML File, 14: HTML File, 15: CSS File, 16: Bitmap, 17: Icon,
787                                 18: Resx File, 19: BSC File, 20: XSX File, 21: C++ Web Service,
788                                 22: ASAX File, 23: Asp Page, 24: Document, 25: Discovery File,
789                                 26: C# File, 27: eFileTypeClassDiagram, 28: MHTML Document,
790                                 29: Property Sheet, 30: Cursor, 31: Manifest, 32: eFileTypeRDLC
791                         """
792                         return ''
793                 def write(self):
794                         Logs.warn('Creating %r' % self.path)
795                         # write the project file only (no filters in vs2008)
796                         template1 = compile_template(self.project_template)
797                         proj_str = template1(self)
798                         proj_str = rm_blank_lines(proj_str)
799                         self.path.write(proj_str)
800         return dec
801
802 class msvs_2008_generator(msvs_generator):
803         cmd = 'msvs2008'
804         fun = msvs_generator.fun
805         __doc__ = msvs_generator.__doc__
806
807         def init(self):
808                 if not getattr(self, 'project_extension', None):
809                         self.project_extension = '_2008.vcproj'
810                 if not getattr(self, 'solution_name', None):
811                         self.solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '_2008.sln'
812
813                 if not getattr(self, 'vsnode_target', None):
814                         self.vsnode_target = wrap_2008(vsnode_target)
815                 if not getattr(self, 'vsnode_build_all', None):
816                         self.vsnode_build_all = wrap_2008(vsnode_build_all)
817                 if not getattr(self, 'vsnode_install_all', None):
818                         self.vsnode_install_all = wrap_2008(vsnode_install_all)
819
820                 msvs_generator.init(self)
821                 self.numver = '10.0'
822                 self.vsver  = '2008'
823
824 def options(ctx):
825         """
826         If the msvs option is used, try to detect if the build is made from visual studio
827         """
828         ctx.add_option('--execsolution', action='store', help='when building with visual studio, use a build state file')
829
830         old = BuildContext.execute
831         def override_build_state(ctx):
832                 def lock(rm, add):
833                         uns = ctx.options.execsolution.replace('.sln', rm)
834                         uns = ctx.root.make_node(uns)
835                         try:
836                                 uns.delete()
837                         except:
838                                 pass
839
840                         uns = ctx.options.execsolution.replace('.sln', add)
841                         uns = ctx.root.make_node(uns)
842                         try:
843                                 uns.write('')
844                         except:
845                                 pass
846
847                 if ctx.options.execsolution:
848                         ctx.launch_dir = Context.top_dir # force a build for the whole project (invalid cwd when called by visual studio)
849                         lock('.lastbuildstate', '.unsuccessfulbuild')
850                         old(ctx)
851                         lock('.unsuccessfulbuild', '.lastbuildstate')
852                 else:
853                         old(ctx)
854         BuildContext.execute = override_build_state
855