third_party/waf: upgrade to waf 2.0.8
[samba.git] / third_party / waf / 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 The msvs class re-uses the same build() function for reading the targets (task generators),
60 you may therefore specify msvs settings on the context object::
61
62         def build(bld):
63                 bld.solution_name = 'foo.sln'
64                 bld.waf_command = 'waf.bat'
65                 bld.projects_dir = bld.srcnode.make_node('.depproj')
66                 bld.projects_dir.mkdir()
67
68 For visual studio 2008, the command is called 'msvs2008', and the classes
69 such as vsnode_target are wrapped by a decorator class 'wrap_2008' to
70 provide special functionality.
71
72 To customize platform toolsets, pass additional parameters, for example::
73
74         class msvs_2013(msvs.msvs_generator):
75                 cmd = 'msvs2013'
76                 numver = '13.00'
77                 vsver = '2013'
78                 platform_toolset_ver = 'v120'
79
80 ASSUMPTIONS:
81 * a project can be either a directory or a target, vcxproj files are written only for targets that have source files
82 * each project is a vcxproj file, therefore the project uuid needs only to be a hash of the absolute path
83 """
84
85 import os, re, sys
86 import uuid # requires python 2.5
87 from waflib.Build import BuildContext
88 from waflib import Utils, TaskGen, Logs, Task, Context, Node, Options
89
90 HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
91
92 PROJECT_TEMPLATE = r'''<?xml version="1.0" encoding="UTF-8"?>
93 <Project DefaultTargets="Build" ToolsVersion="4.0"
94         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
95
96         <ItemGroup Label="ProjectConfigurations">
97                 ${for b in project.build_properties}
98                 <ProjectConfiguration Include="${b.configuration}|${b.platform}">
99                         <Configuration>${b.configuration}</Configuration>
100                         <Platform>${b.platform}</Platform>
101                 </ProjectConfiguration>
102                 ${endfor}
103         </ItemGroup>
104
105         <PropertyGroup Label="Globals">
106                 <ProjectGuid>{${project.uuid}}</ProjectGuid>
107                 <Keyword>MakeFileProj</Keyword>
108                 <ProjectName>${project.name}</ProjectName>
109         </PropertyGroup>
110         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
111
112         ${for b in project.build_properties}
113         <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'" Label="Configuration">
114                 <ConfigurationType>Makefile</ConfigurationType>
115                 <OutDir>${b.outdir}</OutDir>
116                 <PlatformToolset>${project.platform_toolset_ver}</PlatformToolset>
117         </PropertyGroup>
118         ${endfor}
119
120         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
121         <ImportGroup Label="ExtensionSettings">
122         </ImportGroup>
123
124         ${for b in project.build_properties}
125         <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
126                 <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
127         </ImportGroup>
128         ${endfor}
129
130         ${for b in project.build_properties}
131         <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
132                 <NMakeBuildCommandLine>${xml:project.get_build_command(b)}</NMakeBuildCommandLine>
133                 <NMakeReBuildCommandLine>${xml:project.get_rebuild_command(b)}</NMakeReBuildCommandLine>
134                 <NMakeCleanCommandLine>${xml:project.get_clean_command(b)}</NMakeCleanCommandLine>
135                 <NMakeIncludeSearchPath>${xml:b.includes_search_path}</NMakeIncludeSearchPath>
136                 <NMakePreprocessorDefinitions>${xml:b.preprocessor_definitions};$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
137                 <IncludePath>${xml:b.includes_search_path}</IncludePath>
138                 <ExecutablePath>$(ExecutablePath)</ExecutablePath>
139
140                 ${if getattr(b, 'output_file', None)}
141                 <NMakeOutput>${xml:b.output_file}</NMakeOutput>
142                 ${endif}
143                 ${if getattr(b, 'deploy_dir', None)}
144                 <RemoteRoot>${xml:b.deploy_dir}</RemoteRoot>
145                 ${endif}
146         </PropertyGroup>
147         ${endfor}
148
149         ${for b in project.build_properties}
150                 ${if getattr(b, 'deploy_dir', None)}
151         <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='${b.configuration}|${b.platform}'">
152                 <Deploy>
153                         <DeploymentType>CopyToHardDrive</DeploymentType>
154                 </Deploy>
155         </ItemDefinitionGroup>
156                 ${endif}
157         ${endfor}
158
159         <ItemGroup>
160                 ${for x in project.source}
161                 <${project.get_key(x)} Include='${x.win32path()}' />
162                 ${endfor}
163         </ItemGroup>
164         <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
165         <ImportGroup Label="ExtensionTargets">
166         </ImportGroup>
167 </Project>
168 '''
169
170 FILTER_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
171 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
172         <ItemGroup>
173                 ${for x in project.source}
174                         <${project.get_key(x)} Include="${x.win32path()}">
175                                 <Filter>${project.get_filter_name(x.parent)}</Filter>
176                         </${project.get_key(x)}>
177                 ${endfor}
178         </ItemGroup>
179         <ItemGroup>
180                 ${for x in project.dirs()}
181                         <Filter Include="${project.get_filter_name(x)}">
182                                 <UniqueIdentifier>{${project.make_uuid(x.win32path())}}</UniqueIdentifier>
183                         </Filter>
184                 ${endfor}
185         </ItemGroup>
186 </Project>
187 '''
188
189 PROJECT_2008_TEMPLATE = r'''<?xml version="1.0" encoding="UTF-8"?>
190 <VisualStudioProject ProjectType="Visual C++" Version="9,00"
191         Name="${xml: project.name}" ProjectGUID="{${project.uuid}}"
192         Keyword="MakeFileProj"
193         TargetFrameworkVersion="196613">
194         <Platforms>
195                 ${if project.build_properties}
196                 ${for b in project.build_properties}
197                    <Platform Name="${xml: b.platform}" />
198                 ${endfor}
199                 ${else}
200                    <Platform Name="Win32" />
201                 ${endif}
202         </Platforms>
203         <ToolFiles>
204         </ToolFiles>
205         <Configurations>
206                 ${if project.build_properties}
207                 ${for b in project.build_properties}
208                 <Configuration
209                         Name="${xml: b.configuration}|${xml: b.platform}"
210                         IntermediateDirectory="$ConfigurationName"
211                         OutputDirectory="${xml: b.outdir}"
212                         ConfigurationType="0">
213                         <Tool
214                                 Name="VCNMakeTool"
215                                 BuildCommandLine="${xml: project.get_build_command(b)}"
216                                 ReBuildCommandLine="${xml: project.get_rebuild_command(b)}"
217                                 CleanCommandLine="${xml: project.get_clean_command(b)}"
218                                 ${if getattr(b, 'output_file', None)}
219                                 Output="${xml: b.output_file}"
220                                 ${endif}
221                                 PreprocessorDefinitions="${xml: b.preprocessor_definitions}"
222                                 IncludeSearchPath="${xml: b.includes_search_path}"
223                                 ForcedIncludes=""
224                                 ForcedUsingAssemblies=""
225                                 AssemblySearchPath=""
226                                 CompileAsManaged=""
227                         />
228                 </Configuration>
229                 ${endfor}
230                 ${else}
231                         <Configuration Name="Release|Win32" >
232                 </Configuration>
233                 ${endif}
234         </Configurations>
235         <References>
236         </References>
237         <Files>
238 ${project.display_filter()}
239         </Files>
240 </VisualStudioProject>
241 '''
242
243 SOLUTION_TEMPLATE = '''Microsoft Visual Studio Solution File, Format Version ${project.numver}
244 # Visual Studio ${project.vsver}
245 ${for p in project.all_projects}
246 Project("{${p.ptype()}}") = "${p.name}", "${p.title}", "{${p.uuid}}"
247 EndProject${endfor}
248 Global
249         GlobalSection(SolutionConfigurationPlatforms) = preSolution
250                 ${if project.all_projects}
251                 ${for (configuration, platform) in project.all_projects[0].ctx.project_configurations()}
252                 ${configuration}|${platform} = ${configuration}|${platform}
253                 ${endfor}
254                 ${endif}
255         EndGlobalSection
256         GlobalSection(ProjectConfigurationPlatforms) = postSolution
257                 ${for p in project.all_projects}
258                         ${if hasattr(p, 'source')}
259                         ${for b in p.build_properties}
260                 {${p.uuid}}.${b.configuration}|${b.platform}.ActiveCfg = ${b.configuration}|${b.platform}
261                         ${if getattr(p, 'is_active', None)}
262                 {${p.uuid}}.${b.configuration}|${b.platform}.Build.0 = ${b.configuration}|${b.platform}
263                         ${endif}
264                         ${if getattr(p, 'is_deploy', None)}
265                 {${p.uuid}}.${b.configuration}|${b.platform}.Deploy.0 = ${b.configuration}|${b.platform}
266                         ${endif}
267                         ${endfor}
268                         ${endif}
269                 ${endfor}
270         EndGlobalSection
271         GlobalSection(SolutionProperties) = preSolution
272                 HideSolutionNode = FALSE
273         EndGlobalSection
274         GlobalSection(NestedProjects) = preSolution
275         ${for p in project.all_projects}
276                 ${if p.parent}
277                 {${p.uuid}} = {${p.parent.uuid}}
278                 ${endif}
279         ${endfor}
280         EndGlobalSection
281 EndGlobal
282 '''
283
284 COMPILE_TEMPLATE = '''def f(project):
285         lst = []
286         def xml_escape(value):
287                 return value.replace("&", "&amp;").replace('"', "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
288
289         %s
290
291         #f = open('cmd.txt', 'w')
292         #f.write(str(lst))
293         #f.close()
294         return ''.join(lst)
295 '''
296 reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<code>[^}]*?)\})", re.M)
297 def compile_template(line):
298         """
299         Compile a template expression into a python function (like jsps, but way shorter)
300         """
301         extr = []
302         def repl(match):
303                 g = match.group
304                 if g('dollar'):
305                         return "$"
306                 elif g('backslash'):
307                         return "\\"
308                 elif g('subst'):
309                         extr.append(g('code'))
310                         return "<<|@|>>"
311                 return None
312
313         line2 = reg_act.sub(repl, line)
314         params = line2.split('<<|@|>>')
315         assert(extr)
316
317
318         indent = 0
319         buf = []
320         app = buf.append
321
322         def app(txt):
323                 buf.append(indent * '\t' + txt)
324
325         for x in range(len(extr)):
326                 if params[x]:
327                         app("lst.append(%r)" % params[x])
328
329                 f = extr[x]
330                 if f.startswith(('if', 'for')):
331                         app(f + ':')
332                         indent += 1
333                 elif f.startswith('py:'):
334                         app(f[3:])
335                 elif f.startswith(('endif', 'endfor')):
336                         indent -= 1
337                 elif f.startswith(('else', 'elif')):
338                         indent -= 1
339                         app(f + ':')
340                         indent += 1
341                 elif f.startswith('xml:'):
342                         app('lst.append(xml_escape(%s))' % f[4:])
343                 else:
344                         #app('lst.append((%s) or "cannot find %s")' % (f, f))
345                         app('lst.append(%s)' % f)
346
347         if extr:
348                 if params[-1]:
349                         app("lst.append(%r)" % params[-1])
350
351         fun = COMPILE_TEMPLATE % "\n\t".join(buf)
352         #print(fun)
353         return Task.funex(fun)
354
355
356 re_blank = re.compile('(\n|\r|\\s)*\n', re.M)
357 def rm_blank_lines(txt):
358         txt = re_blank.sub('\r\n', txt)
359         return txt
360
361 BOM = '\xef\xbb\xbf'
362 try:
363         BOM = bytes(BOM, 'latin-1') # python 3
364 except TypeError:
365         pass
366
367 def stealth_write(self, data, flags='wb'):
368         try:
369                 unicode
370         except NameError:
371                 data = data.encode('utf-8') # python 3
372         else:
373                 data = data.decode(sys.getfilesystemencoding(), 'replace')
374                 data = data.encode('utf-8')
375
376         if self.name.endswith(('.vcproj', '.vcxproj')):
377                 data = BOM + data
378
379         try:
380                 txt = self.read(flags='rb')
381                 if txt != data:
382                         raise ValueError('must write')
383         except (IOError, ValueError):
384                 self.write(data, flags=flags)
385         else:
386                 Logs.debug('msvs: skipping %s', self.win32path())
387 Node.Node.stealth_write = stealth_write
388
389 re_win32 = re.compile(r'^([/\\]cygdrive)?[/\\]([a-z])([^a-z0-9_-].*)', re.I)
390 def win32path(self):
391         p = self.abspath()
392         m = re_win32.match(p)
393         if m:
394                 return "%s:%s" % (m.group(2).upper(), m.group(3))
395         return p
396 Node.Node.win32path = win32path
397
398 re_quote = re.compile("[^a-zA-Z0-9-]")
399 def quote(s):
400         return re_quote.sub("_", s)
401
402 def xml_escape(value):
403         return value.replace("&", "&amp;").replace('"', "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
404
405 def make_uuid(v, prefix = None):
406         """
407         simple utility function
408         """
409         if isinstance(v, dict):
410                 keys = list(v.keys())
411                 keys.sort()
412                 tmp = str([(k, v[k]) for k in keys])
413         else:
414                 tmp = str(v)
415         d = Utils.md5(tmp.encode()).hexdigest().upper()
416         if prefix:
417                 d = '%s%s' % (prefix, d[8:])
418         gid = uuid.UUID(d, version = 4)
419         return str(gid).upper()
420
421 def diff(node, fromnode):
422         # difference between two nodes, but with "(..)" instead of ".."
423         c1 = node
424         c2 = fromnode
425
426         c1h = c1.height()
427         c2h = c2.height()
428
429         lst = []
430         up = 0
431
432         while c1h > c2h:
433                 lst.append(c1.name)
434                 c1 = c1.parent
435                 c1h -= 1
436
437         while c2h > c1h:
438                 up += 1
439                 c2 = c2.parent
440                 c2h -= 1
441
442         while id(c1) != id(c2):
443                 lst.append(c1.name)
444                 up += 1
445
446                 c1 = c1.parent
447                 c2 = c2.parent
448
449         for i in range(up):
450                 lst.append('(..)')
451         lst.reverse()
452         return tuple(lst)
453
454 class build_property(object):
455         pass
456
457 class vsnode(object):
458         """
459         Abstract class representing visual studio elements
460         We assume that all visual studio nodes have a uuid and a parent
461         """
462         def __init__(self, ctx):
463                 self.ctx = ctx # msvs context
464                 self.name = '' # string, mandatory
465                 self.vspath = '' # path in visual studio (name for dirs, absolute path for projects)
466                 self.uuid = '' # string, mandatory
467                 self.parent = None # parent node for visual studio nesting
468
469         def get_waf(self):
470                 """
471                 Override in subclasses...
472                 """
473                 return 'cd /d "%s" & %s' % (self.ctx.srcnode.win32path(), getattr(self.ctx, 'waf_command', 'waf.bat'))
474
475         def ptype(self):
476                 """
477                 Return a special uuid for projects written in the solution file
478                 """
479                 pass
480
481         def write(self):
482                 """
483                 Write the project file, by default, do nothing
484                 """
485                 pass
486
487         def make_uuid(self, val):
488                 """
489                 Alias for creating uuid values easily (the templates cannot access global variables)
490                 """
491                 return make_uuid(val)
492
493 class vsnode_vsdir(vsnode):
494         """
495         Nodes representing visual studio folders (which do not match the filesystem tree!)
496         """
497         VS_GUID_SOLUTIONFOLDER = "2150E333-8FDC-42A3-9474-1A3956D46DE8"
498         def __init__(self, ctx, uuid, name, vspath=''):
499                 vsnode.__init__(self, ctx)
500                 self.title = self.name = name
501                 self.uuid = uuid
502                 self.vspath = vspath or name
503
504         def ptype(self):
505                 return self.VS_GUID_SOLUTIONFOLDER
506
507 class vsnode_project(vsnode):
508         """
509         Abstract class representing visual studio project elements
510         A project is assumed to be writable, and has a node representing the file to write to
511         """
512         VS_GUID_VCPROJ = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"
513         def ptype(self):
514                 return self.VS_GUID_VCPROJ
515
516         def __init__(self, ctx, node):
517                 vsnode.__init__(self, ctx)
518                 self.path = node
519                 self.uuid = make_uuid(node.win32path())
520                 self.name = node.name
521                 self.platform_toolset_ver = getattr(ctx, 'platform_toolset_ver', None)
522                 self.title = self.path.win32path()
523                 self.source = [] # list of node objects
524                 self.build_properties = [] # list of properties (nmake commands, output dir, etc)
525
526         def dirs(self):
527                 """
528                 Get the list of parent folders of the source files (header files included)
529                 for writing the filters
530                 """
531                 lst = []
532                 def add(x):
533                         if x.height() > self.tg.path.height() and x not in lst:
534                                 lst.append(x)
535                                 add(x.parent)
536                 for x in self.source:
537                         add(x.parent)
538                 return lst
539
540         def write(self):
541                 Logs.debug('msvs: creating %r', self.path)
542
543                 # first write the project file
544                 template1 = compile_template(PROJECT_TEMPLATE)
545                 proj_str = template1(self)
546                 proj_str = rm_blank_lines(proj_str)
547                 self.path.stealth_write(proj_str)
548
549                 # then write the filter
550                 template2 = compile_template(FILTER_TEMPLATE)
551                 filter_str = template2(self)
552                 filter_str = rm_blank_lines(filter_str)
553                 tmp = self.path.parent.make_node(self.path.name + '.filters')
554                 tmp.stealth_write(filter_str)
555
556         def get_key(self, node):
557                 """
558                 required for writing the source files
559                 """
560                 name = node.name
561                 if name.endswith(('.cpp', '.c')):
562                         return 'ClCompile'
563                 return 'ClInclude'
564
565         def collect_properties(self):
566                 """
567                 Returns a list of triplet (configuration, platform, output_directory)
568                 """
569                 ret = []
570                 for c in self.ctx.configurations:
571                         for p in self.ctx.platforms:
572                                 x = build_property()
573                                 x.outdir = ''
574
575                                 x.configuration = c
576                                 x.platform = p
577
578                                 x.preprocessor_definitions = ''
579                                 x.includes_search_path = ''
580
581                                 # can specify "deploy_dir" too
582                                 ret.append(x)
583                 self.build_properties = ret
584
585         def get_build_params(self, props):
586                 opt = '--execsolution=%s' % self.ctx.get_solution_node().win32path()
587                 return (self.get_waf(), opt)
588
589         def get_build_command(self, props):
590                 return "%s build %s" % self.get_build_params(props)
591
592         def get_clean_command(self, props):
593                 return "%s clean %s" % self.get_build_params(props)
594
595         def get_rebuild_command(self, props):
596                 return "%s clean build %s" % self.get_build_params(props)
597
598         def get_filter_name(self, node):
599                 lst = diff(node, self.tg.path)
600                 return '\\'.join(lst) or '.'
601
602 class vsnode_alias(vsnode_project):
603         def __init__(self, ctx, node, name):
604                 vsnode_project.__init__(self, ctx, node)
605                 self.name = name
606                 self.output_file = ''
607
608 class vsnode_build_all(vsnode_alias):
609         """
610         Fake target used to emulate the behaviour of "make all" (starting one process by target is slow)
611         This is the only alias enabled by default
612         """
613         def __init__(self, ctx, node, name='build_all_projects'):
614                 vsnode_alias.__init__(self, ctx, node, name)
615                 self.is_active = True
616
617 class vsnode_install_all(vsnode_alias):
618         """
619         Fake target used to emulate the behaviour of "make install"
620         """
621         def __init__(self, ctx, node, name='install_all_projects'):
622                 vsnode_alias.__init__(self, ctx, node, name)
623
624         def get_build_command(self, props):
625                 return "%s build install %s" % self.get_build_params(props)
626
627         def get_clean_command(self, props):
628                 return "%s clean %s" % self.get_build_params(props)
629
630         def get_rebuild_command(self, props):
631                 return "%s clean build install %s" % self.get_build_params(props)
632
633 class vsnode_project_view(vsnode_alias):
634         """
635         Fake target used to emulate a file system view
636         """
637         def __init__(self, ctx, node, name='project_view'):
638                 vsnode_alias.__init__(self, ctx, node, name)
639                 self.tg = self.ctx() # fake one, cannot remove
640                 self.exclude_files = Node.exclude_regs + '''
641 waf-2*
642 waf3-2*/**
643 .waf-2*
644 .waf3-2*/**
645 **/*.sdf
646 **/*.suo
647 **/*.ncb
648 **/%s
649                 ''' % Options.lockfile
650
651         def collect_source(self):
652                 # this is likely to be slow
653                 self.source = self.ctx.srcnode.ant_glob('**', excl=self.exclude_files)
654
655         def get_build_command(self, props):
656                 params = self.get_build_params(props) + (self.ctx.cmd,)
657                 return "%s %s %s" % params
658
659         def get_clean_command(self, props):
660                 return ""
661
662         def get_rebuild_command(self, props):
663                 return self.get_build_command(props)
664
665 class vsnode_target(vsnode_project):
666         """
667         Visual studio project representing a targets (programs, libraries, etc) and bound
668         to a task generator
669         """
670         def __init__(self, ctx, tg):
671                 """
672                 A project is more or less equivalent to a file/folder
673                 """
674                 base = getattr(ctx, 'projects_dir', None) or tg.path
675                 node = base.make_node(quote(tg.name) + ctx.project_extension) # the project file as a Node
676                 vsnode_project.__init__(self, ctx, node)
677                 self.name = quote(tg.name)
678                 self.tg     = tg  # task generator
679
680         def get_build_params(self, props):
681                 """
682                 Override the default to add the target name
683                 """
684                 opt = '--execsolution=%s' % self.ctx.get_solution_node().win32path()
685                 if getattr(self, 'tg', None):
686                         opt += " --targets=%s" % self.tg.name
687                 return (self.get_waf(), opt)
688
689         def collect_source(self):
690                 tg = self.tg
691                 source_files = tg.to_nodes(getattr(tg, 'source', []))
692                 include_dirs = Utils.to_list(getattr(tg, 'msvs_includes', []))
693                 include_files = []
694                 for x in include_dirs:
695                         if isinstance(x, str):
696                                 x = tg.path.find_node(x)
697                         if x:
698                                 lst = [y for y in x.ant_glob(HEADERS_GLOB, flat=False)]
699                                 include_files.extend(lst)
700
701                 # remove duplicates
702                 self.source.extend(list(set(source_files + include_files)))
703                 self.source.sort(key=lambda x: x.win32path())
704
705         def collect_properties(self):
706                 """
707                 Visual studio projects are associated with platforms and configurations (for building especially)
708                 """
709                 super(vsnode_target, self).collect_properties()
710                 for x in self.build_properties:
711                         x.outdir = self.path.parent.win32path()
712                         x.preprocessor_definitions = ''
713                         x.includes_search_path = ''
714
715                         try:
716                                 tsk = self.tg.link_task
717                         except AttributeError:
718                                 pass
719                         else:
720                                 x.output_file = tsk.outputs[0].win32path()
721                                 x.preprocessor_definitions = ';'.join(tsk.env.DEFINES)
722                                 x.includes_search_path = ';'.join(self.tg.env.INCPATHS)
723
724 class msvs_generator(BuildContext):
725         '''generates a visual studio 2010 solution'''
726         cmd = 'msvs'
727         fun = 'build'
728         numver = '11.00' # Visual Studio Version Number
729         vsver  = '2010'  # Visual Studio Version Year
730         platform_toolset_ver = 'v110' # Platform Toolset Version Number
731
732         def init(self):
733                 """
734                 Some data that needs to be present
735                 """
736                 if not getattr(self, 'configurations', None):
737                         self.configurations = ['Release'] # LocalRelease, RemoteDebug, etc
738                 if not getattr(self, 'platforms', None):
739                         self.platforms = ['Win32']
740                 if not getattr(self, 'all_projects', None):
741                         self.all_projects = []
742                 if not getattr(self, 'project_extension', None):
743                         self.project_extension = '.vcxproj'
744                 if not getattr(self, 'projects_dir', None):
745                         self.projects_dir = self.srcnode.make_node('.depproj')
746                         self.projects_dir.mkdir()
747
748                 # bind the classes to the object, so that subclass can provide custom generators
749                 if not getattr(self, 'vsnode_vsdir', None):
750                         self.vsnode_vsdir = vsnode_vsdir
751                 if not getattr(self, 'vsnode_target', None):
752                         self.vsnode_target = vsnode_target
753                 if not getattr(self, 'vsnode_build_all', None):
754                         self.vsnode_build_all = vsnode_build_all
755                 if not getattr(self, 'vsnode_install_all', None):
756                         self.vsnode_install_all = vsnode_install_all
757                 if not getattr(self, 'vsnode_project_view', None):
758                         self.vsnode_project_view = vsnode_project_view
759
760                 self.numver = self.__class__.numver
761                 self.vsver  = self.__class__.vsver
762                 self.platform_toolset_ver = self.__class__.platform_toolset_ver
763
764         def execute(self):
765                 """
766                 Entry point
767                 """
768                 self.restore()
769                 if not self.all_envs:
770                         self.load_envs()
771                 self.recurse([self.run_dir])
772
773                 # user initialization
774                 self.init()
775
776                 # two phases for creating the solution
777                 self.collect_projects() # add project objects into "self.all_projects"
778                 self.write_files() # write the corresponding project and solution files
779
780         def collect_projects(self):
781                 """
782                 Fill the list self.all_projects with project objects
783                 Fill the list of build targets
784                 """
785                 self.collect_targets()
786                 self.add_aliases()
787                 self.collect_dirs()
788                 default_project = getattr(self, 'default_project', None)
789                 def sortfun(x):
790                         if x.name == default_project:
791                                 return ''
792                         return getattr(x, 'path', None) and x.path.win32path() or x.name
793                 self.all_projects.sort(key=sortfun)
794
795         def write_files(self):
796                 """
797                 Write the project and solution files from the data collected
798                 so far. It is unlikely that you will want to change this
799                 """
800                 for p in self.all_projects:
801                         p.write()
802
803                 # and finally write the solution file
804                 node = self.get_solution_node()
805                 node.parent.mkdir()
806                 Logs.warn('Creating %r', node)
807                 template1 = compile_template(SOLUTION_TEMPLATE)
808                 sln_str = template1(self)
809                 sln_str = rm_blank_lines(sln_str)
810                 node.stealth_write(sln_str)
811
812         def get_solution_node(self):
813                 """
814                 The solution filename is required when writing the .vcproj files
815                 return self.solution_node and if it does not exist, make one
816                 """
817                 try:
818                         return self.solution_node
819                 except AttributeError:
820                         pass
821
822                 solution_name = getattr(self, 'solution_name', None)
823                 if not solution_name:
824                         solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '.sln'
825                 if os.path.isabs(solution_name):
826                         self.solution_node = self.root.make_node(solution_name)
827                 else:
828                         self.solution_node = self.srcnode.make_node(solution_name)
829                 return self.solution_node
830
831         def project_configurations(self):
832                 """
833                 Helper that returns all the pairs (config,platform)
834                 """
835                 ret = []
836                 for c in self.configurations:
837                         for p in self.platforms:
838                                 ret.append((c, p))
839                 return ret
840
841         def collect_targets(self):
842                 """
843                 Process the list of task generators
844                 """
845                 for g in self.groups:
846                         for tg in g:
847                                 if not isinstance(tg, TaskGen.task_gen):
848                                         continue
849
850                                 if not hasattr(tg, 'msvs_includes'):
851                                         tg.msvs_includes = tg.to_list(getattr(tg, 'includes', [])) + tg.to_list(getattr(tg, 'export_includes', []))
852                                 tg.post()
853                                 if not getattr(tg, 'link_task', None):
854                                         continue
855
856                                 p = self.vsnode_target(self, tg)
857                                 p.collect_source() # delegate this processing
858                                 p.collect_properties()
859                                 self.all_projects.append(p)
860
861         def add_aliases(self):
862                 """
863                 Add a specific target that emulates the "make all" necessary for Visual studio when pressing F7
864                 We also add an alias for "make install" (disabled by default)
865                 """
866                 base = getattr(self, 'projects_dir', None) or self.tg.path
867
868                 node_project = base.make_node('build_all_projects' + self.project_extension) # Node
869                 p_build = self.vsnode_build_all(self, node_project)
870                 p_build.collect_properties()
871                 self.all_projects.append(p_build)
872
873                 node_project = base.make_node('install_all_projects' + self.project_extension) # Node
874                 p_install = self.vsnode_install_all(self, node_project)
875                 p_install.collect_properties()
876                 self.all_projects.append(p_install)
877
878                 node_project = base.make_node('project_view' + self.project_extension) # Node
879                 p_view = self.vsnode_project_view(self, node_project)
880                 p_view.collect_source()
881                 p_view.collect_properties()
882                 self.all_projects.append(p_view)
883
884                 n = self.vsnode_vsdir(self, make_uuid(self.srcnode.win32path() + 'build_aliases'), "build_aliases")
885                 p_build.parent = p_install.parent = p_view.parent = n
886                 self.all_projects.append(n)
887
888         def collect_dirs(self):
889                 """
890                 Create the folder structure in the Visual studio project view
891                 """
892                 seen = {}
893                 def make_parents(proj):
894                         # look at a project, try to make a parent
895                         if getattr(proj, 'parent', None):
896                                 # aliases already have parents
897                                 return
898                         x = proj.iter_path
899                         if x in seen:
900                                 proj.parent = seen[x]
901                                 return
902
903                         # There is not vsnode_vsdir for x.
904                         # So create a project representing the folder "x"
905                         n = proj.parent = seen[x] = self.vsnode_vsdir(self, make_uuid(x.win32path()), x.name)
906                         n.iter_path = x.parent
907                         self.all_projects.append(n)
908
909                         # recurse up to the project directory
910                         if x.height() > self.srcnode.height() + 1:
911                                 make_parents(n)
912
913                 for p in self.all_projects[:]: # iterate over a copy of all projects
914                         if not getattr(p, 'tg', None):
915                                 # but only projects that have a task generator
916                                 continue
917
918                         # make a folder for each task generator
919                         p.iter_path = p.tg.path
920                         make_parents(p)
921
922 def wrap_2008(cls):
923         class dec(cls):
924                 def __init__(self, *k, **kw):
925                         cls.__init__(self, *k, **kw)
926                         self.project_template = PROJECT_2008_TEMPLATE
927
928                 def display_filter(self):
929
930                         root = build_property()
931                         root.subfilters = []
932                         root.sourcefiles = []
933                         root.source = []
934                         root.name = ''
935
936                         @Utils.run_once
937                         def add_path(lst):
938                                 if not lst:
939                                         return root
940                                 child = build_property()
941                                 child.subfilters = []
942                                 child.sourcefiles = []
943                                 child.source = []
944                                 child.name = lst[-1]
945
946                                 par = add_path(lst[:-1])
947                                 par.subfilters.append(child)
948                                 return child
949
950                         for x in self.source:
951                                 # this crap is for enabling subclasses to override get_filter_name
952                                 tmp = self.get_filter_name(x.parent)
953                                 tmp = tmp != '.' and tuple(tmp.split('\\')) or ()
954                                 par = add_path(tmp)
955                                 par.source.append(x)
956
957                         def display(n):
958                                 buf = []
959                                 for x in n.source:
960                                         buf.append('<File RelativePath="%s" FileType="%s"/>\n' % (xml_escape(x.win32path()), self.get_key(x)))
961                                 for x in n.subfilters:
962                                         buf.append('<Filter Name="%s">' % xml_escape(x.name))
963                                         buf.append(display(x))
964                                         buf.append('</Filter>')
965                                 return '\n'.join(buf)
966
967                         return display(root)
968
969                 def get_key(self, node):
970                         """
971                         If you do not want to let visual studio use the default file extensions,
972                         override this method to return a value:
973                                 0: C/C++ Code, 1: C++ Class, 2: C++ Header File, 3: C++ Form,
974                                 4: C++ Control, 5: Text File, 6: DEF File, 7: IDL File,
975                                 8: Makefile, 9: RGS File, 10: RC File, 11: RES File, 12: XSD File,
976                                 13: XML File, 14: HTML File, 15: CSS File, 16: Bitmap, 17: Icon,
977                                 18: Resx File, 19: BSC File, 20: XSX File, 21: C++ Web Service,
978                                 22: ASAX File, 23: Asp Page, 24: Document, 25: Discovery File,
979                                 26: C# File, 27: eFileTypeClassDiagram, 28: MHTML Document,
980                                 29: Property Sheet, 30: Cursor, 31: Manifest, 32: eFileTypeRDLC
981                         """
982                         return ''
983
984                 def write(self):
985                         Logs.debug('msvs: creating %r', self.path)
986                         template1 = compile_template(self.project_template)
987                         proj_str = template1(self)
988                         proj_str = rm_blank_lines(proj_str)
989                         self.path.stealth_write(proj_str)
990
991         return dec
992
993 class msvs_2008_generator(msvs_generator):
994         '''generates a visual studio 2008 solution'''
995         cmd = 'msvs2008'
996         fun = msvs_generator.fun
997         numver = '10.00'
998         vsver = '2008'
999
1000         def init(self):
1001                 if not getattr(self, 'project_extension', None):
1002                         self.project_extension = '_2008.vcproj'
1003                 if not getattr(self, 'solution_name', None):
1004                         self.solution_name = getattr(Context.g_module, Context.APPNAME, 'project') + '_2008.sln'
1005
1006                 if not getattr(self, 'vsnode_target', None):
1007                         self.vsnode_target = wrap_2008(vsnode_target)
1008                 if not getattr(self, 'vsnode_build_all', None):
1009                         self.vsnode_build_all = wrap_2008(vsnode_build_all)
1010                 if not getattr(self, 'vsnode_install_all', None):
1011                         self.vsnode_install_all = wrap_2008(vsnode_install_all)
1012                 if not getattr(self, 'vsnode_project_view', None):
1013                         self.vsnode_project_view = wrap_2008(vsnode_project_view)
1014
1015                 msvs_generator.init(self)
1016
1017 def options(ctx):
1018         """
1019         If the msvs option is used, try to detect if the build is made from visual studio
1020         """
1021         ctx.add_option('--execsolution', action='store', help='when building with visual studio, use a build state file')
1022
1023         old = BuildContext.execute
1024         def override_build_state(ctx):
1025                 def lock(rm, add):
1026                         uns = ctx.options.execsolution.replace('.sln', rm)
1027                         uns = ctx.root.make_node(uns)
1028                         try:
1029                                 uns.delete()
1030                         except OSError:
1031                                 pass
1032
1033                         uns = ctx.options.execsolution.replace('.sln', add)
1034                         uns = ctx.root.make_node(uns)
1035                         try:
1036                                 uns.write('')
1037                         except EnvironmentError:
1038                                 pass
1039
1040                 if ctx.options.execsolution:
1041                         ctx.launch_dir = Context.top_dir # force a build for the whole project (invalid cwd when called by visual studio)
1042                         lock('.lastbuildstate', '.unsuccessfulbuild')
1043                         old(ctx)
1044                         lock('.unsuccessfulbuild', '.lastbuildstate')
1045                 else:
1046                         old(ctx)
1047         BuildContext.execute = override_build_state
1048