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("&", "&").replace('"', """).replace("'", "'").replace("<", "<").replace(">", ">") 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("&", "&").replace('"', """).replace("'", "'").replace("<", "<").replace(">", ">") 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