| OLD | NEW |
| (Empty) |
| 1 # | |
| 2 # __COPYRIGHT__ | |
| 3 # | |
| 4 # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 # a copy of this software and associated documentation files (the | |
| 6 # "Software"), to deal in the Software without restriction, including | |
| 7 # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 # permit persons to whom the Software is furnished to do so, subject to | |
| 10 # the following conditions: | |
| 11 # | |
| 12 # The above copyright notice and this permission notice shall be included | |
| 13 # in all copies or substantial portions of the Software. | |
| 14 # | |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY | |
| 16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |
| 17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 # | |
| 23 | |
| 24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" | |
| 25 | |
| 26 __doc__ = """SCons.Node.MSVS | |
| 27 | |
| 28 New implementation of Visual Studio project generation for SCons. | |
| 29 """ | |
| 30 | |
| 31 import md5 | |
| 32 import os | |
| 33 import random | |
| 34 import re | |
| 35 import UserList | |
| 36 import xml.dom | |
| 37 import xml.dom.minidom | |
| 38 | |
| 39 import SCons.Node.FS | |
| 40 import SCons.Script | |
| 41 import SCons.Util | |
| 42 | |
| 43 from SCons.Debug import Trace | |
| 44 TODO = 0 | |
| 45 | |
| 46 # Initialize random number generator | |
| 47 random.seed() | |
| 48 | |
| 49 | |
| 50 #------------------------------------------------------------------------------ | |
| 51 # Entry point for supplying a fixed map of GUIDs for testing. | |
| 52 | |
| 53 GUIDMap = {} | |
| 54 | |
| 55 | |
| 56 #------------------------------------------------------------------------------ | |
| 57 # Helper functions | |
| 58 | |
| 59 | |
| 60 def MakeGuid(name, seed='msvs_new'): | |
| 61 """Returns a GUID for the specified target name. | |
| 62 | |
| 63 Args: | |
| 64 name: Target name. | |
| 65 seed: Seed for MD5 hash. | |
| 66 Returns: | |
| 67 A GUID-line string calculated from the name and seed. | |
| 68 | |
| 69 This generates something which looks like a GUID, but depends only on the | |
| 70 name and seed. This means the same name/seed will always generate the same | |
| 71 GUID, so that projects and solutions which refer to each other can explicitly | |
| 72 determine the GUID to refer to explicitly. It also means that the GUID will | |
| 73 not change when the project for a target is rebuilt. | |
| 74 """ | |
| 75 # Calculate a MD5 signature for the seed and name. | |
| 76 d = md5.new(str(seed) + str(name)).hexdigest().upper() | |
| 77 # Convert most of the signature to GUID form (discard the rest) | |
| 78 guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20] | |
| 79 + '-' + d[20:32] + '}') | |
| 80 return guid | |
| 81 | |
| 82 | |
| 83 #------------------------------------------------------------------------------ | |
| 84 # Global look up of string names. | |
| 85 | |
| 86 class LookupError(Exception): | |
| 87 def __str__(self): | |
| 88 string, expanded = self.args | |
| 89 if string == expanded: | |
| 90 return string | |
| 91 else: | |
| 92 return '%s (%s)' % (string, expanded) | |
| 93 | |
| 94 _lookup_dict = {} | |
| 95 | |
| 96 def LookupAdd(item, result): | |
| 97 _lookup_dict[item] = result | |
| 98 _lookup_dict[result] = result | |
| 99 | |
| 100 def Lookup(item): | |
| 101 """Looks up an MSVS item in the global dictionary. | |
| 102 | |
| 103 Args: | |
| 104 item: A path (string) or instance for looking up. | |
| 105 Returns: | |
| 106 An instance from the global _lookup_dict. | |
| 107 | |
| 108 Raises an exception if the item does not exist in the _lookup_dict. | |
| 109 """ | |
| 110 global _lookup_dict | |
| 111 try: | |
| 112 return _lookup_dict[item] | |
| 113 except KeyError: | |
| 114 return SCons.Node.FS.default_fs.Entry(item, create=False) | |
| 115 | |
| 116 def LookupCreate(klass, item, *args, **kw): | |
| 117 """Looks up an MSVS item, creating it if it doesn't already exist. | |
| 118 | |
| 119 Args: | |
| 120 klass: The class of item being looked up, or created if it | |
| 121 doesn't already exist in the global _lookup_dict. | |
| 122 item: The a string (or instance) being looked up. | |
| 123 *args: positional arguments passed to the klass.initialize() method. | |
| 124 **kw: keyword arguments passed to the klass.initialize() method. | |
| 125 Returns: | |
| 126 An instance from the global _lookup_dict, or None if the item does | |
| 127 not exist in the _lookup_dict. | |
| 128 | |
| 129 This raises a LookupError if the found instance doesn't match the | |
| 130 requested klass. | |
| 131 | |
| 132 When creating a new instance, this populates the _lookup_dict with | |
| 133 both the item and the instance itself as keys, so that looking up | |
| 134 the instance will return itself. | |
| 135 """ | |
| 136 global _lookup_dict | |
| 137 result = _lookup_dict.get(item) | |
| 138 if result: | |
| 139 if not isinstance(result, klass): | |
| 140 raise LookupError, "tried to redefine %s as a %s" % (item, klass) | |
| 141 return result | |
| 142 result = klass() | |
| 143 result.initialize(item, *args, **kw) | |
| 144 LookupAdd(item, result) | |
| 145 return result | |
| 146 | |
| 147 | |
| 148 #------------------------------------------------------------------------------ | |
| 149 | |
| 150 class FileList(object): | |
| 151 def __init__(self, entries=None): | |
| 152 if isinstance(entries, FileList): | |
| 153 entries = entries.entries | |
| 154 self.entries = entries or [] | |
| 155 def __getitem__(self, i): | |
| 156 return self.entries[i] | |
| 157 def __setitem__(self, i, item): | |
| 158 self.entries[i] = item | |
| 159 def __delitem__(self, i): | |
| 160 del self.entries[i] | |
| 161 def __add__(self, other): | |
| 162 if isinstance(other, FileList): | |
| 163 return self.__class__(self.entries + other.entries) | |
| 164 elif isinstance(other, type(self.entries)): | |
| 165 return self.__class__(self.entries + other) | |
| 166 else: | |
| 167 return self.__class__(self.entries + list(other)) | |
| 168 def __radd__(self, other): | |
| 169 if isinstance(other, FileList): | |
| 170 return self.__class__(other.entries + self.entries) | |
| 171 elif isinstance(other, type(self.entries)): | |
| 172 return self.__class__(other + self.entries) | |
| 173 else: | |
| 174 return self.__class__(list(other) + self.entries) | |
| 175 def __iadd__(self, other): | |
| 176 if isinstance(other, FileList): | |
| 177 self.entries += other.entries | |
| 178 elif isinstance(other, type(self.entries)): | |
| 179 self.entries += other | |
| 180 else: | |
| 181 self.entries += list(other) | |
| 182 return self | |
| 183 def append(self, item): | |
| 184 return self.entries.append(item) | |
| 185 def extend(self, item): | |
| 186 return self.entries.extend(item) | |
| 187 def index(self, item, *args): | |
| 188 return self.entries.index(item, *args) | |
| 189 def remove(self, item): | |
| 190 return self.entries.remove(item) | |
| 191 | |
| 192 def FileListWalk(top, topdown=True, onerror=None): | |
| 193 """ | |
| 194 """ | |
| 195 try: | |
| 196 entries = top.entries | |
| 197 except AttributeError, err: | |
| 198 if onerror is not None: | |
| 199 onerror(err) | |
| 200 return | |
| 201 | |
| 202 dirs, nondirs = [], [] | |
| 203 for entry in entries: | |
| 204 if hasattr(entry, 'entries'): | |
| 205 dirs.append(entry) | |
| 206 else: | |
| 207 nondirs.append(entry) | |
| 208 | |
| 209 if topdown: | |
| 210 yield top, dirs, nondirs | |
| 211 for entry in dirs: | |
| 212 for x in FileListWalk(entry, topdown, onerror): | |
| 213 yield x | |
| 214 if not topdown: | |
| 215 yield top, dirs, nondirs | |
| 216 | |
| 217 #------------------------------------------------------------------------------ | |
| 218 | |
| 219 class _MSVSFolder(FileList): | |
| 220 """Folder in a Visual Studio solution.""" | |
| 221 | |
| 222 entry_type_guid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}' | |
| 223 | |
| 224 def initialize(self, path, name=None, entries=None, guid=None, items=None): | |
| 225 """Initializes the folder. | |
| 226 | |
| 227 Args: | |
| 228 path: The unique name of the folder, by which other MSVS Nodes can | |
| 229 refer to it. This is not necessarily the name that gets printed | |
| 230 in the .sln file. | |
| 231 name: The name of this folder as actually written in a generated | |
| 232 .sln file. The default is | |
| 233 entries: List of folder entries to nest inside this folder. May contain | |
| 234 Folder or Project objects. May be None, if the folder is empty. | |
| 235 guid: GUID to use for folder, if not None. | |
| 236 items: List of solution items to include in the folder project. May be | |
| 237 None, if the folder does not directly contain items. | |
| 238 """ | |
| 239 super(_MSVSFolder, self).__init__(entries) | |
| 240 | |
| 241 # For folder entries, the path is the same as the name | |
| 242 self.msvs_path = path | |
| 243 self.msvs_name = name or path | |
| 244 | |
| 245 self.guid = guid | |
| 246 | |
| 247 # Copy passed lists (or set to empty lists) | |
| 248 self.items = list(items or []) | |
| 249 | |
| 250 def get_guid(self): | |
| 251 if self.guid is None: | |
| 252 guid = GUIDMap.get(self.msvs_path) | |
| 253 if not guid: | |
| 254 # The GUID for the folder can be random, since it's used only inside | |
| 255 # solution files and doesn't need to be consistent across runs. | |
| 256 guid = MakeGuid(random.random()) | |
| 257 self.guid = guid | |
| 258 return self.guid | |
| 259 | |
| 260 def get_msvs_path(self, sln): | |
| 261 return self.msvs_name | |
| 262 | |
| 263 def MSVSFolder(env, item, *args, **kw): | |
| 264 return LookupCreate(_MSVSFolder, item, *args, **kw) | |
| 265 | |
| 266 #------------------------------------------------------------------------------ | |
| 267 | |
| 268 class MSVSConfig(object): | |
| 269 """Visual Studio configuration.""" | |
| 270 def __init__(self, Name, config_type, tools=None, **attrs): | |
| 271 """Initializes the configuration. | |
| 272 | |
| 273 Args: | |
| 274 **attrs: Configuration attributes. | |
| 275 """ | |
| 276 # Special handling for attributes that we want to make more | |
| 277 # convenient for the user. | |
| 278 ips = attrs.get('InheritedPropertySheets') | |
| 279 if ips: | |
| 280 if isinstance(ips, list): | |
| 281 ips = ';'.join(ips) | |
| 282 attrs['InheritedPropertySheets'] = ips.replace('/', '\\') | |
| 283 | |
| 284 self.Name = Name | |
| 285 self.config_type = config_type | |
| 286 self.tools = tools | |
| 287 self.attrs = attrs | |
| 288 | |
| 289 def CreateElement(self, doc, project): | |
| 290 """Creates an element for the configuration. | |
| 291 | |
| 292 Args: | |
| 293 doc: xml.dom.Document object to use for node creation. | |
| 294 | |
| 295 Returns: | |
| 296 A new xml.dom.Element for the configuration. | |
| 297 """ | |
| 298 node = doc.createElement(self.config_type) | |
| 299 node.setAttribute('Name', self.Name) | |
| 300 for k, v in self.attrs.items(): | |
| 301 node.setAttribute(k, v) | |
| 302 | |
| 303 tools = self.tools | |
| 304 if tools is None: | |
| 305 tools = project.tools or [] | |
| 306 if not SCons.Util.is_List(tools): | |
| 307 tools = [tools] | |
| 308 tool_objects = [] | |
| 309 for t in tools: | |
| 310 if not isinstance(t, MSVSTool): | |
| 311 t = MSVSTool(t) | |
| 312 tool_objects.append(t) | |
| 313 for t in tool_objects: | |
| 314 node.appendChild(t.CreateElement(doc)) | |
| 315 | |
| 316 return node | |
| 317 | |
| 318 | |
| 319 class MSVSFileListBase(FileList): | |
| 320 """Base class for a file list in a Visual Studio project file.""" | |
| 321 | |
| 322 def CreateElement(self, doc, node_func=lambda x: x): | |
| 323 """Creates an element for an MSVSFileListBase subclass. | |
| 324 | |
| 325 Args: | |
| 326 doc: xml.dom.Document object to use for node creation. | |
| 327 node_func: Function to use to return Nodes for objects that | |
| 328 don't have a CreateElement() method of their own. | |
| 329 | |
| 330 Returns: | |
| 331 A new xml.dom.Element for the MSVSFileListBase object. | |
| 332 """ | |
| 333 node = doc.createElement(self.element_name) | |
| 334 for entry in self.entries: | |
| 335 if hasattr(entry, 'CreateElement'): | |
| 336 n = entry.CreateElement(doc, node_func) | |
| 337 else: | |
| 338 n = node_func(entry) | |
| 339 node.appendChild(n) | |
| 340 return node | |
| 341 | |
| 342 | |
| 343 class MSVSFiles(MSVSFileListBase): | |
| 344 """Files list in a Visual Studio project file.""" | |
| 345 element_name = 'Files' | |
| 346 | |
| 347 | |
| 348 class MSVSFilter(MSVSFileListBase): | |
| 349 """Filter (that is, a virtual folder) in a Visual Studio project file.""" | |
| 350 | |
| 351 element_name = 'Filter' | |
| 352 | |
| 353 def __init__(self, Name, entries=None): | |
| 354 """Initializes the folder. | |
| 355 | |
| 356 Args: | |
| 357 Name: Filter (folder) name. | |
| 358 entries: List of filenames and/or Filter objects contained. | |
| 359 """ | |
| 360 super(MSVSFilter, self).__init__(entries) | |
| 361 self.Name = Name | |
| 362 | |
| 363 def CreateElement(self, doc, node_func=lambda x: x): | |
| 364 """Creates an element for the Filter. | |
| 365 | |
| 366 Args: | |
| 367 doc: xml.dom.Document object to use for node creation. | |
| 368 node_func: Function to use to return Nodes for objects that | |
| 369 don't have a CreateElement() method of their own. | |
| 370 | |
| 371 Returns: | |
| 372 A new xml.dom.Element for the filter. | |
| 373 """ | |
| 374 node = super(MSVSFilter, self).CreateElement(doc, node_func) | |
| 375 node.setAttribute('Name', self.Name) | |
| 376 return node | |
| 377 | |
| 378 | |
| 379 class MSVSTool(object): | |
| 380 """Visual Studio tool.""" | |
| 381 | |
| 382 def __init__(self, Name, **attrs): | |
| 383 """Initializes the tool. | |
| 384 | |
| 385 Args: | |
| 386 Name: Tool name. | |
| 387 **attrs: Tool attributes. | |
| 388 """ | |
| 389 | |
| 390 val = attrs.get('AdditionalDependencies') | |
| 391 if val: | |
| 392 if isinstance(val, list): | |
| 393 val = ' '.join(val) | |
| 394 attrs['AdditionalDependencies'] = val.replace('/', '\\') | |
| 395 | |
| 396 val = attrs.get('AdditionalIncludeDirectories') | |
| 397 if val: | |
| 398 if isinstance(val, list): | |
| 399 val = ';'.join(val) | |
| 400 attrs['AdditionalIncludeDirectories'] = val.replace('/', '\\') | |
| 401 | |
| 402 val = attrs.get('AdditionalManifestFiles') | |
| 403 if val: | |
| 404 if isinstance(val, list): | |
| 405 val = ';'.join(val) | |
| 406 attrs['AdditionalManifestFiles'] = val.replace('/', '\\') | |
| 407 | |
| 408 val = attrs.get('CommandLine') | |
| 409 if val: | |
| 410 if isinstance(val, list): | |
| 411 val = '\r\n'.join(val) | |
| 412 attrs['CommandLine'] = val.replace('/', '\\') | |
| 413 | |
| 414 val = attrs.get('PreprocessorDefinitions') | |
| 415 if val: | |
| 416 if isinstance(val, list): | |
| 417 val = ';'.join(val) | |
| 418 attrs['PreprocessorDefinitions'] = val | |
| 419 | |
| 420 for a in ('ImportLibrary', | |
| 421 'ObjectFile', | |
| 422 'OutputFile', | |
| 423 'Outputs', | |
| 424 'XMLDocumentationFileName'): | |
| 425 val = attrs.get(a) | |
| 426 if val: | |
| 427 val = val.replace('/', '\\') | |
| 428 attrs[a] = val | |
| 429 | |
| 430 self.Name = Name | |
| 431 self.attrs = attrs | |
| 432 | |
| 433 def CreateElement(self, doc): | |
| 434 """Creates an element for the tool. | |
| 435 | |
| 436 Args: | |
| 437 doc: xml.dom.Document object to use for node creation. | |
| 438 | |
| 439 Returns: | |
| 440 A new xml.dom.Element for the tool. | |
| 441 """ | |
| 442 node = doc.createElement('Tool') | |
| 443 node.setAttribute('Name', self.Name) | |
| 444 for k, v in self.attrs.items(): | |
| 445 node.setAttribute(k, v) | |
| 446 return node | |
| 447 | |
| 448 def _format(self): | |
| 449 """Formats a tool specification for debug printing""" | |
| 450 xml_impl = xml.dom.getDOMImplementation() | |
| 451 doc = xml_impl.createDocument(None, 'VisualStudioProject', None) | |
| 452 return self.CreateElement(doc).toprettyxml() | |
| 453 | |
| 454 def diff(self, other): | |
| 455 for key, value in self.attrs.items(): | |
| 456 if other.attrs[key] == value: | |
| 457 del self.attrs[key] | |
| 458 | |
| 459 | |
| 460 class MSVSToolFile(object): | |
| 461 """Visual Studio tool file specification.""" | |
| 462 | |
| 463 def __init__(self, node, **attrs): | |
| 464 """Initializes the tool. | |
| 465 | |
| 466 Args: | |
| 467 node: Node for the Tool File | |
| 468 **attrs: Tool File attributes. | |
| 469 """ | |
| 470 self.node = node | |
| 471 | |
| 472 def CreateElement(self, doc, project): | |
| 473 result = doc.createElement('ToolFile') | |
| 474 result.setAttribute('RelativePath', project.get_rel_path(self.node)) | |
| 475 return result | |
| 476 | |
| 477 | |
| 478 #------------------------------------------------------------------------------ | |
| 479 | |
| 480 def MSVSAction(target, source, env): | |
| 481 target[0].Write(env) | |
| 482 | |
| 483 MSVSProjectAction = SCons.Script.Action(MSVSAction, | |
| 484 "Generating Visual Studio project `$TARG
ET' ...") | |
| 485 | |
| 486 class _MSVSProject(SCons.Node.FS.File): | |
| 487 """Visual Studio project.""" | |
| 488 | |
| 489 entry_type_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' | |
| 490 initialized = False | |
| 491 | |
| 492 def initialize(self, env, path, name = None, | |
| 493 dependencies = None, | |
| 494 guid = None, | |
| 495 buildtargets = [], | |
| 496 files = [], | |
| 497 root_namespace = None, | |
| 498 keyword = None, | |
| 499 relative_path_prefix = None, | |
| 500 local_directory_prefix = None, | |
| 501 relative_path_substitutions = [], | |
| 502 tools = None, | |
| 503 configurations = None, | |
| 504 **attrs): | |
| 505 """Initializes the project. | |
| 506 | |
| 507 Args: | |
| 508 path: Relative path to project file. | |
| 509 name: Name of project. If None, the name will be the same as the base | |
| 510 name of the project file. | |
| 511 dependencies: List of other Project objects this project is dependent | |
| 512 upon, if not None. | |
| 513 guid: GUID to use for project, if not None. | |
| 514 buildtargets: List of target(s) being built by this project. | |
| 515 files: List of source files for the project. This will be | |
| 516 supplemented by any source files of buildtargets. | |
| 517 root_namespace: The value of the RootNamespace attribute of the | |
| 518 project, if not None. The default is to use the same | |
| 519 string as the name. | |
| 520 relative_path_prefix: A prefix to be appended to the beginning of | |
| 521 every file name in the list. The canonical use is to specify | |
| 522 './' to make files explicitly relative to the local directory. | |
| 523 tools: A list of MSVSTool objects or strings representing | |
| 524 tools to be used to build this project. This will be used | |
| 525 for any configurations that don't provide their own | |
| 526 per-configuration tool list. | |
| 527 configurations: A list of MSVSConfig objects representing | |
| 528 configurations built by this project. | |
| 529 """ | |
| 530 if name is None: | |
| 531 if buildtargets: | |
| 532 name = os.path.splitext(buildtargets[0].name)[0] | |
| 533 else: | |
| 534 name = os.path.splitext(os.path.basename(path))[0] | |
| 535 if not root_namespace: | |
| 536 root_namespace or name | |
| 537 | |
| 538 if self.initialized: | |
| 539 # TODO(sgk): fill in | |
| 540 if self.msvs_name != name: | |
| 541 pass | |
| 542 if self.root_namespace != root_namespace: | |
| 543 pass | |
| 544 if self.relative_path_prefix != relative_path_prefix: | |
| 545 pass | |
| 546 if self.guid != guid: | |
| 547 pass | |
| 548 #if self.env != env: | |
| 549 # pass | |
| 550 else: | |
| 551 self.buildtargets = [] | |
| 552 self.configurations = [] | |
| 553 self.dependencies = [] | |
| 554 self.file_configurations = {} | |
| 555 self.files = MSVSFiles([]) | |
| 556 self.tool_files = [] | |
| 557 self.file_lists = [] | |
| 558 self.initialized = True | |
| 559 self.keyword = None | |
| 560 self.local_directory_prefix = '' | |
| 561 self.relative_path_prefix = '' | |
| 562 self.relative_path_substitutions = [] | |
| 563 self.root_namespace = name | |
| 564 | |
| 565 self.attrs = attrs | |
| 566 self.env = env | |
| 567 self.guid = guid | |
| 568 self.msvs_name = name | |
| 569 self.msvs_path = path | |
| 570 if relative_path_prefix: | |
| 571 self.relative_path_prefix = relative_path_prefix | |
| 572 if local_directory_prefix: | |
| 573 self.local_directory_prefix = local_directory_prefix | |
| 574 for left, right in relative_path_substitutions: | |
| 575 t = (left.replace('/', '\\'), right.replace('/', '\\')) | |
| 576 self.relative_path_substitutions.append(t) | |
| 577 if root_namespace: | |
| 578 self.root_namespace = root_namespace | |
| 579 if keyword: | |
| 580 self.keyword = keyword | |
| 581 self.tools = tools | |
| 582 | |
| 583 self.buildtargets.extend(buildtargets) | |
| 584 self.configurations.extend(configurations or []) | |
| 585 self.dependencies.extend(list(dependencies or [])) | |
| 586 self.AddFiles(files) | |
| 587 | |
| 588 env.Command(self, [], MSVSProjectAction) | |
| 589 | |
| 590 def args2nodes(self, entries): | |
| 591 result = [] | |
| 592 for entry in entries: | |
| 593 if SCons.Util.is_String(entry): | |
| 594 entry = self.env.File(entry) | |
| 595 result.append(entry.srcnode()) | |
| 596 elif hasattr(entry, 'entries'): | |
| 597 entry.entries = self.args2nodes(entry.entries) | |
| 598 result.append(entry) | |
| 599 elif isinstance(entry, (list, UserList.UserList)): | |
| 600 result.extend(self.args2nodes(entry)) | |
| 601 elif hasattr(entry, 'sources') and entry.sources: | |
| 602 result.extend(entry.sources) | |
| 603 else: | |
| 604 result.append(entry.srcnode()) | |
| 605 return result | |
| 606 | |
| 607 def FindFile(self, node): | |
| 608 try: | |
| 609 flat_file_dict = self.flat_file_dict | |
| 610 except AttributeError: | |
| 611 flat_file_dict = {} | |
| 612 file_list = self.files[:] | |
| 613 while file_list: | |
| 614 entry = file_list.pop(0) | |
| 615 if not isinstance(entry, (list, UserList.UserList)): | |
| 616 entry = [entry] | |
| 617 for f in entry: | |
| 618 if hasattr(f, 'entries'): | |
| 619 file_list.extend(f.entries) | |
| 620 else: | |
| 621 flat_file_dict[f] = True | |
| 622 flat_file_dict[f.srcnode()] = True | |
| 623 if hasattr(f, 'sources'): | |
| 624 for s in f.sources: | |
| 625 flat_file_dict[s] = True | |
| 626 flat_file_dict[s.srcnode()] = True | |
| 627 self.flat_file_dict = flat_file_dict | |
| 628 | |
| 629 return flat_file_dict.get(node) | |
| 630 | |
| 631 def get_guid(self): | |
| 632 if self.guid is None: | |
| 633 guid = GUIDMap.get(self.msvs_path) | |
| 634 if not guid: | |
| 635 # Set GUID from path | |
| 636 # TODO(rspangler): This is fragile. | |
| 637 # 1. We can't just use the project filename sans path, since there | |
| 638 # could be multiple projects with the same base name (for example, | |
| 639 # foo/unittest.vcproj and bar/unittest.vcproj). | |
| 640 # 2. The path needs to be relative to $SOURCE_ROOT, so that the project | |
| 641 # GUID is the same whether it's included from base/base.sln or | |
| 642 # foo/bar/baz/baz.sln. | |
| 643 # 3. The GUID needs to be the same each time this builder is invoked, | |
| 644 # so that we don't need to rebuild the solution when the | |
| 645 # project changes. | |
| 646 # 4. We should be able to handle pre-built project files by reading the | |
| 647 # GUID from the files. | |
| 648 guid = MakeGuid(self.msvs_path) | |
| 649 self.guid = guid | |
| 650 return self.guid | |
| 651 | |
| 652 def get_msvs_path(self, sln): | |
| 653 return sln.rel_path(self).replace('/', '\\') | |
| 654 | |
| 655 def get_rel_path(self, node): | |
| 656 result = self.rel_path(node) | |
| 657 if self.relative_path_prefix: | |
| 658 if not result.startswith('..'): | |
| 659 result = self.relative_path_prefix + result | |
| 660 elif not os.path.split(result)[0]: | |
| 661 result = self.local_directory_prefix + result | |
| 662 result = result.replace('/', '\\') | |
| 663 for left, right in self.relative_path_substitutions: | |
| 664 result = result.replace(left, right) | |
| 665 return result | |
| 666 | |
| 667 def AddConfig(self, Name, tools=None, **attrs): | |
| 668 """Adds a configuration to the parent node. | |
| 669 | |
| 670 Args: | |
| 671 Name: The name of the configuration. | |
| 672 tools: List of tools (strings or Tool objects); may be None. | |
| 673 **attrs: Configuration attributes. | |
| 674 """ | |
| 675 if tools is None: | |
| 676 # No tool list specifically for this configuration, | |
| 677 # use the Project's as a default. | |
| 678 tools = self.tools | |
| 679 if not attrs.has_key('ConfigurationType'): | |
| 680 # No ConfigurationType specifically for this configuration, | |
| 681 # use the Project's as a default. | |
| 682 try: | |
| 683 attrs['ConfigurationType'] = self.attrs['ConfigurationType'] | |
| 684 except KeyError: | |
| 685 pass | |
| 686 if attrs.has_key('InheritedPropertySheets'): | |
| 687 ips = attrs['InheritedPropertySheets'] | |
| 688 attrs['InheritedPropertySheets'] = self.env.subst(ips) | |
| 689 c = MSVSConfig(Name, 'Configuration', tools=tools, **attrs) | |
| 690 self.configurations.append(c) | |
| 691 | |
| 692 def AddFiles(self, files): | |
| 693 """Adds files to the project. | |
| 694 | |
| 695 Args: | |
| 696 files: A list of Filter objects and/or relative paths to files. | |
| 697 | |
| 698 This makes a copy of the file/filter tree at the time of this call. If you | |
| 699 later add files to a Filter object which was passed into a previous call | |
| 700 to AddFiles(), it will not be reflected in this project. | |
| 701 """ | |
| 702 self.file_lists.append(self.args2nodes(files)) | |
| 703 | |
| 704 def _FilesToSourceFiles(self, files): | |
| 705 file_list = files[:] | |
| 706 result = [] | |
| 707 while file_list: | |
| 708 entry = file_list.pop(0) | |
| 709 if not isinstance(entry, (list, UserList.UserList)): | |
| 710 entry = [entry] | |
| 711 for f in entry: | |
| 712 if hasattr(f, 'entries'): | |
| 713 self._FilesToSourceFiles(f.entries) | |
| 714 result.append(f) | |
| 715 else: | |
| 716 if f.sources: | |
| 717 flist = f.sources | |
| 718 else: | |
| 719 flist = [f] | |
| 720 for x in flist: | |
| 721 result.append(x.srcnode()) | |
| 722 files[:] = result | |
| 723 | |
| 724 def _MergeFiles(self, dest_list, src_list): | |
| 725 for f in src_list: | |
| 726 if f not in dest_list: | |
| 727 dest_list.append(f) | |
| 728 continue | |
| 729 #if hasattr(f, 'entries'): | |
| 730 # self._FilesToSourceFiles(f.entries) | |
| 731 | |
| 732 def AddFileConfig(self, path, Name, tools=None, **attrs): | |
| 733 """Adds a configuration to a file. | |
| 734 | |
| 735 Args: | |
| 736 path: Relative path to the file. | |
| 737 Name: Name of configuration to add. | |
| 738 tools: List of tools (strings or MSVSTool objects); may be None. | |
| 739 **attrs: Configuration attributes. | |
| 740 | |
| 741 Raises: | |
| 742 ValueError: Relative path does not match any file added via AddFiles(). | |
| 743 """ | |
| 744 # Store as the VariantDir node, not as the source node. | |
| 745 node = self.env.File(path) | |
| 746 c = MSVSConfig(Name, 'FileConfiguration', tools=tools, **attrs) | |
| 747 config_list = self.file_configurations.get(node) | |
| 748 if config_list is None: | |
| 749 config_list = [] | |
| 750 self.file_configurations[node] = config_list | |
| 751 config_list.append(c) | |
| 752 | |
| 753 def AddToolFile(self, path): | |
| 754 """Adds a tool file to the project. | |
| 755 | |
| 756 Args: | |
| 757 path: Relative path from project to tool file. | |
| 758 """ | |
| 759 tf = MSVSToolFile(self.env.File(path)) | |
| 760 self.tool_files.append(tf) | |
| 761 | |
| 762 def Create(self): | |
| 763 """Creates the project document. | |
| 764 | |
| 765 Args: | |
| 766 name: Name of the project. | |
| 767 guid: GUID to use for project, if not None. | |
| 768 """ | |
| 769 # Create XML doc | |
| 770 xml_impl = xml.dom.getDOMImplementation() | |
| 771 self.doc = xml_impl.createDocument(None, 'VisualStudioProject', None) | |
| 772 | |
| 773 # Add attributes to root element | |
| 774 root = self.doc.documentElement | |
| 775 root.setAttribute('ProjectType', 'Visual C++') | |
| 776 root.setAttribute('Version', '8.00') | |
| 777 root.setAttribute('Name', self.msvs_name) | |
| 778 root.setAttribute('ProjectGUID', self.get_guid()) | |
| 779 root.setAttribute('RootNamespace', self.root_namespace) | |
| 780 if self.keyword: | |
| 781 root.setAttribute('Keyword', self.keyword) | |
| 782 | |
| 783 # Add platform list | |
| 784 platforms = self.doc.createElement('Platforms') | |
| 785 root.appendChild(platforms) | |
| 786 n = self.doc.createElement('Platform') | |
| 787 n.setAttribute('Name', 'Win32') | |
| 788 platforms.appendChild(n) | |
| 789 | |
| 790 # Add tool files section | |
| 791 tool_files = self.doc.createElement('ToolFiles') | |
| 792 root.appendChild(tool_files) | |
| 793 for tf in self.tool_files: | |
| 794 tool_files.appendChild(tf.CreateElement(self.doc, self)) | |
| 795 | |
| 796 # Add configurations section | |
| 797 configs = self.doc.createElement('Configurations') | |
| 798 root.appendChild(configs) | |
| 799 for c in self.configurations: | |
| 800 configs.appendChild(c.CreateElement(self.doc, self)) | |
| 801 | |
| 802 # Add empty References section | |
| 803 root.appendChild(self.doc.createElement('References')) | |
| 804 | |
| 805 # Add files section | |
| 806 root.appendChild(self.files.CreateElement(self.doc, self.CreateFileElement)) | |
| 807 | |
| 808 # Add empty Globals section | |
| 809 root.appendChild(self.doc.createElement('Globals')) | |
| 810 | |
| 811 def CreateFileElement(self, file): | |
| 812 """Create a DOM node for the specified file. | |
| 813 | |
| 814 Args: | |
| 815 file: The file Node being considered. | |
| 816 | |
| 817 Returns: | |
| 818 A DOM Node for the File, with a relative path to the current | |
| 819 project object, and any file configurations attached to the | |
| 820 project. | |
| 821 """ | |
| 822 | |
| 823 node = self.doc.createElement('File') | |
| 824 node.setAttribute('RelativePath', self.get_rel_path(file)) | |
| 825 for c in self.file_configurations.get(file, []): | |
| 826 node.appendChild(c.CreateElement(self.doc, self)) | |
| 827 return node | |
| 828 | |
| 829 def VCCLCompilerTool(self, args): | |
| 830 default_attrs = { | |
| 831 'BufferSecurityCheck' : "false", | |
| 832 'CompileAs' : 0, # default | |
| 833 'DebugInformationFormat' : 0, # TODO(???) | |
| 834 'DisableSpecificWarnings' : [], | |
| 835 'EnableFiberSafeOptimizations' : "false", | |
| 836 'EnableFunctionLevelLinking' : "false", | |
| 837 'EnableIntrinsicFunctions' : "false", | |
| 838 'FavorSizeOrSpeed' : 0, # favorNone | |
| 839 'InlineFunctionExpansion' : 1, # expandDisable | |
| 840 'MinimalRebuild' : "false", | |
| 841 'OmitFramePointers' : "false", | |
| 842 'Optimization' : 1, # optimizeDisabled TODO(???) | |
| 843 'PreprocessorDefinitions' : [], | |
| 844 'RuntimeLibrary' : TODO, | |
| 845 'RuntimeTypeInfo' : "false", | |
| 846 'StringPooling' : "false", | |
| 847 'SuppressStartupBanner' : "false", | |
| 848 'WarningAsError' : "false", | |
| 849 'WarningLevel' : 1, # warningLevel_1 | |
| 850 'WholeProgramOptimization' : "false", | |
| 851 } | |
| 852 | |
| 853 tool = MSVSTool('VCCLCompilerTool', **default_attrs) | |
| 854 attrs = tool.attrs | |
| 855 | |
| 856 for arg in args: | |
| 857 if arg in ('/c',): | |
| 858 continue | |
| 859 if arg.startswith('/Fo'): | |
| 860 continue | |
| 861 if arg.startswith('/D'): | |
| 862 attrs['PreprocessorDefinitions'].append(arg[2:]) | |
| 863 elif arg == '/EH': | |
| 864 attrs['ExceptionHandling'] = 0 | |
| 865 elif arg == '/GF': | |
| 866 attrs['StringPooling'] = "true" | |
| 867 elif arg == '/GL': | |
| 868 attrs['WholeProgramOptimization'] = "true" | |
| 869 elif arg == '/GM': | |
| 870 attrs['MinimalRebuild'] = "true" | |
| 871 elif arg == '/GR-': | |
| 872 attrs['RuntimeTypeInfo'] = "true" | |
| 873 elif arg == '/Gs': | |
| 874 attrs['BufferSecurityCheck'] = "true" | |
| 875 elif arg == '/Gs-': | |
| 876 attrs['BufferSecurityCheck'] = "false" | |
| 877 elif arg == '/GT': | |
| 878 attrs['EnableFiberSafeOptimizations'] = "true" | |
| 879 elif arg == '/Gy': | |
| 880 attrs['EnableFunctionLevelLinking'] = "true" | |
| 881 elif arg == '/MD': | |
| 882 attrs['RuntimeLibrary'] = 1 # rtMultiThreadedDebug | |
| 883 elif arg == '/MDd': | |
| 884 attrs['RuntimeLibrary'] = 2 # rtMultiThreadedDebugDLL | |
| 885 elif arg == '/MT': | |
| 886 attrs['RuntimeLibrary'] = 0 # rtMultiThreaded | |
| 887 elif arg == '/MTd': | |
| 888 attrs['RuntimeLibrary'] = 3 # rtMultiThreadedDLL | |
| 889 elif arg == '/nologo': | |
| 890 attrs['SuppressStartupBanner'] = "true" | |
| 891 elif arg == '/O1': | |
| 892 attrs['InlineFunctionExpansion'] = 4 # optimizeMinSpace | |
| 893 elif arg == '/O2': | |
| 894 attrs['InlineFunctionExpansion'] = 3 # optimizeMaxSpeed | |
| 895 elif arg == '/Ob1': | |
| 896 attrs['InlineFunctionExpansion'] = 2 # expandOnlyInline | |
| 897 elif arg == '/Ob2': | |
| 898 attrs['InlineFunctionExpansion'] = 0 # expandAnySuitable | |
| 899 elif arg == '/Od': | |
| 900 attrs['Optimization'] = 0 | |
| 901 elif arg == '/Oi': | |
| 902 attrs['EnableIntrinsicFunctions'] = "true" | |
| 903 elif arg == '/Os': | |
| 904 attrs['FavorSizeOrSpeed'] = 1 # favorSize | |
| 905 elif arg == '/Ot': | |
| 906 attrs['FavorSizeOrSpeed'] = 2 # favorSpeed | |
| 907 elif arg == '/Ox': | |
| 908 attrs['Optimization'] = 2 # optimizeFull | |
| 909 elif arg == '/Oy': | |
| 910 attrs['OmitFramePointers'] = "true" | |
| 911 elif arg == '/Oy-': | |
| 912 attrs['TODO'] = "true" | |
| 913 elif arg in ('/Tc', '/TC'): | |
| 914 attrs['CompileAs'] = 1 # compileAsC | |
| 915 elif arg in ('/Tp', '/TP'): | |
| 916 attrs['CompileAs'] = 2 # compileAsCPlusPlus | |
| 917 elif arg == '/WX': | |
| 918 attrs['WarnAsError'] = "true" | |
| 919 elif arg.startswith('/W'): | |
| 920 attrs['WarningLevel'] = int(arg[2:]) # 0 through 4 | |
| 921 elif arg.startswith('/wd'): | |
| 922 attrs['DisableSpecificWarnings'].append(str(arg[3:])) | |
| 923 elif arg == '/Z7': | |
| 924 attrs['DebugInformationFormat'] = 3 # debugOldSytleInfo TODO(???) | |
| 925 elif arg == '/Zd': | |
| 926 attrs['DebugInformationFormat'] = 0 # debugDisabled | |
| 927 elif arg == '/Zi': | |
| 928 attrs['DebugInformationFormat'] = 2 # debugEnabled TODO(???) | |
| 929 elif arg == '/ZI': | |
| 930 attrs['DebugInformationFormat'] = 1 # debugEditAndContinue TODO(???) | |
| 931 | |
| 932 cppdefines = attrs['PreprocessorDefinitions'] | |
| 933 if cppdefines: | |
| 934 attrs['PreprocessorDefinitions'] = ';'.join(cppdefines) | |
| 935 warnings = attrs['DisableSpecificWarnings'] | |
| 936 if warnings: | |
| 937 warnings = SCons.Util.uniquer(warnings) | |
| 938 attrs['DisableSpecificWarnings'] = ';'.join(warnings) | |
| 939 | |
| 940 return tool | |
| 941 | |
| 942 def VCLibrarianTool(self, args): | |
| 943 default_attrs = { | |
| 944 'LinkTimeCodeGeneration' : "false", | |
| 945 'SuppressStartupBanner' : "false", | |
| 946 } | |
| 947 | |
| 948 tool = MSVSTool('VCLibrarianTool', **default_attrs) | |
| 949 attrs = tool.attrs | |
| 950 | |
| 951 for arg in args: | |
| 952 if arg.startswith('/OUT'): | |
| 953 continue | |
| 954 if arg == '/ltcg': | |
| 955 attrs['LinkTimeCodeGeneration'] = "true" | |
| 956 elif arg == '/nologo': | |
| 957 attrs['SuppressStartupBanner'] = "true" | |
| 958 | |
| 959 return tool | |
| 960 | |
| 961 def VCLinkerTool(self, args): | |
| 962 default_attrs = { | |
| 963 'LinkIncremental' : "false", | |
| 964 'LinkTimeCodeGeneration' : "false", | |
| 965 'EnableCOMDATFolding' : TODO, | |
| 966 'OptimizeForWindows98' : TODO, | |
| 967 'OptimizeReferences' : TODO, | |
| 968 'Profile' : "false", | |
| 969 'SuppressStartupBanner' : "false", | |
| 970 } | |
| 971 | |
| 972 tool = MSVSTool('VCLinkerTool', **default_attrs) | |
| 973 attrs = tool.attrs | |
| 974 | |
| 975 for arg in args: | |
| 976 if arg == '': | |
| 977 continue | |
| 978 if arg == '/INCREMENTAL': | |
| 979 attrs['LinkIncremental'] = "true" | |
| 980 elif arg == '/INCREMENTAL:NO': | |
| 981 attrs['LinkIncremental'] = "false" | |
| 982 elif arg == '/LTCG': | |
| 983 attrs['LinkTimeCodeGeneration'] = "true" | |
| 984 elif arg == '/nologo': | |
| 985 attrs['SuppressStartupBanner'] = "true" | |
| 986 elif arg == '/OPT:NOICF': | |
| 987 attrs['EnableCOMDATFolding'] = 2 # | |
| 988 elif arg == '/OPT:NOWIN98': | |
| 989 attrs['OptimizeForWindows98'] = 1 # | |
| 990 elif arg == '/OPT:REF': | |
| 991 attrs['OptimizeReferences'] = 2 # | |
| 992 elif arg == '/PROFILE': | |
| 993 attrs['Profile'] = "true" | |
| 994 | |
| 995 return tool | |
| 996 | |
| 997 command_to_tool_map = { | |
| 998 'cl' : 'VCCLCompilerTool', | |
| 999 'cl.exe' : 'VCCLCompilerTool', | |
| 1000 'lib' : 'VCLibrarianTool', | |
| 1001 'lib.exe' : 'VCLibrarianTool', | |
| 1002 'link' : 'VCLinkerTool', | |
| 1003 'link.exe' : 'VCLinkerTool', | |
| 1004 } | |
| 1005 | |
| 1006 def cl_to_tool(self, args): | |
| 1007 command = os.path.basename(args[0]) | |
| 1008 method_name = self.command_to_tool_map.get(command) | |
| 1009 if not method_name: | |
| 1010 return None | |
| 1011 return getattr(self, method_name)(args[1:]) | |
| 1012 | |
| 1013 def _AddFileConfigurationDifferences(self, target, source, base_env, file_env,
name): | |
| 1014 """Adds a per-file configuration. | |
| 1015 | |
| 1016 Args: | |
| 1017 target: The target being built from the source. | |
| 1018 source: The source to which the file configuration is being added. | |
| 1019 base_env: The base construction environment for the project. | |
| 1020 Differences from this will go into the FileConfiguration | |
| 1021 in the project file. | |
| 1022 file_env: The construction environment for the target, containing | |
| 1023 the per-target settings. | |
| 1024 """ | |
| 1025 executor = target.get_executor() | |
| 1026 base_cl = map(str, base_env.subst_list(executor)[0]) | |
| 1027 file_cl = map(str, file_env.subst_list(executor)[0]) | |
| 1028 if base_cl == file_cl: | |
| 1029 return | |
| 1030 | |
| 1031 base_tool = self.cl_to_tool(base_cl) | |
| 1032 file_tool = self.cl_to_tool(file_cl) | |
| 1033 | |
| 1034 if not base_tool or not_file_tool: | |
| 1035 return | |
| 1036 | |
| 1037 file_tool.diff(base_tool) | |
| 1038 | |
| 1039 self.AddFileConfig(source, name, tools=[file_tool]) | |
| 1040 | |
| 1041 def _AddFileConfigurations(self, env): | |
| 1042 """Adds per-file configurations for the buildtarget's sources. | |
| 1043 | |
| 1044 Args: | |
| 1045 env: The base construction environment for the project. | |
| 1046 """ | |
| 1047 if not self.buildtargets: | |
| 1048 return | |
| 1049 | |
| 1050 for bt in self.buildtargets: | |
| 1051 executor = bt.get_executor() | |
| 1052 build_env = bt.get_build_env() | |
| 1053 bt_cl = map(str, build_env.subst_list(executor)[0]) | |
| 1054 tool = self.cl_to_tool(bt_cl) | |
| 1055 default_tool = self.cl_to_tool([bt_cl[0]]) | |
| 1056 if default_tool: | |
| 1057 tool.diff(default_tool) | |
| 1058 else: | |
| 1059 # TODO(sgk): print a message unconditionally is too | |
| 1060 # verbose for things like Python function actions, | |
| 1061 # but doing nothing runs the risk of burying problems. | |
| 1062 # Work out a better solution. | |
| 1063 #print "no tool for %r" % bt_cl[0] | |
| 1064 pass | |
| 1065 for t in bt.sources: | |
| 1066 e = t.get_build_env() | |
| 1067 additional_files = SCons.Util.UniqueList() | |
| 1068 for s in t.sources: | |
| 1069 s = env.arg2nodes([s])[0].srcnode() | |
| 1070 if not self.FindFile(s): | |
| 1071 additional_files.append(s) | |
| 1072 if not build_env is e: | |
| 1073 # TODO(sgk): This test may be bogus, but it works for now. | |
| 1074 # We're trying to figure out if the file configuration | |
| 1075 # differences need to be added one per build target, or one | |
| 1076 # per configuration for the entire project. The assumption | |
| 1077 # is that if the number of buildtargets configured matches | |
| 1078 # the number of project configurations, that we use those | |
| 1079 # in preference to the project configurations. | |
| 1080 if len(self.buildtargets) == len(self.configurations): | |
| 1081 self._AddFileConfigurationDifferences(t, s, build_env, e, e.subst(
'$MSVSCONFIGURATIONNAME')) | |
| 1082 else: | |
| 1083 for config in self.configurations: | |
| 1084 self._AddFileConfigurationDifferences(t, s, build_env, e, config
.Name) | |
| 1085 self._MergeFiles(self.files, additional_files) | |
| 1086 | |
| 1087 def Write(self, env): | |
| 1088 """Writes the project file.""" | |
| 1089 for flist in self.file_lists: | |
| 1090 self._FilesToSourceFiles(flist) | |
| 1091 self._MergeFiles(self.files, flist) | |
| 1092 for k, v in self.file_configurations.items(): | |
| 1093 self.file_configurations[str(k)] = v | |
| 1094 k = self.env.File(k).srcnode() | |
| 1095 self.file_configurations[k] = v | |
| 1096 self.file_configurations[str(k)] = v | |
| 1097 self._AddFileConfigurations(env) | |
| 1098 | |
| 1099 self.Create() | |
| 1100 | |
| 1101 f = open(str(self), 'wt') | |
| 1102 f.write(self.formatMSVSProjectXML(self.doc)) | |
| 1103 f.close() | |
| 1104 | |
| 1105 # Methods for formatting XML as nearly identically to Microsoft's | |
| 1106 # .vcproj output as we can practically make it. | |
| 1107 # | |
| 1108 # The general design here is copied from: | |
| 1109 # | |
| 1110 # Bruce Eckels' MindView, Inc: 12-09-04 XML Oddyssey | |
| 1111 # http://www.mindview.net/WebLog/log-0068 | |
| 1112 # | |
| 1113 # Eckels' implementation broke up long tag definitions for readability, | |
| 1114 # in much the same way that .vcproj does, but we've modified things | |
| 1115 # for .vcproj quirks (like some tags *always* terminating with </Tag>, | |
| 1116 # even when empty). | |
| 1117 | |
| 1118 encoding = 'Windows-1252' | |
| 1119 | |
| 1120 def formatMSVSProjectXML(self, xmldoc): | |
| 1121 xmldoc = xmldoc.toprettyxml("", "\n", encoding=self.encoding) | |
| 1122 # Remove trailing whitespace from each line: | |
| 1123 xmldoc = "\n".join( | |
| 1124 [line.rstrip() for line in xmldoc.split("\n")]) | |
| 1125 # Remove all empty lines before opening '<': | |
| 1126 while xmldoc.find("\n\n<") != -1: | |
| 1127 xmldoc = xmldoc.replace("\n\n<", "\n<") | |
| 1128 dom = xml.dom.minidom.parseString(xmldoc) | |
| 1129 xmldoc = dom.toprettyxml("\t", "", encoding=self.encoding) | |
| 1130 xmldoc = xmldoc.replace('?><', '?>\n<') | |
| 1131 xmldoc = self.reformatLines(xmldoc) | |
| 1132 return xmldoc | |
| 1133 | |
| 1134 def reformatLines(self, xmldoc): | |
| 1135 result = [] | |
| 1136 for line in [line.rstrip() for line in xmldoc.split("\n")]: | |
| 1137 if line.lstrip().startswith("<"): | |
| 1138 result.append(self.reformatLine(line) + "\n") | |
| 1139 else: | |
| 1140 result.append(line + "\n") | |
| 1141 return ''.join(result) | |
| 1142 | |
| 1143 # Keyword order for specific tags. | |
| 1144 # | |
| 1145 # Listed keywords will come first and in the specified order. | |
| 1146 # Any unlisted keywords come after, in whatever order they appear | |
| 1147 # in the input config. In theory this means we would only *have* to | |
| 1148 # list the keywords that we care about, but in practice we'll probably | |
| 1149 # want to nail down Visual Studio's order to make sure we match them | |
| 1150 # as nearly as possible. | |
| 1151 | |
| 1152 order = { | |
| 1153 'Configuration' : [ | |
| 1154 'Name', | |
| 1155 'ConfigurationType', | |
| 1156 'InheritedPropertySheets', | |
| 1157 ], | |
| 1158 'FileConfiguration' : [ | |
| 1159 'Name', | |
| 1160 'ExcludedFromBuild', | |
| 1161 ], | |
| 1162 'Tool' : [ | |
| 1163 'Name', | |
| 1164 'DisableSpecificWarnings', | |
| 1165 | |
| 1166 'AdditionalIncludeDirectories', | |
| 1167 'Description', | |
| 1168 'CommandLine', | |
| 1169 'OutputFile', | |
| 1170 'ImportLibrary', | |
| 1171 'PreprocessorDefinitions', | |
| 1172 'UsePrecompiledHeader', | |
| 1173 'PrecompiledHeaderThrough', | |
| 1174 'ForcedIncludeFiles', | |
| 1175 ], | |
| 1176 'VisualStudioProject' : [ | |
| 1177 'ProjectType', | |
| 1178 'Version', | |
| 1179 'Name', | |
| 1180 'ProjectGUID', | |
| 1181 'RootNamespace', | |
| 1182 'Keyword', | |
| 1183 ], | |
| 1184 } | |
| 1185 | |
| 1186 force_closing_tag = [ | |
| 1187 'File', | |
| 1188 'Globals', | |
| 1189 'References', | |
| 1190 'ToolFiles' | |
| 1191 ] | |
| 1192 | |
| 1193 oneLiner = re.compile("(\s*)<(\w+)(.*)>") | |
| 1194 keyValuePair = re.compile('\w+="[^"]*?"') | |
| 1195 def reformatLine(self, line): | |
| 1196 """Reformat an xml tag to put each key-value | |
| 1197 element on a single indented line, for readability""" | |
| 1198 matchobj = self.oneLiner.match(line.rstrip()) | |
| 1199 if not matchobj: | |
| 1200 return line | |
| 1201 baseIndent, tag, rest = matchobj.groups() | |
| 1202 slash = '' | |
| 1203 if rest[-1:] == '/': | |
| 1204 slash = '/' | |
| 1205 rest = rest[:-1] | |
| 1206 result = [baseIndent + '<' + tag] | |
| 1207 indent = baseIndent + "\t" | |
| 1208 pairs = self.keyValuePair.findall(rest) | |
| 1209 for key in self.order.get(tag, []): | |
| 1210 for p in [ p for p in pairs if p.startswith(key+'=') ]: | |
| 1211 result.append("\n" + indent + p) | |
| 1212 pairs.remove(p) | |
| 1213 for pair in pairs: | |
| 1214 result.append("\n" + indent + pair) | |
| 1215 result = [''.join(result).rstrip()] | |
| 1216 | |
| 1217 if tag in self.force_closing_tag: | |
| 1218 # These force termination with </Tag>, so translate slash. | |
| 1219 if rest: | |
| 1220 result.append("\n") | |
| 1221 result.append(indent) | |
| 1222 result.append(">") | |
| 1223 if slash: | |
| 1224 result.append("\n") | |
| 1225 result.append(baseIndent + "</" + tag + ">") | |
| 1226 else: | |
| 1227 if rest: | |
| 1228 result.append("\n") | |
| 1229 if slash: | |
| 1230 result.append(baseIndent) | |
| 1231 else: | |
| 1232 result.append(indent) | |
| 1233 result.append(slash + ">") | |
| 1234 | |
| 1235 return ''.join(result) | |
| 1236 | |
| 1237 def MSVSProject(env, item, *args, **kw): | |
| 1238 if not SCons.Util.is_String(item): | |
| 1239 return item | |
| 1240 item = env.subst(item) | |
| 1241 result = env.fs._lookup(item, None, _MSVSProject, create=1) | |
| 1242 result.initialize(env, item, *args, **kw) | |
| 1243 LookupAdd(item, result) | |
| 1244 return result | |
| 1245 | |
| 1246 #------------------------------------------------------------------------------ | |
| 1247 | |
| 1248 MSVSSolutionAction = SCons.Script.Action(MSVSAction, | |
| 1249 "Generating Visual Studio solution `$TA
RGET' ...") | |
| 1250 | |
| 1251 class _MSVSSolution(SCons.Node.FS.File): | |
| 1252 """Visual Studio solution.""" | |
| 1253 | |
| 1254 def initialize(self, env, path, entries=None, variants=None, websiteProperties
=True): | |
| 1255 """Initializes the solution. | |
| 1256 | |
| 1257 Args: | |
| 1258 path: Path to solution file. | |
| 1259 entries: List of entries in solution. May contain Folder or Project | |
| 1260 objects. May be None, if the folder is empty. | |
| 1261 variants: List of build variant strings. If none, a default list will | |
| 1262 be used. | |
| 1263 """ | |
| 1264 self.msvs_path = path | |
| 1265 self.websiteProperties = websiteProperties | |
| 1266 | |
| 1267 # Copy passed lists (or set to empty lists) | |
| 1268 self.entries = list(entries or []) | |
| 1269 | |
| 1270 if variants: | |
| 1271 # Copy passed list | |
| 1272 self.variants = variants[:] | |
| 1273 else: | |
| 1274 # Use default | |
| 1275 self.variants = ['Debug|Win32', 'Release|Win32'] | |
| 1276 # TODO(rspangler): Need to be able to handle a mapping of solution config | |
| 1277 # to project config. Should we be able to handle variants being a dict, | |
| 1278 # or add a separate variant_map variable? If it's a dict, we can't | |
| 1279 # guarantee the order of variants since dict keys aren't ordered. | |
| 1280 | |
| 1281 env.Command(self, [], MSVSSolutionAction) | |
| 1282 | |
| 1283 def Write(self, env): | |
| 1284 """Writes the solution file to disk. | |
| 1285 | |
| 1286 Raises: | |
| 1287 IndexError: An entry appears multiple times. | |
| 1288 """ | |
| 1289 r = [] | |
| 1290 errors = [] | |
| 1291 | |
| 1292 def lookup_subst(item, env=env, errors=errors): | |
| 1293 if SCons.Util.is_String(item): | |
| 1294 lookup_item = env.subst(item) | |
| 1295 else: | |
| 1296 lookup_item = item | |
| 1297 try: | |
| 1298 return Lookup(lookup_item) | |
| 1299 except SCons.Errors.UserError: | |
| 1300 raise LookupError(item, lookup_item) | |
| 1301 | |
| 1302 # Walk the entry tree and collect all the folders and projects. | |
| 1303 all_entries = [] | |
| 1304 entries_to_check = self.entries[:] | |
| 1305 while entries_to_check: | |
| 1306 # Pop from the beginning of the list to preserve the user's order. | |
| 1307 entry = entries_to_check.pop(0) | |
| 1308 try: | |
| 1309 entry = lookup_subst(entry) | |
| 1310 except LookupError, e: | |
| 1311 errors.append("Could not look up entry `%s'." % e) | |
| 1312 continue | |
| 1313 | |
| 1314 # A project or folder can only appear once in the solution's folder tree. | |
| 1315 # This also protects from cycles. | |
| 1316 if entry in all_entries: | |
| 1317 #raise IndexError('Entry "%s" appears more than once in solution' % | |
| 1318 # e.name) | |
| 1319 continue | |
| 1320 | |
| 1321 all_entries.append(entry) | |
| 1322 | |
| 1323 # If this is a folder, check its entries too. | |
| 1324 if isinstance(entry, _MSVSFolder): | |
| 1325 entries_to_check += entry.entries | |
| 1326 | |
| 1327 # Header | |
| 1328 r.append('Microsoft Visual Studio Solution File, Format Version 9.00\n') | |
| 1329 r.append('# Visual Studio 2005\n') | |
| 1330 | |
| 1331 # Project entries | |
| 1332 for e in all_entries: | |
| 1333 r.append('Project("%s") = "%s", "%s", "%s"\n' % ( | |
| 1334 e.entry_type_guid, # Entry type GUID | |
| 1335 e.msvs_name, # Folder name | |
| 1336 e.get_msvs_path(self), # Folder name (again) | |
| 1337 e.get_guid(), # Entry GUID | |
| 1338 )) | |
| 1339 | |
| 1340 # TODO(rspangler): Need a way to configure this stuff | |
| 1341 if self.websiteProperties: | |
| 1342 r.append('\tProjectSection(WebsiteProperties) = preProject\n' | |
| 1343 '\t\tDebug.AspNetCompiler.Debug = "True"\n' | |
| 1344 '\t\tRelease.AspNetCompiler.Debug = "False"\n' | |
| 1345 '\tEndProjectSection\n') | |
| 1346 | |
| 1347 if isinstance(e, _MSVSFolder): | |
| 1348 if e.items: | |
| 1349 r.append('\tProjectSection(SolutionItems) = preProject\n') | |
| 1350 for i in e.items: | |
| 1351 i = i.replace('/', '\\') | |
| 1352 r.append('\t\t%s = %s\n' % (i, i)) | |
| 1353 r.append('\tEndProjectSection\n') | |
| 1354 | |
| 1355 if isinstance(e, _MSVSProject): | |
| 1356 if e.dependencies: | |
| 1357 r.append('\tProjectSection(ProjectDependencies) = postProject\n') | |
| 1358 for d in e.dependencies: | |
| 1359 try: | |
| 1360 d = lookup_subst(d) | |
| 1361 except LookupError, e: | |
| 1362 errors.append("Could not look up dependency `%s'." % e) | |
| 1363 else: | |
| 1364 r.append('\t\t%s = %s\n' % (d.get_guid(), d.get_guid())) | |
| 1365 r.append('\tEndProjectSection\n') | |
| 1366 | |
| 1367 r.append('EndProject\n') | |
| 1368 | |
| 1369 # Global section | |
| 1370 r.append('Global\n') | |
| 1371 | |
| 1372 # Configurations (variants) | |
| 1373 r.append('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') | |
| 1374 for v in self.variants: | |
| 1375 r.append('\t\t%s = %s\n' % (v, v)) | |
| 1376 r.append('\tEndGlobalSection\n') | |
| 1377 | |
| 1378 # Sort config guids for easier diffing of solution changes. | |
| 1379 config_guids = [] | |
| 1380 for e in all_entries: | |
| 1381 if isinstance(e, _MSVSProject): | |
| 1382 config_guids.append(e.get_guid()) | |
| 1383 config_guids.sort() | |
| 1384 | |
| 1385 r.append('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') | |
| 1386 for g in config_guids: | |
| 1387 for v in self.variants: | |
| 1388 r.append('\t\t%s.%s.ActiveCfg = %s\n' % ( | |
| 1389 g, # Project GUID | |
| 1390 v, # Solution build configuration | |
| 1391 v, # Project build config for that solution config | |
| 1392 )) | |
| 1393 | |
| 1394 # Enable project in this solution configuratation | |
| 1395 r.append('\t\t%s.%s.Build.0 = %s\n' % ( | |
| 1396 g, # Project GUID | |
| 1397 v, # Solution build configuration | |
| 1398 v, # Project build config for that solution config | |
| 1399 )) | |
| 1400 r.append('\tEndGlobalSection\n') | |
| 1401 | |
| 1402 # TODO(rspangler): Should be able to configure this stuff too (though I've | |
| 1403 # never seen this be any different) | |
| 1404 r.append('\tGlobalSection(SolutionProperties) = preSolution\n') | |
| 1405 r.append('\t\tHideSolutionNode = FALSE\n') | |
| 1406 r.append('\tEndGlobalSection\n') | |
| 1407 | |
| 1408 # Folder mappings | |
| 1409 # TODO(rspangler): Should omit this section if there are no folders | |
| 1410 folder_mappings = [] | |
| 1411 for e in all_entries: | |
| 1412 if not isinstance(e, _MSVSFolder): | |
| 1413 continue # Does not apply to projects, only folders | |
| 1414 for subentry in e.entries: | |
| 1415 try: | |
| 1416 subentry = lookup_subst(subentry) | |
| 1417 except LookupError, e: | |
| 1418 errors.append("Could not look up subentry `%s'." % subentry) | |
| 1419 else: | |
| 1420 folder_mappings.append((subentry.get_guid(), e.get_guid())) | |
| 1421 folder_mappings.sort() | |
| 1422 r.append('\tGlobalSection(NestedProjects) = preSolution\n') | |
| 1423 for fm in folder_mappings: | |
| 1424 r.append('\t\t%s = %s\n' % fm) | |
| 1425 r.append('\tEndGlobalSection\n') | |
| 1426 | |
| 1427 r.append('EndGlobal\n') | |
| 1428 | |
| 1429 if errors: | |
| 1430 errors = ['Errors while generating solution file:'] + errors | |
| 1431 raise SCons.Errors.UserError, '\n\t'.join(errors) | |
| 1432 | |
| 1433 f = open(self.path, 'wt') | |
| 1434 f.write(''.join(r)) | |
| 1435 f.close() | |
| 1436 | |
| 1437 def MSVSSolution(env, item, *args, **kw): | |
| 1438 if not SCons.Util.is_String(item): | |
| 1439 return item | |
| 1440 item = env.subst(item) | |
| 1441 result = env.fs._lookup(item, None, _MSVSSolution, create=1) | |
| 1442 result.initialize(env, item, *args, **kw) | |
| 1443 LookupAdd(item, result) | |
| 1444 return result | |
| 1445 | |
| 1446 class Derived(SCons.Util.Proxy): | |
| 1447 def srcnode(self, *args, **kw): | |
| 1448 return self | |
| 1449 def __getattr__(self, name): | |
| 1450 if name == 'sources': | |
| 1451 return [] | |
| 1452 return SCons.Util.Proxy.__getattr__(self, name) | |
| 1453 def __hash__(self, *args, **kw): | |
| 1454 return id(self) | |
| 1455 | |
| 1456 import __builtin__ | |
| 1457 | |
| 1458 __builtin__.Derived = Derived | |
| 1459 __builtin__.MSVSConfig = MSVSConfig | |
| 1460 __builtin__.MSVSFilter = MSVSFilter | |
| 1461 __builtin__.MSVSProject = MSVSProject | |
| 1462 __builtin__.MSVSSolution = MSVSSolution | |
| 1463 __builtin__.MSVSTool = MSVSTool | |
| OLD | NEW |