summaryrefslogtreecommitdiffstats
path: root/site_scons/site_tools/component_targets_msvs.py
diff options
context:
space:
mode:
Diffstat (limited to 'site_scons/site_tools/component_targets_msvs.py')
-rwxr-xr-xsite_scons/site_tools/component_targets_msvs.py461
1 files changed, 461 insertions, 0 deletions
diff --git a/site_scons/site_tools/component_targets_msvs.py b/site_scons/site_tools/component_targets_msvs.py
new file mode 100755
index 0000000..57be5b3
--- /dev/null
+++ b/site_scons/site_tools/component_targets_msvs.py
@@ -0,0 +1,461 @@
+#!/usr/bin/python2.4
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Visual Studio solution output of component targets for SCons."""
+
+import md5
+import sys
+import xml.dom
+import xml.dom.minidom
+import SCons
+
+
+#------------------------------------------------------------------------------
+
+
+def MakeGuid(name, seed='component_targets_msvs'):
+ """Returns a GUID for the specified target name.
+
+ Args:
+ name: Target name.
+ seed: Seed for MD5 hash.
+ Returns:
+ A GUID-line string calculated from the name and seed.
+
+ This generates something which looks like a GUID, but depends only on the
+ name and seed. This means the same name/seed will always generate the same
+ GUID, so that projects and solutions which refer to each other can explicitly
+ determine the GUID to refer to explicitly. It also means that the GUID will
+ not change when the project for a target is rebuilt.
+ """
+ # Calculate a MD5 signature for the seed and name.
+ d = md5.new(str(seed) + str(name)).hexdigest().upper()
+ # Convert most of the signature to GUID form (discard the rest)
+ guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20]
+ + '-' + d[20:32] + '}')
+ return guid
+
+#------------------------------------------------------------------------------
+
+
+def GetGuidFromVSProject(project_path):
+ """Reads the GUID from a Visual Studio project file.
+
+ Args:
+ project_path: Path to the Visual Studio project file.
+
+ Returns:
+ The GUID string from the file.
+ """
+ doc = xml.dom.minidom.parse(project_path)
+ try:
+ n_root = doc.documentElement
+ if n_root.nodeName != 'VisualStudioProject':
+ raise SCons.Errors.UserError('%s is not a Visual Studio project.' %
+ project_path)
+ return str(n_root.attributes['ProjectGUID'].nodeValue)
+ finally:
+ # Clean up doc
+ doc.unlink()
+
+#------------------------------------------------------------------------------
+
+
+def ComponentVSProjectBuilder(target, source, env):
+ """Visual Studio project builder.
+
+ Args:
+ target: Destination file.
+ source: List of sources to be added to the target.
+ env: Environment context.
+
+ Returns:
+ Zero if successful.
+ """
+ source = source # Silence gpylint
+
+ target_name = env['TARGET_NAME']
+ project_file = target[0].path
+ project_to_main = env.RelativePath(target[0].dir, env.Dir('$MAIN_DIR'),
+ sep='/')
+ hammer_bat = '$(ProjectDir)/%s/hammer.bat' % project_to_main
+
+ # Project header
+ xml_impl = xml.dom.getDOMImplementation()
+ doc = xml_impl.createDocument(None, 'VisualStudioProject', None)
+
+ n_root = doc.documentElement
+ n_root.setAttribute('ProjectType', 'Visual C++')
+ n_root.setAttribute('Version', '8.00')
+ n_root.setAttribute('Name', target_name)
+ n_root.setAttribute('ProjectGUID', MakeGuid(target_name))
+ n_root.setAttribute('RootNamespace', target_name)
+ n_root.setAttribute('Keyword', 'MakeFileProj')
+
+ n_platform = doc.createElement('Platforms')
+ n_root.appendChild(n_platform)
+ n = doc.createElement('Platform')
+ n.setAttribute('Name', 'Win32')
+ n_platform.appendChild(n)
+
+ n_root.appendChild(doc.createElement('ToolFiles'))
+
+ # One configuration per build mode supported by this target
+ n_configs = doc.createElement('Configurations')
+ n_root.appendChild(n_configs)
+
+ target_path = env['TARGET_PATH']
+ for mode, path in target_path.items():
+ n_config = doc.createElement('Configuration')
+ n_config.setAttribute('Name', '%s|Win32' % mode)
+ n_config.setAttribute('OutputDirectory',
+ '$(ProjectDir)/%s/%s/out' % (mode, target_name))
+ n_config.setAttribute('IntermediateDirectory',
+ '$(ProjectDir)/%s/%s/tmp' % (mode, target_name))
+ n_config.setAttribute('ConfigurationType', '0')
+ n_configs.appendChild(n_config)
+
+ n_tool = doc.createElement('Tool')
+ n_tool.setAttribute('Name', 'VCNMakeTool')
+ n_tool.setAttribute('IncludeSearchPath', '')
+ n_tool.setAttribute('ForcedIncludes', '')
+ n_tool.setAttribute('AssemblySearchPath', '')
+ n_tool.setAttribute('ForcedUsingAssemblies', '')
+ n_tool.setAttribute('CompileAsManaged', '')
+ n_tool.setAttribute('PreprocessorDefinitions', '')
+ if path:
+ n_tool.setAttribute(
+ 'Output', env.RelativePath(target[0].dir, env.Entry(path), sep='/'))
+ build_cmd = '%s --mode=%s %s' % (hammer_bat, mode, target_name)
+ clean_cmd = '%s --mode=%s -c %s' % (hammer_bat, mode, target_name)
+ n_tool.setAttribute('BuildCommandLine', build_cmd)
+ n_tool.setAttribute('CleanCommandLine', clean_cmd)
+ n_tool.setAttribute('ReBuildCommandLine', clean_cmd + ' && ' + build_cmd)
+ n_config.appendChild(n_tool)
+
+ n_files = doc.createElement('Files')
+ n_root.appendChild(n_files)
+ # TODO(rspangler): Fill in files - at least, the .scons file invoking the
+ # target.
+
+ n_root.appendChild(doc.createElement('Globals'))
+
+ f = open(project_file, 'wt')
+ doc.writexml(f, encoding='Windows-1252', addindent=' ', newl='\n')
+ f.close()
+
+ return 0
+
+
+def ComponentVSProject(self, target_name, **kwargs):
+ """Visual Studio project pseudo-builder for the specified target.
+
+ Args:
+ self: Environment context.
+ target_name: Name of the target.
+ kwargs: Optional keyword arguments override environment variables in the
+ derived environment used to create the project.
+
+ Returns:
+ A list of output nodes.
+ """
+ # Builder only works on Windows
+ if sys.platform not in ('win32', 'cygwin'):
+ return []
+
+ # Clone environment and add keyword args
+ env = self.Clone()
+ for k, v in kwargs.items():
+ env[k] = v
+
+ # Save the target name
+ env['TARGET_NAME'] = target_name
+
+ # Extract the target properties and save in the environment for use by the
+ # real builder.
+ t = GetTargets().get(target_name)
+ env['TARGET_PATH'] = {}
+ if t:
+ for mode, mode_properties in t.mode_properties.items():
+ # Since the target path is what Visual Studio will run, use the EXE
+ # property in preference to TARGET_PATH.
+ target_path = mode_properties.get('EXE',
+ mode_properties.get('TARGET_PATH'))
+ env.Append(TARGET_PATH={mode: target_path})
+ else:
+ # No modes declared for this target. Could be a custom alias created by
+ # a SConscript, rather than a component builder. Assume it can be built in
+ # all modes, but produces no output.
+ for mode in GetTargetModes():
+ env.Append(TARGET_PATH={mode: None})
+
+ # Call the real builder
+ return env.ComponentVSProjectBuilder(
+ '$COMPONENT_VS_PROJECT_DIR/${TARGET_NAME}', [])
+
+#------------------------------------------------------------------------------
+
+
+def ComponentVSSolutionBuilder(target, source, env):
+ """Visual Studio solution builder.
+
+ Args:
+ target: Destination file.
+ source: List of sources to be added to the target.
+ env: Environment context.
+
+ Returns:
+ Zero if successful.
+ """
+ source = source # Silence gpylint
+
+ solution_file = target[0].path
+ projects = env['SOLUTION_PROJECTS']
+ folders = env['SOLUTION_FOLDERS']
+
+ f = open(solution_file, 'wt')
+
+ f.write('Microsoft Visual Studio Solution File, Format Version 9.00\n')
+ f.write('# Visual Studio 2005\n')
+
+ # Projects generated by ComponentVSSolution()
+ for p in projects:
+ project_file = env.File(
+ '$COMPONENT_VS_PROJECT_DIR/%s$COMPONENT_VS_PROJECT_SUFFIX' % p)
+ f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
+ '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID
+ p, # Project name
+ env.RelativePath(target[0].dir, project_file), # Path to project file
+ MakeGuid(p), # Project GUID
+ ))
+
+ # Projects generated elsewhere
+ for p in source:
+ f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
+ # TODO(rspangler): What if this project isn't type external makefile?
+ # How to tell what type it is?
+ '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID
+ p, # Project name
+ env.RelativePath(target[0].dir, p), # Path to project file
+ GetGuidFromVSProject(p.abspath), # Project GUID
+ ))
+
+ # Folders from build groups
+ for folder in folders:
+ f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % (
+ '{2150E333-8FDC-42A3-9474-1A3956D46DE8}', # Solution folder GUID
+ folder, # Folder name
+ folder, # Folder name (again)
+ # Use a different seed so the folder won't get the same GUID as a
+ # project.
+ MakeGuid(folder, seed='folder'), # Project GUID
+ ))
+
+ f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
+ for mode in GetTargetModes():
+ f.write('\t\t%s|Win32 = %s|Win32\n' % (mode, mode))
+ f.write('\tEndGlobalSection\n')
+
+ f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
+ for p in projects:
+ for mode in GetTargetModes():
+ f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % (
+ MakeGuid(p), # Project GUID
+ mode, # Solution build configuration
+ mode, # Project build config for that solution config
+ ))
+ t = GetTargets().get(p)
+ if t and mode in t.mode_properties:
+ # Target can be built in this mode
+ f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % (
+ MakeGuid(p), # Project GUID
+ mode, # Solution build configuration
+ mode, # Project build config for that solution config
+ ))
+ f.write('\tEndGlobalSection\n')
+
+ f.write('\tGlobalSection(SolutionProperties) = preSolution\n')
+ f.write('\t\tHideSolutionNode = FALSE\n')
+ f.write('\tEndGlobalSection\n')
+
+ if folders:
+ f.write('\tGlobalSection(NestedProjects) = preSolution\n')
+ for p, folder in projects.items():
+ f.write('\t\t%s = %s\n' % (MakeGuid(p), MakeGuid(folder, seed='folder')))
+ f.write('\tEndGlobalSection\n')
+
+ f.write('EndGlobal\n')
+ f.close()
+
+ return 0
+
+
+def ComponentVSSolution(self, solution_name, target_names, projects=None,
+ **kwargs):
+ """Visual Studio solution pseudo-builder.
+
+ Args:
+ self: Environment context.
+ solution_name: Name of the target.
+ target_names: Names of targets or target groups to include in the solution.
+ This will automatically build projects for them.
+ projects: List of aditional projects not generated by this solution to
+ include in the solution.
+ kwargs: Optional keyword arguments override environment variables in the
+ derived environment used to create the solution.
+
+ Returns:
+ The list of output nodes.
+ """
+ # TODO(rspangler): Should have option to build source project also. Perhaps
+ # select using a --source_project option, since it needs to use gather_inputs
+ # to scan the DAG and will blow up the null build time.
+ # TODO(rspangler): Should also have autobuild_projects option. If false,
+ # don't build them.
+ # TODO(rspangler): Should also be able to specify a target group directly
+ # (i.e. 'run_all_tests')
+
+ # Builder only works on Windows
+ if sys.platform not in ('win32', 'cygwin'):
+ return []
+
+ # Clone environment and add keyword args
+ env = self.Clone()
+ for k, v in kwargs.items():
+ env[k] = v
+
+ # Save the target name
+ env['SOLUTION_NAME'] = solution_name
+
+ # Get list of targets to make projects for. At this point we haven't
+ # determined whether they're groups or targets.
+ target_names = env.SubstList2(target_names)
+ env['SOLUTION_TARGETS'] = target_names
+
+ project_names = {}
+ folders = []
+ # Expand target_names into project names, and create project-to-folder
+ # mappings
+ if target_names:
+ # Expand target_names into project names
+ for target in target_names:
+ if target in GetTargetGroups():
+ # Add target to folders
+ folders.append(target)
+ # Add all project_names in the group
+ for t in GetTargetGroups()[target].GetTargetNames():
+ project_names[t] = target
+ elif target in GetTargets():
+ # Just a target name
+ project_names[target] = None
+ else:
+ print 'Warning: ignoring unknown target "%s"' % target
+ else:
+ # No target names specified, so use all projects
+ for t in GetTargets():
+ project_names[t] = None
+
+ env['SOLUTION_FOLDERS'] = folders
+ env['SOLUTION_PROJECTS'] = project_names
+
+ # Call the real builder
+ out_nodes = env.ComponentVSSolutionBuilder(
+ '$COMPONENT_VS_SOLUTION_DIR/${SOLUTION_NAME}', projects or [])
+
+ # Call the real builder for the projects we generate
+ for p in project_names:
+ out_nodes += env.ComponentVSProject(p)
+
+ # Add the solution target
+ # TODO(rspangler): Should really defer the rest of the work above, since
+ # otherwise we can't build a solution which has a target to rebuild itself.
+ env.Alias('all_solutions', env.Alias(solution_name, out_nodes))
+
+ # TODO(rspangler): To rebuild the solution properly, need to override its
+ # project configuration so it only has '--mode=all' (or some other way of
+ # setting the subset of modes which it should use to rebuild itself).
+ # Rebuilding with the property below will strip it down to just the current
+ # build mode, which isn't what we want.
+ # Let component_targets know this target is available in the current mode
+ #self.SetTargetProperty(solution_name, TARGET_PATH=out_nodes[0])
+
+ return out_nodes
+
+#------------------------------------------------------------------------------
+
+
+def generate(env):
+ # NOTE: SCons requires the use of this name, which fails gpylint.
+ """SCons entry point for this tool."""
+
+ # Add pseudo-builders to set up the project and solution builders. These
+ # need to be available on all platforms so that SConscripts which reference
+ # them will work.
+ env.AddMethod(ComponentVSProject)
+ env.AddMethod(ComponentVSSolution)
+
+ # If not on Windows, don't do anything else
+ if sys.platform not in ('win32', 'cygwin'):
+ return
+
+ # Include tools we need
+ env.Tool('gather_inputs')
+
+ env.SetDefault(
+ COMPONENT_VS_SOLUTION_DIR='$DESTINATION_ROOT/solution',
+ COMPONENT_VS_PROJECT_DIR='$COMPONENT_VS_SOLUTION_DIR/projects',
+ COMPONENT_VS_SOLUTION_SUFFIX='.sln',
+ COMPONENT_VS_PROJECT_SUFFIX='.vcproj',
+ )
+
+ AddTargetGroup('all_solutions', 'solutions can be built')
+
+ # Add builders
+ vcprojaction = SCons.Script.Action(ComponentVSProjectBuilder, varlist=[
+ 'TARGET_NAME',
+ 'TARGET_PATH',
+ ])
+ vcprojbuilder = SCons.Script.Builder(
+ action=vcprojaction,
+ suffix='$COMPONENT_VS_PROJECT_SUFFIX')
+
+ slnaction = SCons.Script.Action(ComponentVSSolutionBuilder, varlist=[
+ 'SOLUTION_TARGETS',
+ 'SOLUTION_FOLDERS',
+ 'SOLUTION_PROJECTS',
+ ])
+ slnbuilder = SCons.Script.Builder(
+ action=slnaction,
+ suffix='$COMPONENT_VS_SOLUTION_SUFFIX')
+
+ env.Append(BUILDERS={
+ 'ComponentVSProjectBuilder': vcprojbuilder,
+ 'ComponentVSSolutionBuilder': slnbuilder,
+ })