Debugging: Add code to print backtrace for guest on SIGSEGV
[nativeclient.git] / site_scons / site_tools / component_targets_msvs.py
blob78d5b85c73f8e2d5538db1a673f622cddec15a92
1 #!/usr/bin/python2.4
2 # Copyright 2009, Google Inc.
3 # All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
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.
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.
31 """Visual Studio solution output of component targets for SCons."""
33 import copy
34 import md5
35 import os
36 import sys
37 import xml.dom
38 import xml.dom.minidom
39 import SCons
42 #------------------------------------------------------------------------------
45 def MakeGuid(name, seed='component_targets_msvs'):
46 """Returns a GUID for the specified target name.
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.
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
67 #------------------------------------------------------------------------------
70 def GetGuidAndNameFromVSProject(project_path):
71 """Reads the GUID from a Visual Studio project file.
73 Args:
74 project_path: Path to the Visual Studio project file.
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),
90 finally:
91 # Clean up doc
92 doc.unlink()
94 #------------------------------------------------------------------------------
97 class VSProjectWriter(object):
98 """Visual Studio XML project writer."""
100 def __init__(self, project_path):
101 """Initializes the project.
103 Args:
104 project_path: Path to the project file.
106 self.project_path = project_path
107 self.doc = None
109 def Create(self, name):
110 """Creates the project document.
112 Args:
113 name: Name of the project.
115 self.name = name
116 self.guid = MakeGuid(name)
118 # Create XML doc
119 xml_impl = xml.dom.getDOMImplementation()
120 self.doc = xml_impl.createDocument(None, 'VisualStudioProject', None)
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')
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)
138 # Add empty ToolFiles section
139 self.n_root.appendChild(self.doc.createElement('ToolFiles'))
141 # Add configurations section
142 self.n_configs = self.doc.createElement('Configurations')
143 self.n_root.appendChild(self.n_configs)
145 # Add files section
146 self.n_files = self.doc.createElement('Files')
147 self.n_root.appendChild(self.n_files)
149 # Add empty Globals section
150 self.n_root.appendChild(self.doc.createElement('Globals'))
152 def AddConfig(self, name, attrs, tool_attrs):
153 """Adds a configuration to the project.
155 Args:
156 name: Configuration name.
157 attrs: Dict of configuration attributes.
158 tool_attrs: Dict of tool attributes.
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)
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)
181 def _WalkFolders(self, folder_dict, parent):
182 """Recursively walks the folder tree.
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)
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)
204 def AddFiles(self, name, files_dict):
205 """Adds files to the project.
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.
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.
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
225 # Recursively add files to the folder
226 self._WalkFolders(files_dict, n_folder)
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()
234 #------------------------------------------------------------------------------
237 def ComponentVSProjectBuilder(target, source, env):
238 """Visual Studio project builder.
240 Args:
241 target: Destination file.
242 source: List of sources to be added to the target.
243 env: Environment context.
245 Returns:
246 Zero if successful.
248 source = source # Silence gpylint
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='/')
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)
258 vsp = VSProjectWriter(project_file)
259 vsp.Create(target_name)
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))
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
279 vsp.AddConfig(mode, attrs, tool_attrs)
281 # TODO: Fill in files - at least, the .scons file invoking the target.
283 # Write project
284 vsp.Write()
285 return 0
288 def ComponentVSProject(self, target_name, **kwargs):
289 """Visual Studio project pseudo-builder for the specified target.
291 Args:
292 self: Environment context.
293 target_name: Name of the target.
294 kwargs: Optional keyword arguments override environment variables in the
295 derived environment used to create the project.
297 Returns:
298 A list of output nodes.
300 # Builder only works on Windows
301 if sys.platform not in ('win32', 'cygwin'):
302 return []
304 # Clone environment and add keyword args
305 env = self.Clone()
306 for k, v in kwargs.items():
307 env[k] = v
309 # Save the target name
310 env['TARGET_NAME'] = target_name
312 # Extract the target properties and save in the environment for use by the
313 # real builder.
314 t = GetTargets().get(target_name)
315 env['TARGET_PATH'] = {}
316 if t:
317 for mode, mode_properties in t.mode_properties.items():
318 # Since the target path is what Visual Studio will run, use the EXE
319 # property in preference to TARGET_PATH.
320 target_path = mode_properties.get('EXE',
321 mode_properties.get('TARGET_PATH'))
322 env.Append(TARGET_PATH={mode: target_path})
323 else:
324 # No modes declared for this target. Could be a custom alias created by
325 # a SConscript, rather than a component builder. Assume it can be built in
326 # all modes, but produces no output.
327 for mode in GetTargetModes():
328 env.Append(TARGET_PATH={mode: None})
330 # Call the real builder
331 return env.ComponentVSProjectBuilder(
332 '$COMPONENT_VS_PROJECT_DIR/${TARGET_NAME}', [])
334 #------------------------------------------------------------------------------
337 class SourceWalker(object):
338 """Iterator for walking a node tree and collecting sources.
340 This is depth-first, children are visited before the parent. The object
341 can be initialized with any node, and returns the next node on the descent
342 with each Next() call.
344 This class does not get caught in node cycles caused, for example, by C
345 header file include loops.
347 Based on SCons.Node.Walker.
350 def __init__(self, node, seen, print_interval = 1000):
351 """Initializes the source walker.
353 Args:
354 node: Node to start walk from.
355 seen: Set to add seen nodes to, if not None.
356 print_interval: Interval in nodes examined at which to print a progress
357 indicator.
359 self.interval = print_interval
360 # Put node on stack
361 self.stack = [node]
362 # Scan for node's children, then copy them to node.wkids
363 node.wkids = copy.copy(node.children(scan=1))
364 # Keep track of nodes we've seen (returned)
365 if seen is None:
366 seen = set()
367 self.seen = seen
368 # Add node to history for cycle detection
369 self.history = set()
370 self.history.add(node)
371 # We've seen one node now
372 self.nodes_examined = 1
373 self.unique_nodes = 1
376 def Next(self):
377 """Get the next node for this walk of the tree.
379 Returns:
380 The next node, or None if no more nodes.
382 This function is intentionally iterative, not recursive, to sidestep any
383 issues of stack size limitations.
385 while self.stack:
386 if self.stack[-1].wkids:
387 # Node has children we haven't examined, so iterate into the first
388 # child
389 node = self.stack[-1].wkids.pop(0)
390 if not self.stack[-1].wkids:
391 # No more children of this node
392 self.stack[-1].wkids = None
393 self.nodes_examined += 1
394 if self.interval and not self.nodes_examined % self.interval:
395 self.PrintProgress()
396 if (node not in self.history) and (node not in self.seen):
397 # Haven't hit a cycle or a node we've already seen
398 node.wkids = copy.copy(node.children(scan=1))
399 self.stack.append(node)
400 self.history.add(node)
401 else:
402 # Coming back from iterating, so return the next node on the stack.
403 node = self.stack.pop()
404 self.history.remove(node)
405 self.seen.add(node)
406 self.unique_nodes += 1
407 return node
408 return None
410 def PrintProgress(self):
411 """Prints a progress indicator."""
412 print ' Examined %d nodes, found %d unique...' % (
413 self.nodes_examined, self.unique_nodes)
415 def WalkAll(self):
416 """Walks all nodes in the the tree."""
417 while self.Next():
418 pass
419 if self.interval:
420 self.PrintProgress()
423 def ComponentVSSourceProjectBuilder(target, source, env):
424 """Visual Studio source project builder.
426 Args:
427 target: Destination file.
428 source: List of sources to be added to the target.
429 env: Environment context.
431 Returns:
432 Zero if successful.
434 source = source # Silence gpylint
436 target_name = env['PROJECT_NAME']
437 project_file = target[0].path
438 project_dir = target[0].dir
440 # Get list of suffixes to include
441 suffixes = env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES')
443 # Convert source folders to absolute paths
444 folders = []
445 for f in env['COMPONENT_VS_SOURCE_FOLDERS']:
446 # (folder name, folder abspath, dict of contents)
447 folders.append((f[0], env.Dir(f[1]).abspath, {}))
449 # TODO: Additional enhancements:
450 # * Should be able to specify paths in folder name (i.e., foo/bar) and
451 # create the nested folder nodes ('foo' and 'bar')
452 # * Should be tolerant of a folder being specified more than once with
453 # the same name (probably necessary to support nested folder nodes anyway)
454 # Can probably accomplish both of those by creating a parent fodler dict and
455 # calling WalkFolders() only once.
456 # Create a temporary solution alias to point to all the targets, so we can
457 # make a single call to SourceWalker()
458 tmp_alias = env.Alias('vs_source_project_' + target_name,
459 map(env.Alias, env['COMPONENT_VS_SOURCE_TARGETS']))
461 # Scan all targets and add unique nodes to set of sources
462 print ' Scanning dependency tree ...'
463 all_srcs = set()
464 walker = SourceWalker(tmp_alias[0], all_srcs)
465 walker.WalkAll()
467 # Walk all sources and build directory trees
468 print ' Building source tree...'
469 for n in all_srcs:
470 if not hasattr(n, 'rfile'):
471 continue # Not a file
472 n = n.rfile()
473 if not hasattr(n, 'isfile') or not n.isfile():
474 continue # Not a file
475 if n.has_builder():
476 continue # Not a leaf node
477 if n.suffix not in suffixes:
478 continue # Not a file type we include
480 path = n.abspath
481 for f in folders:
482 if path.startswith(f[1]):
483 if f[0] is None:
484 # Folder name of None is a filter
485 break
486 relpath = path[len(f[1]) + 1:].split(os.sep)
487 folder_dict = f[2]
488 # Recursively add subdirs
489 for pathseg in relpath[:-1]:
490 if pathseg not in folder_dict:
491 folder_dict[pathseg] = {}
492 folder_dict = folder_dict[pathseg]
493 # Add file to last subdir. No dict, since this isn't a subdir
494 folder_dict[env.RelativePath(project_dir, path)] = None
495 break
497 print ' Writing project file...'
499 vsp = VSProjectWriter(project_file)
500 vsp.Create(target_name)
502 # One configuration for all build modes
503 vsp.AddConfig('all', {}, {})
505 # Add files
506 for f in folders:
507 if f[0] is None:
508 continue # Skip filters
509 vsp.AddFiles(f[0], f[2])
511 vsp.Write()
512 return 0
515 def ComponentVSSourceProject(self, project_name, target_names, **kwargs):
516 """Visual Studio source project pseudo-builder.
518 Args:
519 self: Environment context.
520 project_name: Name of the project.
521 target_names: List of target names to include source for.
522 kwargs: Optional keyword arguments override environment variables in the
523 derived environment used to create the project.
525 Returns:
526 A list of output nodes.
528 # Builder only works on Windows
529 if sys.platform not in ('win32', 'cygwin'):
530 return []
532 # Clone environment and add keyword args
533 env = self.Clone()
534 for k, v in kwargs.items():
535 env[k] = v
537 # Save the project name and targets
538 env['PROJECT_NAME'] = project_name
539 env['COMPONENT_VS_SOURCE_TARGETS'] = target_names
541 # Call the real builder
542 return env.ComponentVSSourceProjectBuilder(
543 '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', [])
545 #------------------------------------------------------------------------------
548 def FindSources(env, dest, source, suffixes=None):
549 """Recursively finds sources and adds them to the destination set.
551 Args:
552 env: Environment context.
553 dest: Set to add source nodes to.
554 source: Source file(s) to find. May be a string, Node, or a list of
555 mixed strings or Nodes. Strings will be passed through env.Glob() to
556 evaluate wildcards. If a source evaluates to a directory, the entire
557 directory will be recursively added.
558 suffixes: List of suffixes to include. If not None, only files with these
559 suffixes will be added to dest.
561 for source_entry in env.Flatten(source):
562 if type(source_entry) == str:
563 # Search for matches for each source entry
564 source_nodes = env.Glob(source_entry)
565 else:
566 # Source entry is already a file or directory node; no need to glob it
567 source_nodes = [source_entry]
568 for s in source_nodes:
569 if str(s.__class__) == 'SCons.Node.FS.Dir':
570 # Recursively search subdir. Since glob('*') doesn't match dot files,
571 # also glob('.*').
572 FindSources(env, dest, [s.abspath + '/*', s.abspath + '/.*'], suffixes)
573 elif suffixes and s.suffix in suffixes:
574 dest.add(s)
577 def ComponentVSDirProjectBuilder(target, source, env):
578 """Visual Studio directory project builder.
580 Args:
581 target: Destination file.
582 source: List of sources to be added to the target.
583 env: Environment context.
585 Returns:
586 Zero if successful.
588 source = source # Silence gpylint
590 target_name = env['PROJECT_NAME']
591 project_file = target[0].path
592 project_dir = target[0].dir
594 # Convert source folders to absolute paths
595 folders = []
596 for f in env['COMPONENT_VS_SOURCE_FOLDERS']:
597 # (folder name, folder abspath, dict of contents)
598 folders.append((f[0], env.Dir(f[1]).abspath, {}))
600 # Recursively scan source directories
601 print ' Scanning directories for source...'
602 all_srcs = set()
603 FindSources(env, all_srcs, env['PROJECT_SOURCES'],
604 suffixes=env.SubstList2('$COMPONENT_VS_SOURCE_SUFFIXES'))
606 # Walk all sources and build directory trees
607 print ' Building source tree...'
608 for n in all_srcs:
609 # Map addRepository'd source to its real location.
610 path = n.rfile().abspath
611 for f in folders:
612 if path.startswith(f[1]):
613 if f[0] is None:
614 # Folder name of None is a filter
615 break
616 relpath = path[len(f[1]) + 1:].split(os.sep)
617 folder_dict = f[2]
618 # Recursively add subdirs
619 for pathseg in relpath[:-1]:
620 if pathseg not in folder_dict:
621 folder_dict[pathseg] = {}
622 folder_dict = folder_dict[pathseg]
623 # Add file to last subdir. No dict, since this isn't a subdir
624 folder_dict[env.RelativePath(project_dir, path)] = None
625 break
627 print ' Writing project file...'
629 vsp = VSProjectWriter(project_file)
630 vsp.Create(target_name)
632 # One configuration for all build modes
633 vsp.AddConfig('all', {}, {})
635 # Add files
636 for f in folders:
637 if f[0] is None:
638 continue # Skip filters
639 vsp.AddFiles(f[0], f[2])
641 vsp.Write()
642 return 0
645 def ComponentVSDirProject(self, project_name, source, **kwargs):
646 """Visual Studio directory project pseudo-builder.
648 Args:
649 self: Environment context.
650 project_name: Name of the project.
651 source: List of source files and/or directories.
652 kwargs: Optional keyword arguments override environment variables in the
653 derived environment used to create the project.
655 Returns:
656 A list of output nodes.
658 # Builder only works on Windows
659 if sys.platform not in ('win32', 'cygwin'):
660 return []
662 # Clone environment and add keyword args
663 env = self.Clone()
664 for k, v in kwargs.items():
665 env[k] = v
667 # Save the project name and sources
668 env['PROJECT_NAME'] = project_name
669 env['PROJECT_SOURCES'] = source
671 # Call the real builder
672 return env.ComponentVSDirProjectBuilder(
673 '$COMPONENT_VS_PROJECT_DIR/${PROJECT_NAME}', [])
675 #------------------------------------------------------------------------------
678 def ComponentVSSolutionBuilder(target, source, env):
679 """Visual Studio solution builder.
681 Args:
682 target: Destination file.
683 source: List of sources to be added to the target.
684 env: Environment context.
686 Returns:
687 Zero if successful.
689 source = source # Silence gpylint
691 solution_file = target[0].path
692 projects = env['SOLUTION_PROJECTS']
693 folders = env['SOLUTION_FOLDERS']
695 # Scan externally-generated projects
696 external_projects = []
697 for p in source:
698 guid, name = GetGuidAndNameFromVSProject(p.abspath)
699 external_projects.append((p, name, guid))
701 f = open(solution_file, 'wt')
702 f.write('Microsoft Visual Studio Solution File, Format Version 9.00\n')
703 f.write('# Visual Studio 2005\n')
705 # Projects generated by ComponentVSSolution()
706 for p in projects:
707 project_file = env.File(
708 '$COMPONENT_VS_PROJECT_DIR/%s$COMPONENT_VS_PROJECT_SUFFIX' % p)
709 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
710 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID
711 p, # Project name
712 env.RelativePath(target[0].dir, project_file), # Path to project file
713 MakeGuid(p), # Project GUID
716 # Projects generated elsewhere
717 for p, name, guid in external_projects:
718 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
719 # TODO: What if this project isn't type external makefile?
720 # How to tell what type it is?
721 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID
722 name, # Project name
723 env.RelativePath(target[0].dir, p), # Path to project file
724 guid, # Project GUID
727 # Folders from build groups
728 # TODO: Currently no way to add external project (specified in sources) to a
729 # solution folder.
730 for folder in folders:
731 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
732 '{2150E333-8FDC-42A3-9474-1A3956D46DE8}', # Solution folder GUID
733 folder, # Folder name
734 folder, # Folder name (again)
735 # Use a different seed so the folder won't get the same GUID as a
736 # project.
737 MakeGuid(folder, seed='folder'), # Project GUID
740 f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
741 for mode in GetTargetModes():
742 f.write('\t\t%s|Win32 = %s|Win32\n' % (mode, mode))
743 f.write('\tEndGlobalSection\n')
745 # Determine which projects should be enabled
746 # TODO: This is somewhat clunky. DEFAULT_TARGETS is global, and what we
747 # really need is something mode-specific. In theory we could make this a
748 # mode-specific dict rather than a list, but that'd also be a pain to
749 # populate.
750 # These variable names are also getting REALLY long. Perhaps we should
751 # define shorter ones (with the default value redirecting to the longer
752 # ones for legacy compatibility).
753 enable_projects = env.SubstList2('$COMPONENT_VS_ENABLED_PROJECTS')
755 f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
757 # Projects generated by ComponentVSSolution()
758 for p in projects:
759 for mode in GetTargetModes():
760 f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % (
761 MakeGuid(p), # Project GUID
762 mode, # Solution build configuration
763 mode, # Project build config for that solution config
766 t = GetTargets().get(p)
768 # Determine if project should be enabled in this mode
769 enabled = t and mode in t.mode_properties
770 if enable_projects and p not in enable_projects:
771 # Enable list specified, but target isn't in it
772 # TODO: Since we env.Default(scons-out) elsewhere, this likely causes
773 # all projects to be disabled by default. But that's realistically
774 # better than enabling them all...
775 enabled = False
777 if enabled:
778 # Target can be built in this mode
779 f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % (
780 MakeGuid(p), # Project GUID
781 mode, # Solution build configuration
782 mode, # Project build config for that solution config
785 # Projects generated elsewhere
786 for p, name, guid in external_projects:
787 for mode in GetTargetModes():
788 f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % (
789 guid, # Project GUID
790 mode, # Solution build configuration
791 mode, # Project build config for that solution config
794 if name in enable_projects or not enable_projects:
795 # Build target in this mode
796 f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % (
797 guid, # Project GUID
798 mode, # Solution build configuration
799 mode, # Project build config for that solution config
802 f.write('\tEndGlobalSection\n')
804 f.write('\tGlobalSection(SolutionProperties) = preSolution\n')
805 f.write('\t\tHideSolutionNode = FALSE\n')
806 f.write('\tEndGlobalSection\n')
808 if folders:
809 f.write('\tGlobalSection(NestedProjects) = preSolution\n')
810 for p, folder in projects.items():
811 f.write('\t\t%s = %s\n' % (MakeGuid(p), MakeGuid(folder, seed='folder')))
812 f.write('\tEndGlobalSection\n')
814 f.write('EndGlobal\n')
815 f.close()
817 return 0
820 def ComponentVSSolution(self, solution_name, target_names, projects=None,
821 **kwargs):
822 """Visual Studio solution pseudo-builder.
824 Args:
825 self: Environment context.
826 solution_name: Name of the solution.
827 target_names: Names of targets or target groups to include in the solution.
828 This will automatically build projects for them.
829 projects: List of aditional projects not generated by this solution to
830 include in the solution.
831 kwargs: Optional keyword arguments override environment variables in the
832 derived environment used to create the solution.
834 Returns:
835 The list of output nodes.
837 # TODO: Should have option to build source project also. Perhaps select
838 # using a --source_project option, since it needs to use gather_inputs
839 # to scan the DAG and will blow up the null build time.
840 # TODO: Should also have autobuild_projects option. If false, don't build
841 # them.
842 # TODO: Should also be able to specify a target group directly
843 # (i.e. 'run_all_tests')
845 # Builder only works on Windows
846 if sys.platform not in ('win32', 'cygwin'):
847 return []
849 # Clone environment and add keyword args
850 env = self.Clone()
851 for k, v in kwargs.items():
852 env[k] = v
854 # Save the target name
855 env['SOLUTION_NAME'] = solution_name
857 # Get list of targets to make projects for. At this point we haven't
858 # determined whether they're groups or targets.
859 target_names = env.SubstList2(target_names)
860 env['SOLUTION_TARGETS'] = target_names
862 # Save the default targets list as an environment variable
863 env['COMPONENT_VS_SCONS_DEFAULT_TARGETS'] = SCons.Script.DEFAULT_TARGETS
865 # Expand target_names into project names, and create project-to-folder
866 # mappings
867 project_names = {}
868 folders = []
869 if target_names:
870 # Expand target_names into project names
871 for target in target_names:
872 if target in GetTargetGroups():
873 # Add target to folders
874 folders.append(target)
875 # Add all project_names in the group
876 for t in GetTargetGroups()[target].GetTargetNames():
877 project_names[t] = target
878 elif target in GetTargets():
879 # Just a target name
880 project_names[target] = None
881 else:
882 print 'Warning: ignoring unknown target "%s"' % target
883 else:
884 # No target names specified, so use all projects
885 for t in GetTargets():
886 project_names[t] = None
888 env['SOLUTION_FOLDERS'] = folders
889 env['SOLUTION_PROJECTS'] = project_names
891 # Call the real builder
892 out_nodes = env.ComponentVSSolutionBuilder(
893 '$COMPONENT_VS_SOLUTION_DIR/${SOLUTION_NAME}', projects or [])
895 # Call the real builder for the projects we generate
896 for p in project_names:
897 out_nodes += env.ComponentVSProject(p)
899 # Add the solution target
900 # TODO: Should really defer the rest of the work above, since otherwise we
901 # can't build a solution which has a target to rebuild itself.
902 env.Alias('all_solutions', env.Alias(solution_name, out_nodes))
904 # TODO: To rebuild the solution properly, need to override its project
905 # configuration so it only has '--mode=all' (or some other way of setting the
906 # subset of modes which it should use to rebuild itself). Rebuilding with
907 # the property below will strip it down to just the current build mode, which
908 # isn't what we want.
909 # Let component_targets know this target is available in the current mode
910 #self.SetTargetProperty(solution_name, TARGET_PATH=out_nodes[0])
912 return out_nodes
914 #------------------------------------------------------------------------------
917 def generate(env):
918 # NOTE: SCons requires the use of this name, which fails gpylint.
919 """SCons entry point for this tool."""
921 # Add pseudo-builders to set up the project and solution builders. These
922 # need to be available on all platforms so that SConscripts which reference
923 # them will work.
924 env.AddMethod(ComponentVSDirProject)
925 env.AddMethod(ComponentVSProject)
926 env.AddMethod(ComponentVSSolution)
927 env.AddMethod(ComponentVSSourceProject)
929 # If not on Windows, don't do anything else
930 if sys.platform not in ('win32', 'cygwin'):
931 return
933 # Include tools we need
934 env.Tool('gather_inputs')
936 env.SetDefault(
937 COMPONENT_VS_PROJECT_DIR='$COMPONENT_VS_SOLUTION_DIR/projects',
938 COMPONENT_VS_PROJECT_SCRIPT_NAME = 'hammer.bat',
939 COMPONENT_VS_PROJECT_SCRIPT_PATH = (
940 '$$(ProjectDir)/$VS_PROJECT_TO_MAIN_DIR/'
941 '$COMPONENT_VS_PROJECT_SCRIPT_NAME'),
942 COMPONENT_VS_PROJECT_SUFFIX='.vcproj',
944 COMPONENT_VS_SOLUTION_DIR='$DESTINATION_ROOT/solution',
945 COMPONENT_VS_SOLUTION_SUFFIX='.sln',
946 COMPONENT_VS_ENABLED_PROJECTS=['$COMPONENT_VS_SCONS_DEFAULT_TARGETS'],
948 COMPONENT_VS_SOURCE_SUFFIXES=['$CPPSUFFIXES', '.rc', '.scons'],
949 COMPONENT_VS_SOURCE_FOLDERS=[('source', '$MAIN_DIR')],
952 AddTargetGroup('all_solutions', 'solutions can be built')
954 # Add builders
955 vcprojaction = SCons.Script.Action(ComponentVSProjectBuilder, varlist=[
956 'COMPONENT_VS_PROJECT_SCRIPT_PATH',
957 'TARGET_NAME',
958 'TARGET_PATH',
960 vcprojbuilder = SCons.Script.Builder(
961 action=vcprojaction,
962 suffix='$COMPONENT_VS_PROJECT_SUFFIX')
964 source_vcproj_action = SCons.Script.Action(
965 ComponentVSSourceProjectBuilder, varlist=[
966 'COMPONENT_VS_SOURCE_FOLDERS',
967 'COMPONENT_VS_SOURCE_SUFFIXES',
968 'COMPONENT_VS_SOURCE_TARGETS',
970 source_vcproj_builder = SCons.Script.Builder(
971 action=source_vcproj_action,
972 suffix='$COMPONENT_VS_PROJECT_SUFFIX')
974 dir_vcproj_action = SCons.Script.Action(
975 ComponentVSDirProjectBuilder, varlist=[
976 'COMPONENT_VS_SOURCE_FOLDERS',
977 'COMPONENT_VS_SOURCE_SUFFIXES',
978 'PROJECT_SOURCES',
980 dir_vcproj_builder = SCons.Script.Builder(
981 action=dir_vcproj_action,
982 suffix='$COMPONENT_VS_PROJECT_SUFFIX')
984 slnaction = SCons.Script.Action(ComponentVSSolutionBuilder, varlist=[
985 'COMPONENT_VS_ENABLED_PROJECTS',
986 'SOLUTION_FOLDERS',
987 'SOLUTION_PROJECTS',
988 'SOLUTION_TARGETS',
990 slnbuilder = SCons.Script.Builder(
991 action=slnaction,
992 suffix='$COMPONENT_VS_SOLUTION_SUFFIX')
994 env.Append(BUILDERS={
995 'ComponentVSDirProjectBuilder': dir_vcproj_builder,
996 'ComponentVSProjectBuilder': vcprojbuilder,
997 'ComponentVSSolutionBuilder': slnbuilder,
998 'ComponentVSSourceProjectBuilder': source_vcproj_builder,