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