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