OLD | NEW |
1 #!/usr/bin/python2.4 | 1 # |
2 # Copyright 2008, Google Inc. | 2 # __COPYRIGHT__ |
3 # All rights reserved. | 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__" |
4 | 25 |
5 __doc__ = """SCons.Node.MSVS | 26 __doc__ = """SCons.Node.MSVS |
6 | 27 |
7 Microsoft Visual Studio nodes. | 28 New implementation of Visual Studio project generation for SCons. |
8 """ | 29 """ |
9 | 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 |
10 import SCons.Node.FS | 39 import SCons.Node.FS |
11 import SCons.Script | 40 import SCons.Script |
12 | 41 |
13 | 42 |
14 """New implementation of Visual Studio project generation for SCons.""" | |
15 | |
16 import md5 | |
17 import os | |
18 import random | |
19 | |
20 | |
21 # Initialize random number generator | 43 # Initialize random number generator |
22 random.seed() | 44 random.seed() |
23 | 45 |
24 | 46 |
25 #------------------------------------------------------------------------------ | 47 #------------------------------------------------------------------------------ |
26 # Entry point for supplying a fixed map of GUIDs for testing. | 48 # Entry point for supplying a fixed map of GUIDs for testing. |
27 | 49 |
28 GUIDMap = {} | 50 GUIDMap = {} |
29 | 51 |
30 | 52 |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
115 raise LookupError, "tried to redefine %s as a %s" % (item, klass) | 137 raise LookupError, "tried to redefine %s as a %s" % (item, klass) |
116 return result | 138 return result |
117 result = klass() | 139 result = klass() |
118 result.initialize(item, *args, **kw) | 140 result.initialize(item, *args, **kw) |
119 LookupAdd(item, result) | 141 LookupAdd(item, result) |
120 return result | 142 return result |
121 | 143 |
122 | 144 |
123 #------------------------------------------------------------------------------ | 145 #------------------------------------------------------------------------------ |
124 | 146 |
125 class _MSVSFolder(SCons.Node.Node): | 147 class FileList(object): |
126 """Folder in a Visual Studio project or solution.""" | 148 def __init__(self, entries=None): |
| 149 if isinstance(entries, FileList): |
| 150 entries = entries.entries |
| 151 self.entries = entries or [] |
| 152 def __getitem__(self, i): |
| 153 return self.entries[i] |
| 154 def __setitem__(self, i, item): |
| 155 self.entries[i] = item |
| 156 def __delitem__(self, i): |
| 157 del self.entries[i] |
| 158 def __add__(self, other): |
| 159 if isinstance(other, FileList): |
| 160 return self.__class__(self.entries + other.entries) |
| 161 elif isinstance(other, type(self.entries)): |
| 162 return self.__class__(self.entries + other) |
| 163 else: |
| 164 return self.__class__(self.entries + list(other)) |
| 165 def __radd__(self, other): |
| 166 if isinstance(other, FileList): |
| 167 return self.__class__(other.entries + self.entries) |
| 168 elif isinstance(other, type(self.entries)): |
| 169 return self.__class__(other + self.entries) |
| 170 else: |
| 171 return self.__class__(list(other) + self.entries) |
| 172 def __iadd__(self, other): |
| 173 if isinstance(other, FileList): |
| 174 self.entries += other.entries |
| 175 elif isinstance(other, type(self.entries)): |
| 176 self.entries += other |
| 177 else: |
| 178 self.entries += list(other) |
| 179 return self |
| 180 def append(self, item): |
| 181 return self.entries.append(item) |
| 182 def extend(self, item): |
| 183 return self.entries.extend(item) |
| 184 def index(self, item, *args): |
| 185 return self.entries.index(item, *args) |
| 186 def remove(self, item): |
| 187 return self.entries.remove(item) |
| 188 |
| 189 def FileListWalk(top, topdown=True, onerror=None): |
| 190 """ |
| 191 """ |
| 192 try: |
| 193 entries = top.entries |
| 194 except AttributeError, err: |
| 195 if onerror is not None: |
| 196 onerror(err) |
| 197 return |
| 198 |
| 199 dirs, nondirs = [], [] |
| 200 for entry in entries: |
| 201 if hasattr(entry, 'entries'): |
| 202 dirs.append(entry) |
| 203 else: |
| 204 nondirs.append(entry) |
| 205 |
| 206 if topdown: |
| 207 yield top, dirs, nondirs |
| 208 for entry in dirs: |
| 209 for x in FileListWalk(entry, topdown, onerror): |
| 210 yield x |
| 211 if not topdown: |
| 212 yield top, dirs, nondirs |
| 213 |
| 214 #------------------------------------------------------------------------------ |
| 215 |
| 216 class _MSVSFolder(FileList): |
| 217 """Folder in a Visual Studio solution.""" |
127 | 218 |
128 entry_type_guid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}' | 219 entry_type_guid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}' |
129 | 220 |
130 def initialize(self, path, name = None, entries = None, guid = None, | 221 def initialize(self, path, name = None, entries = None, guid = None, items = N
one): |
131 items = None): | |
132 """Initializes the folder. | 222 """Initializes the folder. |
133 | 223 |
134 Args: | 224 Args: |
135 path: The unique name of the folder, by which other MSVS Nodes can | 225 path: The unique name of the folder, by which other MSVS Nodes can |
136 refer to it. This is not necessarily the name that gets printed | 226 refer to it. This is not necessarily the name that gets printed |
137 in the .sln file. | 227 in the .sln file. |
138 name: The name of this folder as actually written in a generated | 228 name: The name of this folder as actually written in a generated |
139 .sln file. The default is | 229 .sln file. The default is |
140 entries: List of folder entries to nest inside this folder. May contain | 230 entries: List of folder entries to nest inside this folder. May contain |
141 Folder or Project objects. May be None, if the folder is empty. | 231 Folder or Project objects. May be None, if the folder is empty. |
142 guid: GUID to use for folder, if not None. | 232 guid: GUID to use for folder, if not None. |
143 items: List of solution items to include in the folder project. May be | 233 items: List of solution items to include in the folder project. May be |
144 None, if the folder does not directly contain items. | 234 None, if the folder does not directly contain items. |
145 """ | 235 """ |
| 236 super(_MSVSFolder, self).__init__(entries) |
| 237 |
146 # For folder entries, the path is the same as the name | 238 # For folder entries, the path is the same as the name |
147 self.msvs_path = path | 239 self.msvs_path = path |
148 self.msvs_name = name or path | 240 self.msvs_name = name or path |
149 | 241 |
150 self.guid = guid | 242 self.guid = guid |
151 | 243 |
152 # Copy passed lists (or set to empty lists) | 244 # Copy passed lists (or set to empty lists) |
153 self.entries = list(entries or []) | |
154 self.items = list(items or []) | 245 self.items = list(items or []) |
155 | 246 |
156 def get_guid(self): | 247 def get_guid(self): |
157 if self.guid is None: | 248 if self.guid is None: |
158 guid = GUIDMap.get(self.msvs_path) | 249 guid = GUIDMap.get(self.msvs_path) |
159 if not guid: | 250 if not guid: |
160 # The GUID for the folder can be random, since it's used only inside | 251 # The GUID for the folder can be random, since it's used only inside |
161 # solution files and doesn't need to be consistent across runs. | 252 # solution files and doesn't need to be consistent across runs. |
162 guid = MakeGuid(random.random()) | 253 guid = MakeGuid(random.random()) |
163 self.guid = guid | 254 self.guid = guid |
164 return self.guid | 255 return self.guid |
165 | 256 |
166 def get_msvs_path(self, sln): | 257 def get_msvs_path(self, sln): |
167 return self.msvs_name | 258 return self.msvs_name |
168 | 259 |
169 def MSVSFolder(env, item, *args, **kw): | 260 def MSVSFolder(env, item, *args, **kw): |
170 return LookupCreate(_MSVSFolder, item, *args, **kw) | 261 return LookupCreate(_MSVSFolder, item, *args, **kw) |
171 | 262 |
172 #------------------------------------------------------------------------------ | 263 #------------------------------------------------------------------------------ |
173 | 264 |
| 265 class MSVSConfig(object): |
| 266 """Visual Studio configuration.""" |
| 267 def __init__(self, Name, config_type, tools=[], **attrs): |
| 268 """Initializes the configuration. |
| 269 |
| 270 Args: |
| 271 **attrs: Configuration attributes. |
| 272 """ |
| 273 # Special handling for attributes that we want to make more |
| 274 # convenient for the user. |
| 275 ips = attrs.get('InheritedPropertySheets') |
| 276 if ips: |
| 277 if isinstance(ips, list): |
| 278 ips = ';'.join(ips) |
| 279 attrs['InheritedPropertySheets'] = ips.replace('/', '\\') |
| 280 |
| 281 tools = tools or [] |
| 282 if not SCons.Util.is_List(tools): |
| 283 tools = [tools] |
| 284 tool_objects = [] |
| 285 for t in tools: |
| 286 if not isinstance(t, MSVSTool): |
| 287 t = MSVSTool(t) |
| 288 tool_objects.append(t) |
| 289 |
| 290 self.Name = Name |
| 291 self.config_type = config_type |
| 292 self.tools = tool_objects |
| 293 self.attrs = attrs |
| 294 |
| 295 def CreateElement(self, doc): |
| 296 """Creates an element for the configuration. |
| 297 |
| 298 Args: |
| 299 doc: xml.dom.Document object to use for node creation. |
| 300 |
| 301 Returns: |
| 302 A new xml.dom.Element for the configuration. |
| 303 """ |
| 304 node = doc.createElement(self.config_type) |
| 305 node.setAttribute('Name', self.Name) |
| 306 for k, v in self.attrs.items(): |
| 307 node.setAttribute(k, v) |
| 308 for t in self.tools: |
| 309 node.appendChild(t.CreateElement(doc)) |
| 310 return node |
| 311 |
| 312 |
| 313 class MSVSFileListBase(FileList): |
| 314 """Base class for a file list in a Visual Studio project file.""" |
| 315 |
| 316 def CreateElement(self, doc, node_func=lambda x: x): |
| 317 """Creates an element for an MSVSFileListBase subclass. |
| 318 |
| 319 Args: |
| 320 doc: xml.dom.Document object to use for node creation. |
| 321 node_func: Function to use to return Nodes for objects that |
| 322 don't have a CreateElement() method of their own. |
| 323 |
| 324 Returns: |
| 325 A new xml.dom.Element for the MSVSFileListBase object. |
| 326 """ |
| 327 node = doc.createElement(self.element_name) |
| 328 for entry in self.entries: |
| 329 if hasattr(entry, 'CreateElement'): |
| 330 n = entry.CreateElement(doc, node_func) |
| 331 else: |
| 332 n = node_func(entry) |
| 333 node.appendChild(n) |
| 334 return node |
| 335 |
| 336 |
| 337 class MSVSFiles(MSVSFileListBase): |
| 338 """Files list in a Visual Studio project file.""" |
| 339 element_name = 'Files' |
| 340 |
| 341 |
| 342 class MSVSFilter(MSVSFileListBase): |
| 343 """Filter (that is, a virtual folder) in a Visual Studio project file.""" |
| 344 |
| 345 element_name = 'Filter' |
| 346 |
| 347 def __init__(self, Name, entries=None): |
| 348 """Initializes the folder. |
| 349 |
| 350 Args: |
| 351 Name: Filter (folder) name. |
| 352 entries: List of filenames and/or Filter objects contained. |
| 353 """ |
| 354 super(MSVSFilter, self).__init__(entries) |
| 355 self.Name = Name |
| 356 |
| 357 def CreateElement(self, doc, node_func=lambda x: x): |
| 358 """Creates an element for the Filter. |
| 359 |
| 360 Args: |
| 361 doc: xml.dom.Document object to use for node creation. |
| 362 node_func: Function to use to return Nodes for objects that |
| 363 don't have a CreateElement() method of their own. |
| 364 |
| 365 Returns: |
| 366 A new xml.dom.Element for the filter. |
| 367 """ |
| 368 node = super(MSVSFilter, self).CreateElement(doc, node_func) |
| 369 node.setAttribute('Name', self.Name) |
| 370 return node |
| 371 |
| 372 |
| 373 class MSVSTool(object): |
| 374 """Visual Studio tool.""" |
| 375 |
| 376 def __init__(self, Name, **attrs): |
| 377 """Initializes the tool. |
| 378 |
| 379 Args: |
| 380 Name: Tool name. |
| 381 **attrs: Tool attributes. |
| 382 """ |
| 383 self.Name = Name |
| 384 self.attrs = attrs |
| 385 |
| 386 def CreateElement(self, doc): |
| 387 """Creates an element for the tool. |
| 388 |
| 389 Args: |
| 390 doc: xml.dom.Document object to use for node creation. |
| 391 |
| 392 Returns: |
| 393 A new xml.dom.Element for the tool. |
| 394 """ |
| 395 node = doc.createElement('Tool') |
| 396 node.setAttribute('Name', self.Name) |
| 397 for k, v in self.attrs.items(): |
| 398 node.setAttribute(k, v) |
| 399 return node |
| 400 |
| 401 |
| 402 class MSVSToolFile(object): |
| 403 """Visual Studio tool file specification.""" |
| 404 |
| 405 def __init__(self, node, **attrs): |
| 406 """Initializes the tool. |
| 407 |
| 408 Args: |
| 409 node: Node for the Tool File |
| 410 **attrs: Tool File attributes. |
| 411 """ |
| 412 self.node = node |
| 413 |
| 414 def CreateElement(self, doc, project): |
| 415 result = doc.createElement('ToolFile') |
| 416 result.setAttribute('RelativePath', project.get_rel_path(self.node)) |
| 417 return result |
| 418 |
| 419 |
| 420 #------------------------------------------------------------------------------ |
| 421 |
| 422 def MSVSAction(target, source, env): |
| 423 target[0].Write(env) |
| 424 |
| 425 MSVSProjectAction = SCons.Script.Action(MSVSAction, |
| 426 "Generating Visual Studio project `$TARG
ET' ...") |
174 | 427 |
175 class _MSVSProject(SCons.Node.FS.File): | 428 class _MSVSProject(SCons.Node.FS.File): |
176 """Visual Studio project.""" | 429 """Visual Studio project.""" |
177 | 430 |
178 entry_type_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' | 431 entry_type_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' |
179 | 432 |
180 def initialize(self, path, name = None, dependencies = None, guid = None): | 433 def initialize(self, env, path, name = None, |
| 434 dependencies = None, |
| 435 guid = None, |
| 436 buildtargets = [], |
| 437 files = [], |
| 438 root_namespace = None, |
| 439 relative_path_prefix = '', |
| 440 tools = None, |
| 441 configurations = None): |
181 """Initializes the project. | 442 """Initializes the project. |
182 | 443 |
183 Args: | 444 Args: |
184 path: Relative path to project file. | 445 path: Relative path to project file. |
185 name: Name of project. If None, the name will be the same as the base | 446 name: Name of project. If None, the name will be the same as the base |
186 name of the project file. | 447 name of the project file. |
187 dependencies: List of other Project objects this project is dependent | 448 dependencies: List of other Project objects this project is dependent |
188 upon, if not None. | 449 upon, if not None. |
189 guid: GUID to use for project, if not None. | 450 guid: GUID to use for project, if not None. |
| 451 buildtargets: List of target(s) being built by this project. |
| 452 files: List of source files for the project. This will be |
| 453 supplemented by any source files of buildtargets. |
| 454 root_namespace: The value of the RootNamespace attribute of the |
| 455 project, if not None. The default is to use the same |
| 456 string as the name. |
| 457 relative_path_prefix: A prefix to be appended to the beginning of |
| 458 every file name in the list. The canonical use is to specify |
| 459 './' to make files explicitly relative to the local directory. |
| 460 tools: A list of MSVSTool objects or strings representing |
| 461 tools to be used to build this project. This will be used |
| 462 for any configurations that don't provide their own |
| 463 per-configuration tool list. |
| 464 configurations: A list of MSVSConfig objects representing |
| 465 configurations built by this project. |
190 """ | 466 """ |
191 self.msvs_path = path | 467 self.msvs_path = path |
192 self.msvs_name = name or os.path.splitext(os.path.basename(self.name))[0] | 468 self.msvs_node = env.File(path) |
| 469 if name is None: |
| 470 if buildtargets: |
| 471 name = os.path.splitext(buildtargets[0].name)[0] |
| 472 else: |
| 473 name = os.path.splitext(os.path.basename(path))[0] |
| 474 self.msvs_name = name |
| 475 self.root_namespace = root_namespace or self.msvs_name |
| 476 self.buildtargets = buildtargets |
| 477 self.relative_path_prefix = relative_path_prefix |
| 478 self.tools = tools |
193 | 479 |
| 480 self.env = env |
194 self.guid = guid | 481 self.guid = guid |
195 | 482 |
196 # Copy passed lists (or set to empty lists) | |
197 self.dependencies = list(dependencies or []) | 483 self.dependencies = list(dependencies or []) |
| 484 self.configurations = list(configurations or []) |
| 485 self.file_configurations = {} |
| 486 self.tool_files = [] |
| 487 |
| 488 if not isinstance(files, MSVSFiles): |
| 489 files = MSVSFiles(self.args2nodes(files)) |
| 490 self.files = files |
| 491 |
| 492 env.Command(self, [], MSVSSolutionAction) |
| 493 |
| 494 def args2nodes(self, entries): |
| 495 result = [] |
| 496 for entry in entries: |
| 497 if SCons.Util.is_String(entry): |
| 498 entry = self.env.File(entry) |
| 499 result.append(entry) |
| 500 elif hasattr(entry, 'entries'): |
| 501 entry.entries = self.args2nodes(entry.entries) |
| 502 result.append(entry) |
| 503 elif isinstance(entry, (list, UserList.UserList)): |
| 504 result.extend(self.args2nodes(entry)) |
| 505 elif hasattr(entry, 'sources') and entry.sources: |
| 506 result.extend(entry.sources) |
| 507 else: |
| 508 result.append(entry) |
| 509 return result |
| 510 |
| 511 def FindFile(self, node): |
| 512 try: |
| 513 flat_file_dict = self.flat_file_dict |
| 514 except AttributeError: |
| 515 flat_file_dict = {} |
| 516 file_list = self.files[:] |
| 517 while file_list: |
| 518 entry = file_list.pop(0) |
| 519 if not isinstance(entry, (list, UserList.UserList)): |
| 520 entry = [entry] |
| 521 for f in entry: |
| 522 if hasattr(f, 'entries'): |
| 523 file_list.extend(f.entries) |
| 524 else: |
| 525 flat_file_dict[f] = True |
| 526 if hasattr(f, 'sources'): |
| 527 for s in f.sources: |
| 528 flat_file_dict[s] = True |
| 529 self.flat_file_dict = flat_file_dict |
| 530 |
| 531 return flat_file_dict.get(node) |
198 | 532 |
199 def get_guid(self): | 533 def get_guid(self): |
200 if self.guid is None: | 534 if self.guid is None: |
201 guid = GUIDMap.get(self.msvs_path) | 535 guid = GUIDMap.get(self.msvs_path) |
202 if not guid: | 536 if not guid: |
203 # Set GUID from path | 537 # Set GUID from path |
204 # TODO(rspangler): This is fragile. | 538 # TODO(rspangler): This is fragile. |
205 # 1. We can't just use the project filename sans path, since there | 539 # 1. We can't just use the project filename sans path, since there |
206 # could be multiple projects with the same base name (for example, | 540 # could be multiple projects with the same base name (for example, |
207 # foo/unittest.vcproj and bar/unittest.vcproj). | 541 # foo/unittest.vcproj and bar/unittest.vcproj). |
208 # 2. The path needs to be relative to $SOURCE_ROOT, so that the project | 542 # 2. The path needs to be relative to $SOURCE_ROOT, so that the project |
209 # GUID is the same whether it's included from base/base.sln or | 543 # GUID is the same whether it's included from base/base.sln or |
210 # foo/bar/baz/baz.sln. | 544 # foo/bar/baz/baz.sln. |
211 # 3. The GUID needs to be the same each time this builder is invoked, | 545 # 3. The GUID needs to be the same each time this builder is invoked, |
212 # so that we don't need to rebuild the solution when the | 546 # so that we don't need to rebuild the solution when the |
213 # project changes. | 547 # project changes. |
214 # 4. We should be able to handle pre-built project files by reading the | 548 # 4. We should be able to handle pre-built project files by reading the |
215 # GUID from the files. | 549 # GUID from the files. |
216 guid = MakeGuid(self.msvs_path) | 550 guid = MakeGuid(self.msvs_path) |
217 self.guid = guid | 551 self.guid = guid |
218 return self.guid | 552 return self.guid |
219 | 553 |
220 def get_msvs_path(self, sln): | 554 def get_msvs_path(self, sln): |
221 return sln.rel_path(self).replace('/', '\\') | 555 return sln.rel_path(self).replace('/', '\\') |
222 | 556 |
| 557 def get_rel_path(self, node): |
| 558 result = self.relative_path_prefix + self.msvs_node.rel_path(node) |
| 559 return result.replace('/', '\\') |
| 560 |
| 561 def AddConfig(self, Name, tools=None, **attrs): |
| 562 """Adds a configuration to the parent node. |
| 563 |
| 564 Args: |
| 565 Name: The name of the configuration. |
| 566 tools: List of tools (strings or Tool objects); may be None. |
| 567 **attrs: Configuration attributes. |
| 568 """ |
| 569 if tools is None: |
| 570 # No tool list specifically for this configuration, |
| 571 # use the Project's as a default. |
| 572 tools = self.tools |
| 573 c = MSVSConfig(Name, 'Configuration', tools=tools, **attrs) |
| 574 self.configurations.append(c) |
| 575 |
| 576 def AddFiles(self, files): |
| 577 """Adds files to the project. |
| 578 |
| 579 Args: |
| 580 files: A list of Filter objects and/or relative paths to files. |
| 581 |
| 582 This makes a copy of the file/filter tree at the time of this call. If you |
| 583 later add files to a Filter object which was passed into a previous call |
| 584 to AddFiles(), it will not be reflected in this project. |
| 585 """ |
| 586 # TODO(rspangler) This also doesn't handle adding files to an existing |
| 587 # filter. That is, it doesn't merge the trees. |
| 588 self.files.extend(self.args2nodes(files)) |
| 589 |
| 590 def AddFileConfig(self, path, Name, tools=None, **attrs): |
| 591 """Adds a configuration to a file. |
| 592 |
| 593 Args: |
| 594 path: Relative path to the file. |
| 595 Name: Name of configuration to add. |
| 596 tools: List of tools (strings or MSVSTool objects); may be None. |
| 597 **attrs: Configuration attributes. |
| 598 |
| 599 Raises: |
| 600 ValueError: Relative path does not match any file added via AddFiles(). |
| 601 """ |
| 602 node = self.env.File(path) |
| 603 if not self.FindFile(node): |
| 604 raise ValueError('AddFileConfig: file "%s" not in project' % path) |
| 605 c = MSVSConfig(Name, 'FileConfiguration', tools=tools, **attrs) |
| 606 config_list = self.file_configurations.get(node) |
| 607 if config_list is None: |
| 608 config_list = [] |
| 609 self.file_configurations[node] = config_list |
| 610 config_list.append(c) |
| 611 |
| 612 def AddToolFile(self, path): |
| 613 """Adds a tool file to the project. |
| 614 |
| 615 Args: |
| 616 path: Relative path from project to tool file. |
| 617 """ |
| 618 tf = MSVSToolFile(self.env.File(path)) |
| 619 self.tool_files.append(tf) |
| 620 |
| 621 def Create(self): |
| 622 """Creates the project document. |
| 623 |
| 624 Args: |
| 625 name: Name of the project. |
| 626 guid: GUID to use for project, if not None. |
| 627 """ |
| 628 # Create XML doc |
| 629 xml_impl = xml.dom.getDOMImplementation() |
| 630 self.doc = xml_impl.createDocument(None, 'VisualStudioProject', None) |
| 631 |
| 632 # Add attributes to root element |
| 633 root = self.doc.documentElement |
| 634 root.setAttribute('ProjectType', 'Visual C++') |
| 635 root.setAttribute('Version', '8.00') |
| 636 root.setAttribute('Name', self.msvs_name) |
| 637 root.setAttribute('ProjectGUID', self.get_guid()) |
| 638 root.setAttribute('RootNamespace', self.root_namespace) |
| 639 root.setAttribute('Keyword', 'Win32Proj') |
| 640 |
| 641 # Add platform list |
| 642 platforms = self.doc.createElement('Platforms') |
| 643 root.appendChild(platforms) |
| 644 n = self.doc.createElement('Platform') |
| 645 n.setAttribute('Name', 'Win32') |
| 646 platforms.appendChild(n) |
| 647 |
| 648 # Add tool files section |
| 649 tool_files = self.doc.createElement('ToolFiles') |
| 650 root.appendChild(tool_files) |
| 651 for tf in self.tool_files: |
| 652 tool_files.appendChild(tf.CreateElement(self.doc, self)) |
| 653 |
| 654 # Add configurations section |
| 655 configs = self.doc.createElement('Configurations') |
| 656 root.appendChild(configs) |
| 657 for c in self.configurations: |
| 658 configs.appendChild(c.CreateElement(self.doc)) |
| 659 |
| 660 # Add empty References section |
| 661 root.appendChild(self.doc.createElement('References')) |
| 662 |
| 663 # Add files section |
| 664 root.appendChild(self.files.CreateElement(self.doc, self.CreateFileElement)) |
| 665 |
| 666 # Add empty Globals section |
| 667 root.appendChild(self.doc.createElement('Globals')) |
| 668 |
| 669 def CreateFileElement(self, file): |
| 670 """Create a DOM node for the specified file. |
| 671 |
| 672 Args: |
| 673 file: The file Node being considered. |
| 674 |
| 675 Returns: |
| 676 A DOM Node for the File, with a relative path to the current |
| 677 project object, and any file configurations attached to the |
| 678 project. |
| 679 """ |
| 680 |
| 681 node = self.doc.createElement('File') |
| 682 node.setAttribute('RelativePath', self.get_rel_path(file)) |
| 683 for c in self.file_configurations.get(file, []): |
| 684 node.appendChild(c.CreateElement(self.doc)) |
| 685 return node |
| 686 |
| 687 def _AddFileConfigurationDifferences(self, target, source, base_env, file_env)
: |
| 688 """Adds a per-file configuration. |
| 689 |
| 690 Args: |
| 691 target: The target being built from the source. |
| 692 source: The source to which the file configuration is being added. |
| 693 base_env: The base construction environment for the project. |
| 694 Differences from this will go into the FileConfiguration |
| 695 in the project file. |
| 696 file_env: The construction environment for the target, containing |
| 697 the per-target settings. |
| 698 """ |
| 699 pass |
| 700 |
| 701 def _AddFileConfigurations(self, env): |
| 702 """Adds per-file configurations for the buildtarget's sources. |
| 703 |
| 704 Args: |
| 705 env: The base construction environment for the project. |
| 706 """ |
| 707 if not self.buildtargets: |
| 708 return |
| 709 |
| 710 bt = self.buildtargets[0] |
| 711 additional_files = [] |
| 712 for t in bt.sources: |
| 713 e = t.get_build_env() |
| 714 for s in t.sources: |
| 715 s = env.arg2nodes([s])[0] |
| 716 if not self.FindFile(s): |
| 717 additional_files.append(s) |
| 718 if not env is e: |
| 719 self._AddFileConfigurationDifferences(t, s, env, e) |
| 720 self.AddFiles(additional_files) |
| 721 |
| 722 def Write(self, env): |
| 723 """Writes the project file.""" |
| 724 self._AddFileConfigurations(env) |
| 725 |
| 726 self.Create() |
| 727 |
| 728 f = open(str(self.msvs_node), 'wt') |
| 729 f.write(self.formatMSVSProjectXML(self.doc)) |
| 730 f.close() |
| 731 |
| 732 # Methods for formatting XML as nearly identically to Microsoft's |
| 733 # .vcproj output as we can practically make it. |
| 734 # |
| 735 # The general design here is copied from: |
| 736 # |
| 737 # Bruce Eckels' MindView, Inc: 12-09-04 XML Oddyssey |
| 738 # http://www.mindview.net/WebLog/log-0068 |
| 739 # |
| 740 # Eckels' implementation broke up long tag definitions for readability, |
| 741 # in much the same way that .vcproj does, but we've modified things |
| 742 # for .vcproj quirks (like some tags *always* terminating with </Tag>, |
| 743 # even when empty). |
| 744 |
| 745 encoding = 'Windows-1252' |
| 746 |
| 747 def formatMSVSProjectXML(self, xmldoc): |
| 748 xmldoc = xmldoc.toprettyxml("", "\n", encoding=self.encoding) |
| 749 # Remove trailing whitespace from each line: |
| 750 xmldoc = "\n".join( |
| 751 [line.rstrip() for line in xmldoc.split("\n")]) |
| 752 # Remove all empty lines before opening '<': |
| 753 while xmldoc.find("\n\n<") != -1: |
| 754 xmldoc = xmldoc.replace("\n\n<", "\n<") |
| 755 dom = xml.dom.minidom.parseString(xmldoc) |
| 756 xmldoc = dom.toprettyxml("\t", "", encoding=self.encoding) |
| 757 xmldoc = xmldoc.replace('?><', '?>\n<') |
| 758 xmldoc = self.reformatLines(xmldoc) |
| 759 return xmldoc |
| 760 |
| 761 def reformatLines(self, xmldoc): |
| 762 result = [] |
| 763 for line in [line.rstrip() for line in xmldoc.split("\n")]: |
| 764 if line.lstrip().startswith("<"): |
| 765 result.append(self.reformatLine(line) + "\n") |
| 766 else: |
| 767 result.append(line + "\n") |
| 768 return ''.join(result) |
| 769 |
| 770 # Keyword order for specific tags. |
| 771 # |
| 772 # Listed keywords will come first and in the specified order. |
| 773 # Any unlisted keywords come after, in whatever order they appear |
| 774 # in the input config. In theory this means we would only *have* to |
| 775 # list the keywords that we care about, but in practice we'll probably |
| 776 # want to nail down Visual Studio's order to make sure we match them |
| 777 # as nearly as possible. |
| 778 |
| 779 order = { |
| 780 'Configuration' : [ |
| 781 'Name', |
| 782 'ConfigurationType', |
| 783 'InheritedPropertySheets', |
| 784 ], |
| 785 'FileConfiguration' : [ |
| 786 'Name', |
| 787 'ExcludedFromBuild', |
| 788 ], |
| 789 'Tool' : [ |
| 790 'Name', |
| 791 'DisableSpecificWarnings', |
| 792 ], |
| 793 'VisualStudioProject' : [ |
| 794 'ProjectType', |
| 795 'Version', |
| 796 'Name', |
| 797 'ProjectGUID', |
| 798 'RootNamespace', |
| 799 'Keyword', |
| 800 ], |
| 801 } |
| 802 |
| 803 force_closing_tag = [ |
| 804 'File', |
| 805 'Globals', |
| 806 'References', |
| 807 'ToolFiles' |
| 808 ] |
| 809 |
| 810 oneLiner = re.compile("(\s*)<(\w+)(.*)>") |
| 811 keyValuePair = re.compile('\w+="[^"]*?"') |
| 812 def reformatLine(self, line): |
| 813 """Reformat an xml tag to put each key-value |
| 814 element on a single indented line, for readability""" |
| 815 matchobj = self.oneLiner.match(line.rstrip()) |
| 816 if not matchobj: |
| 817 return line |
| 818 baseIndent, tag, rest = matchobj.groups() |
| 819 slash = '' |
| 820 if rest[-1:] == '/': |
| 821 slash = '/' |
| 822 rest = rest[:-1] |
| 823 result = [baseIndent + '<' + tag] |
| 824 indent = baseIndent + "\t" |
| 825 pairs = self.keyValuePair.findall(rest) |
| 826 for key in self.order.get(tag, []): |
| 827 for p in [ p for p in pairs if p.startswith(key+'=') ]: |
| 828 result.append("\n" + indent + p) |
| 829 pairs.remove(p) |
| 830 for pair in pairs: |
| 831 result.append("\n" + indent + pair) |
| 832 result = [''.join(result).rstrip()] |
| 833 |
| 834 if tag in self.force_closing_tag: |
| 835 # These force termination with </Tag>, so translate slash. |
| 836 if rest: |
| 837 result.append("\n") |
| 838 result.append(indent) |
| 839 result.append(">") |
| 840 if slash: |
| 841 result.append("\n") |
| 842 result.append(baseIndent + "</" + tag + ">") |
| 843 else: |
| 844 if rest: |
| 845 result.append("\n") |
| 846 if slash: |
| 847 result.append(baseIndent) |
| 848 else: |
| 849 result.append(indent) |
| 850 result.append(slash + ">") |
| 851 |
| 852 return ''.join(result) |
| 853 |
223 def MSVSProject(env, item, *args, **kw): | 854 def MSVSProject(env, item, *args, **kw): |
224 if not SCons.Util.is_String(item): | 855 if not SCons.Util.is_String(item): |
225 return item | 856 return item |
226 item = env.subst(item) | 857 item = env.subst(item) |
227 result = env.fs._lookup(item, None, _MSVSProject, create=1) | 858 result = env.fs._lookup(item, None, _MSVSProject, create=1) |
228 result.initialize(item, *args, **kw) | 859 result.initialize(env, item, *args, **kw) |
229 LookupAdd(item, result) | 860 LookupAdd(item, result) |
230 return result | 861 return result |
231 | 862 |
232 #------------------------------------------------------------------------------ | 863 #------------------------------------------------------------------------------ |
233 | 864 |
234 def MSVSAction(target, source, env): | |
235 target[0].Write(env) | |
236 | |
237 MSVSSolutionAction = SCons.Script.Action(MSVSAction, | 865 MSVSSolutionAction = SCons.Script.Action(MSVSAction, |
238 "Generating Visual Studio solution `$TA
RGET' ...") | 866 "Generating Visual Studio solution `$TA
RGET' ...") |
239 | 867 |
240 class _MSVSSolution(SCons.Node.FS.File): | 868 class _MSVSSolution(SCons.Node.FS.File): |
241 """Visual Studio solution.""" | 869 """Visual Studio solution.""" |
242 | 870 |
243 def initialize(self, env, path, entries = None, variants = None, | 871 def initialize(self, env, path, entries = None, variants = None, websiteProper
ties = True): |
244 websiteProperties = True): | |
245 """Initializes the solution. | 872 """Initializes the solution. |
246 | 873 |
247 Args: | 874 Args: |
248 path: Path to solution file. | 875 path: Path to solution file. |
249 entries: List of entries in solution. May contain Folder or Project | 876 entries: List of entries in solution. May contain Folder or Project |
250 objects. May be None, if the folder is empty. | 877 objects. May be None, if the folder is empty. |
251 variants: List of build variant strings. If none, a default list will | 878 variants: List of build variant strings. If none, a default list will |
252 be used. | 879 be used. |
253 """ | 880 """ |
254 self.msvs_path = path | 881 self.msvs_path = path |
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
425 f.close() | 1052 f.close() |
426 | 1053 |
427 def MSVSSolution(env, item, *args, **kw): | 1054 def MSVSSolution(env, item, *args, **kw): |
428 if not SCons.Util.is_String(item): | 1055 if not SCons.Util.is_String(item): |
429 return item | 1056 return item |
430 item = env.subst(item) | 1057 item = env.subst(item) |
431 result = env.fs._lookup(item, None, _MSVSSolution, create=1) | 1058 result = env.fs._lookup(item, None, _MSVSSolution, create=1) |
432 result.initialize(env, item, *args, **kw) | 1059 result.initialize(env, item, *args, **kw) |
433 LookupAdd(item, result) | 1060 LookupAdd(item, result) |
434 return result | 1061 return result |
| 1062 |
| 1063 import __builtin__ |
| 1064 |
| 1065 __builtin__.MSVSFilter = MSVSFilter |
| 1066 __builtin__.MSVSProject = MSVSProject |
| 1067 __builtin__.MSVSSolution = MSVSSolution |
| 1068 __builtin__.MSVSTool = MSVSTool |
OLD | NEW |