1#! /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
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
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
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
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
72To 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
80ASSUMPTIONS:
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
85import os, re, sys
86import uuid # requires python 2.5
87from waflib.Build import BuildContext
88from waflib import Utils, TaskGen, Logs, Task, Context, Node, Options
89
90HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
91
92PROJECT_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
170FILTER_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
189PROJECT_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
243SOLUTION_TEMPLATE = '''Microsoft Visual Studio Solution File, Format Version ${project.numver}
244# Visual Studio ${project.vsver}
245${for p in project.all_projects}
246Project("{${p.ptype()}}") = "${p.name}", "${p.title}", "{${p.uuid}}"
247EndProject${endfor}
248Global
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
281EndGlobal
282'''
283
284COMPILE_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'''
296reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<code>[^}]*?)\})", re.M)
297def 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
356re_blank = re.compile('(\n|\r|\\s)*\n', re.M)
357def rm_blank_lines(txt):
358	txt = re_blank.sub('\r\n', txt)
359	return txt
360
361BOM = '\xef\xbb\xbf'
362try:
363	BOM = bytes(BOM, 'latin-1') # python 3
364except TypeError:
365	pass
366
367def 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())
387Node.Node.stealth_write = stealth_write
388
389re_win32 = re.compile(r'^([/\\]cygdrive)?[/\\]([a-z])([^a-z0-9_-].*)', re.I)
390def 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
396Node.Node.win32path = win32path
397
398re_quote = re.compile("[^a-zA-Z0-9-]")
399def quote(s):
400	return re_quote.sub("_", s)
401
402def xml_escape(value):
403	return value.replace("&", "&amp;").replace('"', "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
404
405def 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
421def 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
454class build_property(object):
455	pass
456
457class 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
493class 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
507class 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
602class 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
608class 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
617class 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
633class 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 + '''
641waf-2*
642waf3-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
665class 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
724class 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
922def 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
993class 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
1017def 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
1049