waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

msvs.py (31198B)


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