| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python2.4 | |
| 2 # Copyright 2008, Google Inc. | |
| 3 # All rights reserved. | |
| 4 # | |
| 5 # Redistribution and use in source and binary forms, with or without | |
| 6 # modification, are permitted provided that the following conditions are | |
| 7 # met: | |
| 8 # | |
| 9 # * Redistributions of source code must retain the above copyright | |
| 10 # notice, this list of conditions and the following disclaimer. | |
| 11 # * Redistributions in binary form must reproduce the above | |
| 12 # copyright notice, this list of conditions and the following disclaimer | |
| 13 # in the documentation and/or other materials provided with the | |
| 14 # distribution. | |
| 15 # * Neither the name of Google Inc. nor the names of its | |
| 16 # contributors may be used to endorse or promote products derived from | |
| 17 # this software without specific prior written permission. | |
| 18 # | |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 30 | |
| 31 """Visual Studio solution output of component targets for SCons.""" | |
| 32 | |
| 33 import copy | |
| 34 import md5 | |
| 35 import os | |
| 36 import sys | |
| 37 import xml.dom | |
| 38 import xml.dom.minidom | |
| 39 import SCons | |
| 40 | |
| 41 | |
| 42 #------------------------------------------------------------------------------ | |
| 43 | |
| 44 | |
| 45 def MakeGuid(name, seed='component_targets_msvs'): | |
| 46 """Returns a GUID for the specified target name. | |
| 47 | |
| 48 Args: | |
| 49 name: Target name. | |
| 50 seed: Seed for MD5 hash. | |
| 51 Returns: | |
| 52 A GUID-line string calculated from the name and seed. | |
| 53 | |
| 54 This generates something which looks like a GUID, but depends only on the | |
| 55 name and seed. This means the same name/seed will always generate the same | |
| 56 GUID, so that projects and solutions which refer to each other can explicitly | |
| 57 determine the GUID to refer to explicitly. It also means that the GUID will | |
| 58 not change when the project for a target is rebuilt. | |
| 59 """ | |
| 60 # Calculate a MD5 signature for the seed and name. | |
| 61 d = md5.new(str(seed) + str(name)).hexdigest().upper() | |
| 62 # Convert most of the signature to GUID form (discard the rest) | |
| 63 guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20] | |
| 64 + '-' + d[20:32] + '}') | |
| 65 return guid | |
| 66 | |
| 67 #------------------------------------------------------------------------------ | |
| 68 | |
| 69 | |
| 70 def GetGuidAndNameFromVSProject(project_path): | |
| 71 """Reads the GUID from a Visual Studio project file. | |
| 72 | |
| 73 Args: | |
| 74 project_path: Path to the Visual Studio project file. | |
| 75 | |
| 76 Returns: | |
| 77 The GUID string from the file. | |
| 78 The project name from the file. | |
| 79 """ | |
| 80 doc = xml.dom.minidom.parse(project_path) | |
| 81 try: | |
| 82 n_root = doc.documentElement | |
| 83 if n_root.nodeName != 'VisualStudioProject': | |
| 84 raise SCons.Errors.UserError('%s is not a Visual Studio project.' % | |
| 85 project_path) | |
| 86 return ( | |
| 87 str(n_root.attributes['ProjectGUID'].nodeValue), | |
| 88 str(n_root.attributes['Name'].nodeValue), | |
| 89 ) | |
| 90 finally: | |
| 91 # Clean up doc | |
| 92 doc.unlink() | |
| 93 | |
| 94 #------------------------------------------------------------------------------ | |
| 95 | |
| 96 | |
| 97 class VSProjectWriter(object): | |
| 98 """Visual Studio XML project writer.""" | |
| 99 | |
| 100 def __init__(self, project_path): | |
| 101 """Initializes the project. | |
| 102 | |
| 103 Args: | |
| 104 project_path: Path to the project file. | |
| 105 """ | |
| 106 self.project_path = project_path | |
| 107 self.doc = None | |
| 108 | |
| 109 def Create(self, name): | |
| 110 """Creates the project document. | |
| 111 | |
| 112 Args: | |
| 113 name: Name of the project. | |
| 114 """ | |
| 115 self.name = name | |
| 116 self.guid = MakeGuid(name) | |
| 117 | |
| 118 # Create XML doc | |
| 119 xml_impl = xml.dom.getDOMImplementation() | |
| 120 self.doc = xml_impl.createDocument(None, 'VisualStudioProject', None) | |
| 121 | |
| 122 # Add attributes to root element | |
| 123 self.n_root = self.doc.documentElement | |
| 124 self.n_root.setAttribute('ProjectType', 'Visual C++') | |
| 125 self.n_root.setAttribute('Version', '8.00') | |
| 126 self.n_root.setAttribute('Name', self.name) | |
| 127 self.n_root.setAttribute('ProjectGUID', self.guid) | |
| 128 self.n_root.setAttribute('RootNamespace', self.name) | |
| 129 self.n_root.setAttribute('Keyword', 'MakeFileProj') | |
| 130 | |
| 131 # Add platform list | |
| 132 n_platform = self.doc.createElement('Platforms') | |
| 133 self.n_root.appendChild(n_platform) | |
| 134 n = self.doc.createElement('Platform') | |
| 135 n.setAttribute('Name', 'Win32') | |
| 136 n_platform.appendChild(n) | |
| 137 | |
| 138 # Add empty ToolFiles section | |
| 139 self.n_root.appendChild(self.doc.createElement('ToolFiles')) | |
| 140 | |
| 141 # Add configurations section | |
| 142 self.n_configs = self.doc.createElement('Configurations') | |
| 143 self.n_root.appendChild(self.n_configs) | |
| 144 | |
| 145 # Add files section | |
| 146 self.n_files = self.doc.createElement('Files') | |
| 147 self.n_root.appendChild(self.n_files) | |
| 148 | |
| 149 # Add empty Globals section | |
| 150 self.n_root.appendChild(self.doc.createElement('Globals')) | |
| 151 | |
| 152 def AddConfig(self, name, attrs, tool_attrs): | |
| 153 """Adds a configuration to the project. | |
| 154 | |
| 155 Args: | |
| 156 name: Configuration name. | |
| 157 attrs: Dict of configuration attributes. | |
| 158 tool_attrs: Dict of tool attributes. | |
| 159 """ | |
| 160 # Add configuration node | |
| 161 n_config = self.doc.createElement('Configuration') | |
| 162 n_config.setAttribute('Name', '%s|Win32' % name) | |
| 163 n_config.setAttribute('ConfigurationType', '0') | |
| 164 for k, v in attrs.items(): | |
| 165 n_config.setAttribute(k, v) | |
| 166 self.n_configs.appendChild(n_config) | |
| 167 | |
| 168 # Add tool node | |
| 169 n_tool = self.doc.createElement('Tool') | |
| 170 n_tool.setAttribute('Name', 'VCNMakeTool') | |
| 171 n_tool.setAttribute('IncludeSearchPath', '') | |
| 172 n_tool.setAttribute('ForcedIncludes', '') | |
| 173 n_tool.setAttribute('AssemblySearchPath', '') | |
| 174 n_tool.setAttribute('ForcedUsingAssemblies', '') | |
| 175 n_tool.setAttribute('CompileAsManaged', '') | |
| 176 n_tool.setAttribute('PreprocessorDefinitions', '') | |
| 177 for k, v in tool_attrs.items(): | |
| 178 n_tool.setAttribute(k, v) | |
| 179 n_config.appendChild(n_tool) | |
| 180 | |
| 181 def _WalkFolders(self, folder_dict, parent): | |
| 182 """Recursively walks the folder tree. | |
| 183 | |
| 184 Args: | |
| 185 folder_dict: Dict of folder entries. Entry is | |
| 186 either subdir_name:subdir_dict or relative_path_to_file:None. | |
| 187 parent: Parent node (folder node for that dict) | |
| 188 """ | |
| 189 entries = folder_dict.keys() | |
| 190 entries.sort() | |
| 191 for e in entries: | |
| 192 if folder_dict[e]: | |
| 193 # Folder | |
| 194 n_subfolder = self.doc.createElement('Filter') | |
| 195 n_subfolder.setAttribute('Name', e) | |
| 196 parent.appendChild(n_subfolder) | |
| 197 self._WalkFolders(folder_dict[e], n_subfolder) | |
| 198 else: | |
| 199 # File | |
| 200 n_file = self.doc.createElement('File') | |
| 201 n_file.setAttribute('RelativePath', e) | |
| 202 parent.appendChild(n_file) | |
| 203 | |
| 204 def AddFiles(self, name, files_dict): | |
| 205 """Adds files to the project. | |
| 206 | |
| 207 Args: | |
| 208 name: Name of the folder. If None, files/folders will be added directly | |
| 209 to the files list. | |
| 210 files_dict: A dict of files / folders. | |
| 211 | |
| 212 Within the files_dict: | |
| 213 * A file entry is relative_path:None | |
| 214 * A folder entry is folder_name:files_dict, where files_dict is another | |
| 215 dict of this form. | |
| 216 """ | |
| 217 # Create subfolder if necessary | |
| 218 if name: | |
| 219 n_folder = self.doc.createElement('Filter') | |
| 220 n_folder.setAttribute('Name', name) | |
| 221 self.n_files.appendChild(n_folder) | |
| 222 else: | |
| 223 n_folder = self.n_files | |
| 224 | |
| 225 # Recursively add files to the folder | |
| 226 self._WalkFolders(files_dict, n_folder) | |
| 227 | |
| 228 def Write(self): | |
| 229 """Writes the project file.""" | |
| 230 f = open(self.project_path, 'wt') | |
| 231 self.doc.writexml(f, encoding='Windows-1252', addindent=' ', newl='\n') | |
| 232 f.close() | |
| 233 | |
| 234 #------------------------------------------------------------------------------ | |
| 235 | |
| 236 | |
| 237 def ComponentVSProjectBuilder(target, source, env): | |
| 238 """Visual Studio project builder. | |
| 239 | |
| 240 Args: | |
| 241 target: Destination file. | |
| 242 source: List of sources to be added to the target. | |
| 243 env: Environment context. | |
| 244 | |
| 245 Returns: | |
| 246 Zero if successful. | |
| 247 """ | |
| 248 source = source # Silence gpylint | |
| 249 | |
| 250 target_name = env['TARGET_NAME'] | |
| 251 project_file = target[0].path | |
| 252 project_to_main = env.RelativePath(target[0].dir, env.Dir('$MAIN_DIR'), | |
| 253 sep='/') | |
| 254 | |
| 255 env_hammer_bat = env.Clone(VS_PROJECT_TO_MAIN_DIR=project_to_main) | |
| 256 hammer_bat = env_hammer_bat.subst('$COMPONENT_VS_PROJECT_SCRIPT_PATH', raw=1) | |
| 257 | |
| 258 vsp = VSProjectWriter(project_file) | |
| 259 vsp.Create(target_name) | |
| 260 | |
| 261 # Add configuration per build mode supported by this target | |
| 262 target_path = env['TARGET_PATH'] | |
| 263 for mode, path in target_path.items(): | |
| 264 attrs = {} | |
| 265 attrs['OutputDirectory'] = '$(ProjectDir)/%s/%s/out' % (mode, target_name) | |
| 266 attrs['IntermediateDirectory'] = ('$(ProjectDir)/%s/%s/tmp' % | |
| 267 (mode, target_name)) | |
| 268 | |
| 269 tool_attrs = {} | |
| 270 if path: | |
| 271 tool_attrs['Output'] = env.RelativePath(target[0].dir, | |
| 272 env.Entry(path), sep='/') | |
| 273 build_cmd = '%s --mode=%s %s' % (hammer_bat, mode, target_name) | |
| 274 clean_cmd = '%s --mode=%s -c %s' % (hammer_bat, mode, target_name) | |
| 275 tool_attrs['BuildCommandLine'] = build_cmd | |
| 276 tool_attrs['CleanCommandLine'] = clean_cmd | |
| 277 tool_attrs['ReBuildCommandLine'] = clean_cmd + ' && ' + build_cmd | |
| 278 | |
| 279 vsp.AddConfig(mode, attrs, tool_attrs) | |
| 280 | |
| 281 # TODO(rspangler): Fill in files - at least, the .scons file invoking the | |
| 282 # target. | |
| 283 | |
| 284 # Write project | |
| 285 vsp.Write() | |
| 286 return 0 | |
| 287 | |
| 288 | |
| 289 def ComponentVSProject(self, target_name, **kwargs): | |
| 290 """Visual Studio project pseudo-builder for the specified target. | |
| 291 | |
| 292 Args: | |
| 293 self: Environment context. | |
| 294 target_name: Name of the target. | |
| 295 kwargs: Optional keyword arguments override environment variables in the | |
| 296 derived environment used to create the project. | |
| 297 | |
| 298 Returns: | |
| 299 A list of output nodes. | |
| 300 """ | |
| 301 # Builder only works on Windows | |
| 302 if sys.platform not in ('win32', 'cygwin'): | |
| 303 return [] | |
| 304 | |
| 305 # Clone environment and add keyword args | |
| 306 env = self.Clone() | |
| 307 for k, v in kwargs.items(): | |
| 308 env[k] = v | |
| 309 | |
| 310 # Save the target name | |
| 311 env['TARGET_NAME'] = target_name | |
| 312 | |
| 313 # Extract the target properties and save in the environment for use by the | |
| 314 # real builder. | |
| 315 t = GetTargets().get(target_name) | |
| 316 env['TARGET_PATH'] = {} | |
| 317 if t: | |
| 318 for mode, mode_properties in t.mode_properties.items(): | |
| 319 # Since the target path is what Visual Studio will run, use the EXE | |
| 320 # property in preference to TARGET_PATH. | |
| 321 target_path = mode_properties.get('EXE', | |
| 322 mode_properties.get('TARGET_PATH')) | |
| 323 env.Append(TARGET_PATH={mode: target_path}) | |
| 324 else: | |
| 325 # No modes declared for this target. Could be a custom alias created by | |
| 326 # a SConscript, rather than a component builder. Assume it can be built in | |
| 327 # all modes, but produces no output. | |
| 328 for mode in GetTargetModes(): | |
| 329 env.Append(TARGET_PATH={mode: None}) | |
| 330 | |
| 331 # Call the real builder | |
| 332 return env.ComponentVSProjectBuilder( | |
| 333 '$COMPONENT_VS_PROJECT_DIR/${TARGET_NAME}', []) | |
| 334 | |
| 335 #------------------------------------------------------------------------------ | |
| 336 | |
| 337 | |
| 338 class SourceWalker(object): | |
| 339 """Iterator for walking a node tree and collecting sources. | |
| 340 | |
| 341 This is depth-first, children are visited before the parent. The object | |
| 342 can be initialized with any node, and returns the next node on the descent | |
| 343 with each Next() call. | |
| 344 | |
| 345 This class does not get caught in node cycles caused, for example, by C | |
| 346 header file include loops. | |
| 347 | |
| 348 Based on SCons.Node.Walker. | |
| 349 """ | |
| 350 | |
| 351 def __init__(self, node, seen, print_interval = 1000): | |
| 352 """Initializes the source walker. | |
| 353 | |
| 354 Args: | |
| 355 node: Node to start walk from. | |
| 356 seen: Set to add seen nodes to, if not None. | |
| 357 print_interval: Interval in nodes examined at which to print a progress | |
| 358 indicator. | |
| 359 """ | |
| 360 self.interval = print_interval | |
| 361 # Put node on stack | |
| 362 self.stack = [node] | |
| 363 # Scan for node's children, then copy them to node.wkids | |
| 364 node.wkids = copy.copy(node.children(scan=1)) | |
| 365 # Keep track of nodes we've seen (returned) | |
| 366 if seen is None: | |
| 367 seen = set() | |
| 368 self.seen = seen | |
| 369 # Add node to history for cycle detection | |
| 370 self.history = set() | |
| 371 self.history.add(node) | |
| 372 # We've seen one node now | |
| 373 self.nodes_examined = 1 | |
| 374 self.unique_nodes = 1 | |
| 375 | |
| 376 | |
| 377 def Next(self): | |
| 378 """Get the next node for this walk of the tree. | |
| 379 | |
| 380 Returns: | |
| 381 The next node, or None if no more nodes. | |
| 382 | |
| 383 This function is intentionally iterative, not recursive, to sidestep any | |
| 384 issues of stack size limitations. | |
| 385 """ | |
| 386 while self.stack: | |
| 387 if self.stack[-1].wkids: | |
| 388 # Node has children we haven't examined, so iterate into the first | |
| 389 # child | |
| 390 node = self.stack[-1].wkids.pop(0) | |
| 391 if not self.stack[-1].wkids: | |
| 392 # No more children of this node | |
| 393 self.stack[-1].wkids = None | |
| 394 self.nodes_examined += 1 | |
| 395 if self.interval and not self.nodes_examined % self.interval: | |
| 396 self.PrintProgress() | |
| 397 if (node not in self.history) and (node not in self.seen): | |
| 398 # Haven't hit a cycle or a node we've already seen | |
| 399 node.wkids = copy.copy(node.children(scan=1)) | |
| 400 self.stack.append(node) | |
| 401 self.history.add(node) | |
| 402 else: | |
| 403 # Coming back from iterating, so return the next node on the stack. | |
| 404 node = self.stack.pop() | |
| 405 self.history.remove(node) | |
| 406 self.seen.add(node) | |
| 407 self.unique_nodes += 1 | |
| 408 return node | |
| 409 return None | |
| 410 | |
| 411 def PrintProgress(self): | |
| 412 """Prints a progress indicator.""" | |
| 413 print ' Examined %d nodes, found %d unique...' % ( | |
| 414 self.nodes_examined, self.unique_nodes) | |
| 415 | |
| 416 def WalkAll(self): | |
| 417 """Walks all nodes in the the tree.""" | |
| 418 while self.Next(): | |
| 419 pass | |
| 420 if self.interval: | |
| 421 self.PrintProgress() | |
| 422 | |
| 423 | |
| 424 def ComponentVSSourceProjectBuilder(target, source, env): | |
| 425 """Visual Studio source project builder. | |
| 426 | |
| 427 Args: | |
| 428 target: Destination file. | |
| 429 source: List of sources to be added to the target. | |
| 430 env: Environment context. | |
| 431 | |
| 432 Returns: | |
| 433 Zero if successful. | |
| 434 """ | |
| 435 source = source # Silence gpylint | |
| 436 | |
| 437 target_name = env['PROJECT_NAME'] | |
| 438 project_file = target[0].path | |
| 439 project_dir = target[0].dir | |
| 440 | |
| 441 # Get list of suffixes to include | |
| 442 suffixes = env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES') | |
| 443 | |
| 444 # Convert source folders to absolute paths | |
| 445 folders = [] | |
| 446 for f in env['COMPONENT_VS_SOURCE_FOLDERS']: | |
| 447 # (folder name, folder abspath, dict of contents) | |
| 448 folders.append((f[0], env.Dir(f[1]).abspath, {})) | |
| 449 | |
| 450 # TODO(rspangler): Additional enhancements: | |
| 451 # * Should be able to specify paths in folder name (i.e., foo/bar) and | |
| 452 # create the nested folder nodes ('foo' and 'bar') | |
| 453 # * Should be tolerant of a folder being specified more than once with | |
| 454 # the same name (probably necessary to support nested folder nodes anyway) | |
| 455 # Can probably accomplish both of those by creating a parent fodler dict and | |
| 456 # calling WalkFolders() only once. | |
| 457 # Create a temporary solution alias to point to all the targets, so we can | |
| 458 # make a single call to SourceWalker() | |
| 459 tmp_alias = env.Alias('vs_source_project_' + target_name, | |
| 460 map(env.Alias, env['COMPONENT_VS_SOURCE_TARGETS'])) | |
| 461 | |
| 462 # Scan all targets and add unique nodes to set of sources | |
| 463 print ' Scanning dependency tree ...' | |
| 464 all_srcs = set() | |
| 465 walker = SourceWalker(tmp_alias[0], all_srcs) | |
| 466 walker.WalkAll() | |
| 467 | |
| 468 # Walk all sources and build directory trees | |
| 469 print ' Building source tree...' | |
| 470 for n in all_srcs: | |
| 471 if not hasattr(n, 'isfile') or not n.isfile(): | |
| 472 continue # Not a file | |
| 473 if n.has_builder(): | |
| 474 continue # Not a leaf node | |
| 475 if n.suffix not in suffixes: | |
| 476 continue # Not a file type we include | |
| 477 | |
| 478 path = n.rfile().abspath | |
| 479 for f in folders: | |
| 480 if path.startswith(f[1]): | |
| 481 if f[0] is None: | |
| 482 # Folder name of None is a filter | |
| 483 break | |
| 484 relpath = path[len(f[1]) + 1:].split(os.sep) | |
| 485 folder_dict = f[2] | |
| 486 # Recursively add subdirs | |
| 487 for pathseg in relpath[:-1]: | |
| 488 if pathseg not in folder_dict: | |
| 489 folder_dict[pathseg] = {} | |
| 490 folder_dict = folder_dict[pathseg] | |
| 491 # Add file to last subdir. No dict, since this isn't a subdir | |
| 492 folder_dict[env.RelativePath(project_dir, path)] = None | |
| 493 break | |
| 494 | |
| 495 print ' Writing project file...' | |
| 496 | |
| 497 vsp = VSProjectWriter(project_file) | |
| 498 vsp.Create(target_name) | |
| 499 | |
| 500 # One configuration for all build modes | |
| 501 vsp.AddConfig('all', {}, {}) | |
| 502 | |
| 503 # Add files | |
| 504 for f in folders: | |
| 505 if f[0] is None: | |
| 506 continue # Skip filters | |
| 507 vsp.AddFiles(f[0], f[2]) | |
| 508 | |
| 509 vsp.Write() | |
| 510 return 0 | |
| 511 | |
| 512 | |
| 513 def ComponentVSSourceProject(self, project_name, target_names, **kwargs): | |
| 514 """Visual Studio source project pseudo-builder. | |
| 515 | |
| 516 Args: | |
| 517 self: Environment context. | |
| 518 project_name: Name of the project. | |
| 519 target_names: List of target names to include source for. | |
| 520 kwargs: Optional keyword arguments override environment variables in the | |
| 521 derived environment used to create the project. | |
| 522 | |
| 523 Returns: | |
| 524 A list of output nodes. | |
| 525 """ | |
| 526 # Builder only works on Windows | |
| 527 if sys.platform not in ('win32', 'cygwin'): | |
| 528 return [] | |
| 529 | |
| 530 # Clone environment and add keyword args | |
| 531 env = self.Clone() | |
| 532 for k, v in kwargs.items(): | |
| 533 env[k] = v | |
| 534 | |
| 535 # Save the project name and targets | |
| 536 env['PROJECT_NAME'] = project_name | |
| 537 env['COMPONENT_VS_SOURCE_TARGETS'] = target_names | |
| 538 | |
| 539 # Call the real builder | |
| 540 return env.ComponentVSSourceProjectBuilder( | |
| 541 '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', []) | |
| 542 | |
| 543 #------------------------------------------------------------------------------ | |
| 544 | |
| 545 | |
| 546 def FindSources(env, dest, source, suffixes=None): | |
| 547 """Recursively finds sources and adds them to the destination set. | |
| 548 | |
| 549 Args: | |
| 550 env: Environment context. | |
| 551 dest: Set to add source nodes to. | |
| 552 source: Source file(s) to find. May be a string, Node, or a list of | |
| 553 mixed strings or Nodes. Strings will be passed through env.Glob() to | |
| 554 evaluate wildcards. If a source evaluates to a directory, the entire | |
| 555 directory will be recursively added. | |
| 556 suffixes: List of suffixes to include. If not None, only files with these | |
| 557 suffixes will be added to dest. | |
| 558 """ | |
| 559 for source_entry in env.Flatten(source): | |
| 560 if type(source_entry) == str: | |
| 561 # Search for matches for each source entry | |
| 562 source_nodes = env.Glob(source_entry) | |
| 563 else: | |
| 564 # Source entry is already a file or directory node; no need to glob it | |
| 565 source_nodes = [source_entry] | |
| 566 for s in source_nodes: | |
| 567 if str(s.__class__) == 'SCons.Node.FS.Dir': | |
| 568 # Recursively search subdir. Since glob('*') doesn't match dot files, | |
| 569 # also glob('.*'). | |
| 570 FindSources(env, dest, [s.abspath + '/*', s.abspath + '/.*'], suffixes) | |
| 571 elif suffixes and s.suffix in suffixes: | |
| 572 dest.add(s) | |
| 573 | |
| 574 | |
| 575 def ComponentVSDirProjectBuilder(target, source, env): | |
| 576 """Visual Studio directory project builder. | |
| 577 | |
| 578 Args: | |
| 579 target: Destination file. | |
| 580 source: List of sources to be added to the target. | |
| 581 env: Environment context. | |
| 582 | |
| 583 Returns: | |
| 584 Zero if successful. | |
| 585 """ | |
| 586 source = source # Silence gpylint | |
| 587 | |
| 588 target_name = env['PROJECT_NAME'] | |
| 589 project_file = target[0].path | |
| 590 project_dir = target[0].dir | |
| 591 | |
| 592 # Convert source folders to absolute paths | |
| 593 folders = [] | |
| 594 for f in env['COMPONENT_VS_SOURCE_FOLDERS']: | |
| 595 # (folder name, folder abspath, dict of contents) | |
| 596 folders.append((f[0], env.Dir(f[1]).abspath, {})) | |
| 597 | |
| 598 # Recursively scan source directories | |
| 599 print ' Scanning directories for source...' | |
| 600 all_srcs = set() | |
| 601 FindSources(env, all_srcs, env['PROJECT_SOURCES'], | |
| 602 suffixes=env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES')) | |
| 603 | |
| 604 # Walk all sources and build directory trees | |
| 605 print ' Building source tree...' | |
| 606 for n in all_srcs: | |
| 607 # Map addRepository'd source to its real location. | |
| 608 path = n.rfile().abspath | |
| 609 for f in folders: | |
| 610 if path.startswith(f[1]): | |
| 611 if f[0] is None: | |
| 612 # Folder name of None is a filter | |
| 613 break | |
| 614 relpath = path[len(f[1]) + 1:].split(os.sep) | |
| 615 folder_dict = f[2] | |
| 616 # Recursively add subdirs | |
| 617 for pathseg in relpath[:-1]: | |
| 618 if pathseg not in folder_dict: | |
| 619 folder_dict[pathseg] = {} | |
| 620 folder_dict = folder_dict[pathseg] | |
| 621 # Add file to last subdir. No dict, since this isn't a subdir | |
| 622 folder_dict[env.RelativePath(project_dir, path)] = None | |
| 623 break | |
| 624 | |
| 625 print ' Writing project file...' | |
| 626 | |
| 627 vsp = VSProjectWriter(project_file) | |
| 628 vsp.Create(target_name) | |
| 629 | |
| 630 # One configuration for all build modes | |
| 631 vsp.AddConfig('all', {}, {}) | |
| 632 | |
| 633 # Add files | |
| 634 for f in folders: | |
| 635 if f[0] is None: | |
| 636 continue # Skip filters | |
| 637 vsp.AddFiles(f[0], f[2]) | |
| 638 | |
| 639 vsp.Write() | |
| 640 return 0 | |
| 641 | |
| 642 | |
| 643 def ComponentVSDirProject(self, project_name, source, **kwargs): | |
| 644 """Visual Studio directory project pseudo-builder. | |
| 645 | |
| 646 Args: | |
| 647 self: Environment context. | |
| 648 project_name: Name of the project. | |
| 649 source: List of source files and/or directories. | |
| 650 kwargs: Optional keyword arguments override environment variables in the | |
| 651 derived environment used to create the project. | |
| 652 | |
| 653 Returns: | |
| 654 A list of output nodes. | |
| 655 """ | |
| 656 # Builder only works on Windows | |
| 657 if sys.platform not in ('win32', 'cygwin'): | |
| 658 return [] | |
| 659 | |
| 660 # Clone environment and add keyword args | |
| 661 env = self.Clone() | |
| 662 for k, v in kwargs.items(): | |
| 663 env[k] = v | |
| 664 | |
| 665 # Save the project name and sources | |
| 666 env['PROJECT_NAME'] = project_name | |
| 667 env['PROJECT_SOURCES'] = source | |
| 668 | |
| 669 # Call the real builder | |
| 670 return env.ComponentVSDirProjectBuilder( | |
| 671 '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', []) | |
| 672 | |
| 673 #------------------------------------------------------------------------------ | |
| 674 | |
| 675 | |
| 676 def ComponentVSSolutionBuilder(target, source, env): | |
| 677 """Visual Studio solution builder. | |
| 678 | |
| 679 Args: | |
| 680 target: Destination file. | |
| 681 source: List of sources to be added to the target. | |
| 682 env: Environment context. | |
| 683 | |
| 684 Returns: | |
| 685 Zero if successful. | |
| 686 """ | |
| 687 source = source # Silence gpylint | |
| 688 | |
| 689 solution_file = target[0].path | |
| 690 projects = env['SOLUTION_PROJECTS'] | |
| 691 folders = env['SOLUTION_FOLDERS'] | |
| 692 | |
| 693 # Scan externally-generated projects | |
| 694 external_projects = [] | |
| 695 for p in source: | |
| 696 guid, name = GetGuidAndNameFromVSProject(p.abspath) | |
| 697 external_projects.append((p, name, guid)) | |
| 698 | |
| 699 f = open(solution_file, 'wt') | |
| 700 f.write('Microsoft Visual Studio Solution File, Format Version 9.00\n') | |
| 701 f.write('# Visual Studio 2005\n') | |
| 702 | |
| 703 # Projects generated by ComponentVSSolution() | |
| 704 for p in projects: | |
| 705 project_file = env.File( | |
| 706 '$COMPONENT_VS_PROJECT_DIR/%s$COMPONENT_VS_PROJECT_SUFFIX' % p) | |
| 707 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( | |
| 708 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID | |
| 709 p, # Project name | |
| 710 env.RelativePath(target[0].dir, project_file), # Path to project file | |
| 711 MakeGuid(p), # Project GUID | |
| 712 )) | |
| 713 | |
| 714 # Projects generated elsewhere | |
| 715 for p, name, guid in external_projects: | |
| 716 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( | |
| 717 # TODO(rspangler): What if this project isn't type external makefile? | |
| 718 # How to tell what type it is? | |
| 719 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID | |
| 720 name, # Project name | |
| 721 env.RelativePath(target[0].dir, p), # Path to project file | |
| 722 guid, # Project GUID | |
| 723 )) | |
| 724 | |
| 725 # Folders from build groups | |
| 726 # TODO(rspangler): Currently no way to add external project (specified in | |
| 727 # sources) to a solution folder. | |
| 728 for folder in folders: | |
| 729 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( | |
| 730 '{2150E333-8FDC-42A3-9474-1A3956D46DE8}', # Solution folder GUID | |
| 731 folder, # Folder name | |
| 732 folder, # Folder name (again) | |
| 733 # Use a different seed so the folder won't get the same GUID as a | |
| 734 # project. | |
| 735 MakeGuid(folder, seed='folder'), # Project GUID | |
| 736 )) | |
| 737 | |
| 738 f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') | |
| 739 for mode in GetTargetModes(): | |
| 740 f.write('\t\t%s|Win32 = %s|Win32\n' % (mode, mode)) | |
| 741 f.write('\tEndGlobalSection\n') | |
| 742 | |
| 743 # Determine which projects should be enabled | |
| 744 # TODO(rspangler): This is somewhat clunky. DEFAULT_TARGETS is global, and | |
| 745 # what we really need is something mode-specific. In theory we could make | |
| 746 # this a mode-specific dict rather than a list, but that'd also be a pain to | |
| 747 # populate. | |
| 748 # These variable names are also getting REALLY long. Perhaps we should | |
| 749 # define shorter ones (with the default value redirecting to the longer | |
| 750 # ones for legacy compatibility). | |
| 751 enable_projects = env.SubstList2('$COMPONENT_VS_ENABLED_PROJECTS') | |
| 752 | |
| 753 f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') | |
| 754 | |
| 755 # Projects generated by ComponentVSSolution() | |
| 756 for p in projects: | |
| 757 for mode in GetTargetModes(): | |
| 758 f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % ( | |
| 759 MakeGuid(p), # Project GUID | |
| 760 mode, # Solution build configuration | |
| 761 mode, # Project build config for that solution config | |
| 762 )) | |
| 763 | |
| 764 t = GetTargets().get(p) | |
| 765 | |
| 766 # Determine if project should be enabled in this mode | |
| 767 enabled = t and mode in t.mode_properties | |
| 768 if enable_projects and p not in enable_projects: | |
| 769 # Enable list specified, but target isn't in it | |
| 770 # TODO(rspangler): Since we env.Default(scons-out) elsewhere, this | |
| 771 # likely causes all projects to be disabled by default. But that's | |
| 772 # realistically better than enabling them all... | |
| 773 enabled = False | |
| 774 | |
| 775 if enabled: | |
| 776 # Target can be built in this mode | |
| 777 f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % ( | |
| 778 MakeGuid(p), # Project GUID | |
| 779 mode, # Solution build configuration | |
| 780 mode, # Project build config for that solution config | |
| 781 )) | |
| 782 | |
| 783 # Projects generated elsewhere | |
| 784 for p, name, guid in external_projects: | |
| 785 for mode in GetTargetModes(): | |
| 786 f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % ( | |
| 787 guid, # Project GUID | |
| 788 mode, # Solution build configuration | |
| 789 mode, # Project build config for that solution config | |
| 790 )) | |
| 791 | |
| 792 if name in enable_projects or not enable_projects: | |
| 793 # Build target in this mode | |
| 794 f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % ( | |
| 795 guid, # Project GUID | |
| 796 mode, # Solution build configuration | |
| 797 mode, # Project build config for that solution config | |
| 798 )) | |
| 799 | |
| 800 f.write('\tEndGlobalSection\n') | |
| 801 | |
| 802 f.write('\tGlobalSection(SolutionProperties) = preSolution\n') | |
| 803 f.write('\t\tHideSolutionNode = FALSE\n') | |
| 804 f.write('\tEndGlobalSection\n') | |
| 805 | |
| 806 if folders: | |
| 807 f.write('\tGlobalSection(NestedProjects) = preSolution\n') | |
| 808 for p, folder in projects.items(): | |
| 809 f.write('\t\t%s = %s\n' % (MakeGuid(p), MakeGuid(folder, seed='folder'))) | |
| 810 f.write('\tEndGlobalSection\n') | |
| 811 | |
| 812 f.write('EndGlobal\n') | |
| 813 f.close() | |
| 814 | |
| 815 return 0 | |
| 816 | |
| 817 | |
| 818 def ComponentVSSolution(self, solution_name, target_names, projects=None, | |
| 819 **kwargs): | |
| 820 """Visual Studio solution pseudo-builder. | |
| 821 | |
| 822 Args: | |
| 823 self: Environment context. | |
| 824 solution_name: Name of the solution. | |
| 825 target_names: Names of targets or target groups to include in the solution. | |
| 826 This will automatically build projects for them. | |
| 827 projects: List of aditional projects not generated by this solution to | |
| 828 include in the solution. | |
| 829 kwargs: Optional keyword arguments override environment variables in the | |
| 830 derived environment used to create the solution. | |
| 831 | |
| 832 Returns: | |
| 833 The list of output nodes. | |
| 834 """ | |
| 835 # TODO(rspangler): Should have option to build source project also. Perhaps | |
| 836 # select using a --source_project option, since it needs to use gather_inputs | |
| 837 # to scan the DAG and will blow up the null build time. | |
| 838 # TODO(rspangler): Should also have autobuild_projects option. If false, | |
| 839 # don't build them. | |
| 840 # TODO(rspangler): Should also be able to specify a target group directly | |
| 841 # (i.e. 'run_all_tests') | |
| 842 | |
| 843 # Builder only works on Windows | |
| 844 if sys.platform not in ('win32', 'cygwin'): | |
| 845 return [] | |
| 846 | |
| 847 # Clone environment and add keyword args | |
| 848 env = self.Clone() | |
| 849 for k, v in kwargs.items(): | |
| 850 env[k] = v | |
| 851 | |
| 852 # Save the target name | |
| 853 env['SOLUTION_NAME'] = solution_name | |
| 854 | |
| 855 # Get list of targets to make projects for. At this point we haven't | |
| 856 # determined whether they're groups or targets. | |
| 857 target_names = env.SubstList2(target_names) | |
| 858 env['SOLUTION_TARGETS'] = target_names | |
| 859 | |
| 860 # Save the default targets list as an environment variable | |
| 861 env['COMPONENT_VS_SCONS_DEFAULT_TARGETS'] = SCons.Script.DEFAULT_TARGETS | |
| 862 | |
| 863 # Expand target_names into project names, and create project-to-folder | |
| 864 # mappings | |
| 865 project_names = {} | |
| 866 folders = [] | |
| 867 if target_names: | |
| 868 # Expand target_names into project names | |
| 869 for target in target_names: | |
| 870 if target in GetTargetGroups(): | |
| 871 # Add target to folders | |
| 872 folders.append(target) | |
| 873 # Add all project_names in the group | |
| 874 for t in GetTargetGroups()[target].GetTargetNames(): | |
| 875 project_names[t] = target | |
| 876 elif target in GetTargets(): | |
| 877 # Just a target name | |
| 878 project_names[target] = None | |
| 879 else: | |
| 880 print 'Warning: ignoring unknown target "%s"' % target | |
| 881 else: | |
| 882 # No target names specified, so use all projects | |
| 883 for t in GetTargets(): | |
| 884 project_names[t] = None | |
| 885 | |
| 886 env['SOLUTION_FOLDERS'] = folders | |
| 887 env['SOLUTION_PROJECTS'] = project_names | |
| 888 | |
| 889 # Call the real builder | |
| 890 out_nodes = env.ComponentVSSolutionBuilder( | |
| 891 '$COMPONENT_VS_SOLUTION_DIR/${SOLUTION_NAME}', projects or []) | |
| 892 | |
| 893 # Call the real builder for the projects we generate | |
| 894 for p in project_names: | |
| 895 out_nodes += env.ComponentVSProject(p) | |
| 896 | |
| 897 # Add the solution target | |
| 898 # TODO(rspangler): Should really defer the rest of the work above, since | |
| 899 # otherwise we can't build a solution which has a target to rebuild itself. | |
| 900 env.Alias('all_solutions', env.Alias(solution_name, out_nodes)) | |
| 901 | |
| 902 # TODO(rspangler): To rebuild the solution properly, need to override its | |
| 903 # project configuration so it only has '--mode=all' (or some other way of | |
| 904 # setting the subset of modes which it should use to rebuild itself). | |
| 905 # Rebuilding with the property below will strip it down to just the current | |
| 906 # build mode, which isn't what we want. | |
| 907 # Let component_targets know this target is available in the current mode | |
| 908 #self.SetTargetProperty(solution_name, TARGET_PATH=out_nodes[0]) | |
| 909 | |
| 910 return out_nodes | |
| 911 | |
| 912 #------------------------------------------------------------------------------ | |
| 913 | |
| 914 | |
| 915 def generate(env): | |
| 916 # NOTE: SCons requires the use of this name, which fails gpylint. | |
| 917 """SCons entry point for this tool.""" | |
| 918 | |
| 919 # Add pseudo-builders to set up the project and solution builders. These | |
| 920 # need to be available on all platforms so that SConscripts which reference | |
| 921 # them will work. | |
| 922 env.AddMethod(ComponentVSDirProject) | |
| 923 env.AddMethod(ComponentVSProject) | |
| 924 env.AddMethod(ComponentVSSolution) | |
| 925 env.AddMethod(ComponentVSSourceProject) | |
| 926 | |
| 927 # If not on Windows, don't do anything else | |
| 928 if sys.platform not in ('win32', 'cygwin'): | |
| 929 return | |
| 930 | |
| 931 # Include tools we need | |
| 932 env.Tool('gather_inputs') | |
| 933 | |
| 934 env.SetDefault( | |
| 935 COMPONENT_VS_PROJECT_DIR='$COMPONENT_VS_SOLUTION_DIR/projects', | |
| 936 COMPONENT_VS_PROJECT_SCRIPT_NAME = 'hammer.bat', | |
| 937 COMPONENT_VS_PROJECT_SCRIPT_PATH = ( | |
| 938 '$$(ProjectDir)/$VS_PROJECT_TO_MAIN_DIR/' | |
| 939 '$COMPONENT_VS_PROJECT_SCRIPT_NAME'), | |
| 940 COMPONENT_VS_PROJECT_SUFFIX='.vcproj', | |
| 941 | |
| 942 COMPONENT_VS_SOLUTION_DIR='$DESTINATION_ROOT/solution', | |
| 943 COMPONENT_VS_SOLUTION_SUFFIX='.sln', | |
| 944 COMPONENT_VS_ENABLED_PROJECTS=['$COMPONENT_VS_SCONS_DEFAULT_TARGETS'], | |
| 945 | |
| 946 COMPONENT_VS_SOURCE_SUFFIXES=['$CPPSUFFIXES', '.rc', '.scons'], | |
| 947 COMPONENT_VS_SOURCE_FOLDERS=[('source', '$MAIN_DIR')], | |
| 948 ) | |
| 949 | |
| 950 AddTargetGroup('all_solutions', 'solutions can be built') | |
| 951 | |
| 952 # Add builders | |
| 953 vcprojaction = SCons.Script.Action(ComponentVSProjectBuilder, varlist=[ | |
| 954 'COMPONENT_VS_PROJECT_SCRIPT_PATH', | |
| 955 'TARGET_NAME', | |
| 956 'TARGET_PATH', | |
| 957 ]) | |
| 958 vcprojbuilder = SCons.Script.Builder( | |
| 959 action=vcprojaction, | |
| 960 suffix='$COMPONENT_VS_PROJECT_SUFFIX') | |
| 961 | |
| 962 source_vcproj_action = SCons.Script.Action( | |
| 963 ComponentVSSourceProjectBuilder, varlist=[ | |
| 964 'COMPONENT_VS_SOURCE_FOLDERS', | |
| 965 'COMPONENT_VS_SOURCE_SUFFIXES', | |
| 966 'COMPONENT_VS_SOURCE_TARGETS', | |
| 967 ]) | |
| 968 source_vcproj_builder = SCons.Script.Builder( | |
| 969 action=source_vcproj_action, | |
| 970 suffix='$COMPONENT_VS_PROJECT_SUFFIX') | |
| 971 | |
| 972 dir_vcproj_action = SCons.Script.Action( | |
| 973 ComponentVSDirProjectBuilder, varlist=[ | |
| 974 'COMPONENT_VS_SOURCE_FOLDERS', | |
| 975 'COMPONENT_VS_SOURCE_SUFFIXES', | |
| 976 'PROJECT_SOURCES', | |
| 977 ]) | |
| 978 dir_vcproj_builder = SCons.Script.Builder( | |
| 979 action=dir_vcproj_action, | |
| 980 suffix='$COMPONENT_VS_PROJECT_SUFFIX') | |
| 981 | |
| 982 slnaction = SCons.Script.Action(ComponentVSSolutionBuilder, varlist=[ | |
| 983 'COMPONENT_VS_ENABLED_PROJECTS', | |
| 984 'SOLUTION_FOLDERS', | |
| 985 'SOLUTION_PROJECTS', | |
| 986 'SOLUTION_TARGETS', | |
| 987 ]) | |
| 988 slnbuilder = SCons.Script.Builder( | |
| 989 action=slnaction, | |
| 990 suffix='$COMPONENT_VS_SOLUTION_SUFFIX') | |
| 991 | |
| 992 env.Append(BUILDERS={ | |
| 993 'ComponentVSDirProjectBuilder': dir_vcproj_builder, | |
| 994 'ComponentVSProjectBuilder': vcprojbuilder, | |
| 995 'ComponentVSSolutionBuilder': slnbuilder, | |
| 996 'ComponentVSSourceProjectBuilder': source_vcproj_builder, | |
| 997 }) | |
| OLD | NEW |