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