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