| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/python2.4 |
| 2 # Copyright 2008, Google Inc. |
| 3 # All rights reserved. |
| 4 # |
| 5 # Redistribution and use in source and binary forms, with or without |
| 6 # modification, are permitted provided that the following conditions are |
| 7 # met: |
| 8 # |
| 9 # * Redistributions of source code must retain the above copyright |
| 10 # notice, this list of conditions and the following disclaimer. |
| 11 # * Redistributions in binary form must reproduce the above |
| 12 # copyright notice, this list of conditions and the following disclaimer |
| 13 # in the documentation and/or other materials provided with the |
| 14 # distribution. |
| 15 # * Neither the name of Google Inc. nor the names of its |
| 16 # contributors may be used to endorse or promote products derived from |
| 17 # this software without specific prior written permission. |
| 18 # |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 |
| 31 """Visual Studio solution output of component targets for SCons.""" |
| 32 |
| 33 import md5 |
| 34 import sys |
| 35 import xml.dom |
| 36 import xml.dom.minidom |
| 37 import SCons |
| 38 |
| 39 |
| 40 #------------------------------------------------------------------------------ |
| 41 |
| 42 |
| 43 def MakeGuid(name, seed='component_targets_msvs'): |
| 44 """Returns a GUID for the specified target name. |
| 45 |
| 46 Args: |
| 47 name: Target name. |
| 48 seed: Seed for MD5 hash. |
| 49 Returns: |
| 50 A GUID-line string calculated from the name and seed. |
| 51 |
| 52 This generates something which looks like a GUID, but depends only on the |
| 53 name and seed. This means the same name/seed will always generate the same |
| 54 GUID, so that projects and solutions which refer to each other can explicitly |
| 55 determine the GUID to refer to explicitly. It also means that the GUID will |
| 56 not change when the project for a target is rebuilt. |
| 57 """ |
| 58 # Calculate a MD5 signature for the seed and name. |
| 59 d = md5.new(str(seed) + str(name)).hexdigest().upper() |
| 60 # Convert most of the signature to GUID form (discard the rest) |
| 61 guid = ('{' + d[:8] + '-' + d[8:12] + '-' + d[12:16] + '-' + d[16:20] |
| 62 + '-' + d[20:32] + '}') |
| 63 return guid |
| 64 |
| 65 #------------------------------------------------------------------------------ |
| 66 |
| 67 |
| 68 def GetGuidFromVSProject(project_path): |
| 69 """Reads the GUID from a Visual Studio project file. |
| 70 |
| 71 Args: |
| 72 project_path: Path to the Visual Studio project file. |
| 73 |
| 74 Returns: |
| 75 The GUID string from the file. |
| 76 """ |
| 77 doc = xml.dom.minidom.parse(project_path) |
| 78 try: |
| 79 n_root = doc.documentElement |
| 80 if n_root.nodeName != 'VisualStudioProject': |
| 81 raise SCons.Errors.UserError('%s is not a Visual Studio project.' % |
| 82 project_path) |
| 83 return str(n_root.attributes['ProjectGUID'].nodeValue) |
| 84 finally: |
| 85 # Clean up doc |
| 86 doc.unlink() |
| 87 |
| 88 #------------------------------------------------------------------------------ |
| 89 |
| 90 |
| 91 def ComponentVSProjectBuilder(target, source, env): |
| 92 """Visual Studio project builder. |
| 93 |
| 94 Args: |
| 95 target: Destination file. |
| 96 source: List of sources to be added to the target. |
| 97 env: Environment context. |
| 98 |
| 99 Returns: |
| 100 Zero if successful. |
| 101 """ |
| 102 source = source # Silence gpylint |
| 103 |
| 104 target_name = env['TARGET_NAME'] |
| 105 project_file = target[0].path |
| 106 project_to_main = env.RelativePath(target[0].dir, env.Dir('$MAIN_DIR'), |
| 107 sep='/') |
| 108 hammer_bat = '$(ProjectDir)/%s/hammer.bat' % project_to_main |
| 109 |
| 110 # Project header |
| 111 xml_impl = xml.dom.getDOMImplementation() |
| 112 doc = xml_impl.createDocument(None, 'VisualStudioProject', None) |
| 113 |
| 114 n_root = doc.documentElement |
| 115 n_root.setAttribute('ProjectType', 'Visual C++') |
| 116 n_root.setAttribute('Version', '8.00') |
| 117 n_root.setAttribute('Name', target_name) |
| 118 n_root.setAttribute('ProjectGUID', MakeGuid(target_name)) |
| 119 n_root.setAttribute('RootNamespace', target_name) |
| 120 n_root.setAttribute('Keyword', 'MakeFileProj') |
| 121 |
| 122 n_platform = doc.createElement('Platforms') |
| 123 n_root.appendChild(n_platform) |
| 124 n = doc.createElement('Platform') |
| 125 n.setAttribute('Name', 'Win32') |
| 126 n_platform.appendChild(n) |
| 127 |
| 128 n_root.appendChild(doc.createElement('ToolFiles')) |
| 129 |
| 130 # One configuration per build mode supported by this target |
| 131 n_configs = doc.createElement('Configurations') |
| 132 n_root.appendChild(n_configs) |
| 133 |
| 134 target_path = env['TARGET_PATH'] |
| 135 for mode, path in target_path.items(): |
| 136 n_config = doc.createElement('Configuration') |
| 137 n_config.setAttribute('Name', '%s|Win32' % mode) |
| 138 n_config.setAttribute('OutputDirectory', |
| 139 '$(ProjectDir)/%s/%s/out' % (mode, target_name)) |
| 140 n_config.setAttribute('IntermediateDirectory', |
| 141 '$(ProjectDir)/%s/%s/tmp' % (mode, target_name)) |
| 142 n_config.setAttribute('ConfigurationType', '0') |
| 143 n_configs.appendChild(n_config) |
| 144 |
| 145 n_tool = doc.createElement('Tool') |
| 146 n_tool.setAttribute('Name', 'VCNMakeTool') |
| 147 n_tool.setAttribute('IncludeSearchPath', '') |
| 148 n_tool.setAttribute('ForcedIncludes', '') |
| 149 n_tool.setAttribute('AssemblySearchPath', '') |
| 150 n_tool.setAttribute('ForcedUsingAssemblies', '') |
| 151 n_tool.setAttribute('CompileAsManaged', '') |
| 152 n_tool.setAttribute('PreprocessorDefinitions', '') |
| 153 if path: |
| 154 n_tool.setAttribute( |
| 155 'Output', env.RelativePath(target[0].dir, env.Entry(path), sep='/')) |
| 156 build_cmd = '%s --mode=%s %s' % (hammer_bat, mode, target_name) |
| 157 clean_cmd = '%s --mode=%s -c %s' % (hammer_bat, mode, target_name) |
| 158 n_tool.setAttribute('BuildCommandLine', build_cmd) |
| 159 n_tool.setAttribute('CleanCommandLine', clean_cmd) |
| 160 n_tool.setAttribute('ReBuildCommandLine', clean_cmd + ' && ' + build_cmd) |
| 161 n_config.appendChild(n_tool) |
| 162 |
| 163 n_files = doc.createElement('Files') |
| 164 n_root.appendChild(n_files) |
| 165 # TODO(rspangler): Fill in files - at least, the .scons file invoking the |
| 166 # target. |
| 167 |
| 168 n_root.appendChild(doc.createElement('Globals')) |
| 169 |
| 170 f = open(project_file, 'wt') |
| 171 doc.writexml(f, encoding='Windows-1252', addindent=' ', newl='\n') |
| 172 f.close() |
| 173 |
| 174 return 0 |
| 175 |
| 176 |
| 177 def ComponentVSProject(self, target_name, **kwargs): |
| 178 """Visual Studio project pseudo-builder for the specified target. |
| 179 |
| 180 Args: |
| 181 self: Environment context. |
| 182 target_name: Name of the target. |
| 183 kwargs: Optional keyword arguments override environment variables in the |
| 184 derived environment used to create the project. |
| 185 |
| 186 Returns: |
| 187 A list of output nodes. |
| 188 """ |
| 189 # Builder only works on Windows |
| 190 if sys.platform not in ('win32', 'cygwin'): |
| 191 return [] |
| 192 |
| 193 # Clone environment and add keyword args |
| 194 env = self.Clone() |
| 195 for k, v in kwargs.items(): |
| 196 env[k] = v |
| 197 |
| 198 # Save the target name |
| 199 env['TARGET_NAME'] = target_name |
| 200 |
| 201 # Extract the target properties and save in the environment for use by the |
| 202 # real builder. |
| 203 t = GetTargets().get(target_name) |
| 204 env['TARGET_PATH'] = {} |
| 205 if t: |
| 206 for mode, mode_properties in t.mode_properties.items(): |
| 207 # Since the target path is what Visual Studio will run, use the EXE |
| 208 # property in preference to TARGET_PATH. |
| 209 target_path = mode_properties.get('EXE', |
| 210 mode_properties.get('TARGET_PATH')) |
| 211 env.Append(TARGET_PATH={mode: target_path}) |
| 212 else: |
| 213 # No modes declared for this target. Could be a custom alias created by |
| 214 # a SConscript, rather than a component builder. Assume it can be built in |
| 215 # all modes, but produces no output. |
| 216 for mode in GetTargetModes(): |
| 217 env.Append(TARGET_PATH={mode: None}) |
| 218 |
| 219 # Call the real builder |
| 220 return env.ComponentVSProjectBuilder( |
| 221 '$COMPONENT_VS_PROJECT_DIR/${TARGET_NAME}', []) |
| 222 |
| 223 #------------------------------------------------------------------------------ |
| 224 |
| 225 |
| 226 def ComponentVSSolutionBuilder(target, source, env): |
| 227 """Visual Studio solution builder. |
| 228 |
| 229 Args: |
| 230 target: Destination file. |
| 231 source: List of sources to be added to the target. |
| 232 env: Environment context. |
| 233 |
| 234 Returns: |
| 235 Zero if successful. |
| 236 """ |
| 237 source = source # Silence gpylint |
| 238 |
| 239 solution_file = target[0].path |
| 240 projects = env['SOLUTION_PROJECTS'] |
| 241 folders = env['SOLUTION_FOLDERS'] |
| 242 |
| 243 f = open(solution_file, 'wt') |
| 244 |
| 245 f.write('Microsoft Visual Studio Solution File, Format Version 9.00\n') |
| 246 f.write('# Visual Studio 2005\n') |
| 247 |
| 248 # Projects generated by ComponentVSSolution() |
| 249 for p in projects: |
| 250 project_file = env.File( |
| 251 '$COMPONENT_VS_PROJECT_DIR/%s$COMPONENT_VS_PROJECT_SUFFIX' % p) |
| 252 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( |
| 253 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID |
| 254 p, # Project name |
| 255 env.RelativePath(target[0].dir, project_file), # Path to project file |
| 256 MakeGuid(p), # Project GUID |
| 257 )) |
| 258 |
| 259 # Projects generated elsewhere |
| 260 for p in source: |
| 261 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( |
| 262 # TODO(rspangler): What if this project isn't type external makefile? |
| 263 # How to tell what type it is? |
| 264 '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', # External makefile GUID |
| 265 p, # Project name |
| 266 env.RelativePath(target[0].dir, p), # Path to project file |
| 267 GetGuidFromVSProject(p.abspath), # Project GUID |
| 268 )) |
| 269 |
| 270 # Folders from build groups |
| 271 for folder in folders: |
| 272 f.write('Project("%s") = "%s", "%s", "%s"\nEndProject\n' % ( |
| 273 '{2150E333-8FDC-42A3-9474-1A3956D46DE8}', # Solution folder GUID |
| 274 folder, # Folder name |
| 275 folder, # Folder name (again) |
| 276 # Use a different seed so the folder won't get the same GUID as a |
| 277 # project. |
| 278 MakeGuid(folder, seed='folder'), # Project GUID |
| 279 )) |
| 280 |
| 281 f.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') |
| 282 for mode in GetTargetModes(): |
| 283 f.write('\t\t%s|Win32 = %s|Win32\n' % (mode, mode)) |
| 284 f.write('\tEndGlobalSection\n') |
| 285 |
| 286 f.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') |
| 287 for p in projects: |
| 288 for mode in GetTargetModes(): |
| 289 f.write('\t\t%s.%s|Win32.ActiveCfg = %s|Win32\n' % ( |
| 290 MakeGuid(p), # Project GUID |
| 291 mode, # Solution build configuration |
| 292 mode, # Project build config for that solution config |
| 293 )) |
| 294 t = GetTargets().get(p) |
| 295 if t and mode in t.mode_properties: |
| 296 # Target can be built in this mode |
| 297 f.write('\t\t%s.%s|Win32.Build.0 = %s|Win32\n' % ( |
| 298 MakeGuid(p), # Project GUID |
| 299 mode, # Solution build configuration |
| 300 mode, # Project build config for that solution config |
| 301 )) |
| 302 f.write('\tEndGlobalSection\n') |
| 303 |
| 304 f.write('\tGlobalSection(SolutionProperties) = preSolution\n') |
| 305 f.write('\t\tHideSolutionNode = FALSE\n') |
| 306 f.write('\tEndGlobalSection\n') |
| 307 |
| 308 if folders: |
| 309 f.write('\tGlobalSection(NestedProjects) = preSolution\n') |
| 310 for p, folder in projects.items(): |
| 311 f.write('\t\t%s = %s\n' % (MakeGuid(p), MakeGuid(folder, seed='folder'))) |
| 312 f.write('\tEndGlobalSection\n') |
| 313 |
| 314 f.write('EndGlobal\n') |
| 315 f.close() |
| 316 |
| 317 return 0 |
| 318 |
| 319 |
| 320 def ComponentVSSolution(self, solution_name, target_names, projects=None, |
| 321 **kwargs): |
| 322 """Visual Studio solution pseudo-builder. |
| 323 |
| 324 Args: |
| 325 self: Environment context. |
| 326 solution_name: Name of the target. |
| 327 target_names: Names of targets or target groups to include in the solution. |
| 328 This will automatically build projects for them. |
| 329 projects: List of aditional projects not generated by this solution to |
| 330 include in the solution. |
| 331 kwargs: Optional keyword arguments override environment variables in the |
| 332 derived environment used to create the solution. |
| 333 |
| 334 Returns: |
| 335 The list of output nodes. |
| 336 """ |
| 337 # TODO(rspangler): Should have option to build source project also. Perhaps |
| 338 # select using a --source_project option, since it needs to use gather_inputs |
| 339 # to scan the DAG and will blow up the null build time. |
| 340 # TODO(rspangler): Should also have autobuild_projects option. If false, |
| 341 # don't build them. |
| 342 # TODO(rspangler): Should also be able to specify a target group directly |
| 343 # (i.e. 'run_all_tests') |
| 344 |
| 345 # Builder only works on Windows |
| 346 if sys.platform not in ('win32', 'cygwin'): |
| 347 return [] |
| 348 |
| 349 # Clone environment and add keyword args |
| 350 env = self.Clone() |
| 351 for k, v in kwargs.items(): |
| 352 env[k] = v |
| 353 |
| 354 # Save the target name |
| 355 env['SOLUTION_NAME'] = solution_name |
| 356 |
| 357 # Get list of targets to make projects for. At this point we haven't |
| 358 # determined whether they're groups or targets. |
| 359 target_names = env.SubstList2(target_names) |
| 360 env['SOLUTION_TARGETS'] = target_names |
| 361 |
| 362 project_names = {} |
| 363 folders = [] |
| 364 # Expand target_names into project names, and create project-to-folder |
| 365 # mappings |
| 366 if target_names: |
| 367 # Expand target_names into project names |
| 368 for target in target_names: |
| 369 if target in GetTargetGroups(): |
| 370 # Add target to folders |
| 371 folders.append(target) |
| 372 # Add all project_names in the group |
| 373 for t in GetTargetGroups()[target].GetTargetNames(): |
| 374 project_names[t] = target |
| 375 elif target in GetTargets(): |
| 376 # Just a target name |
| 377 project_names[target] = None |
| 378 else: |
| 379 print 'Warning: ignoring unknown target "%s"' % target |
| 380 else: |
| 381 # No target names specified, so use all projects |
| 382 for t in GetTargets(): |
| 383 project_names[t] = None |
| 384 |
| 385 env['SOLUTION_FOLDERS'] = folders |
| 386 env['SOLUTION_PROJECTS'] = project_names |
| 387 |
| 388 # Call the real builder |
| 389 out_nodes = env.ComponentVSSolutionBuilder( |
| 390 '$COMPONENT_VS_SOLUTION_DIR/${SOLUTION_NAME}', projects or []) |
| 391 |
| 392 # Call the real builder for the projects we generate |
| 393 for p in project_names: |
| 394 out_nodes += env.ComponentVSProject(p) |
| 395 |
| 396 # Add the solution target |
| 397 # TODO(rspangler): Should really defer the rest of the work above, since |
| 398 # otherwise we can't build a solution which has a target to rebuild itself. |
| 399 env.Alias('all_solutions', env.Alias(solution_name, out_nodes)) |
| 400 |
| 401 # TODO(rspangler): To rebuild the solution properly, need to override its |
| 402 # project configuration so it only has '--mode=all' (or some other way of |
| 403 # setting the subset of modes which it should use to rebuild itself). |
| 404 # Rebuilding with the property below will strip it down to just the current |
| 405 # build mode, which isn't what we want. |
| 406 # Let component_targets know this target is available in the current mode |
| 407 #self.SetTargetProperty(solution_name, TARGET_PATH=out_nodes[0]) |
| 408 |
| 409 return out_nodes |
| 410 |
| 411 #------------------------------------------------------------------------------ |
| 412 |
| 413 |
| 414 def generate(env): |
| 415 # NOTE: SCons requires the use of this name, which fails gpylint. |
| 416 """SCons entry point for this tool.""" |
| 417 |
| 418 # Add pseudo-builders to set up the project and solution builders. These |
| 419 # need to be available on all platforms so that SConscripts which reference |
| 420 # them will work. |
| 421 env.AddMethod(ComponentVSProject) |
| 422 env.AddMethod(ComponentVSSolution) |
| 423 |
| 424 # If not on Windows, don't do anything else |
| 425 if sys.platform not in ('win32', 'cygwin'): |
| 426 return |
| 427 |
| 428 # Include tools we need |
| 429 env.Tool('gather_inputs') |
| 430 |
| 431 env.SetDefault( |
| 432 COMPONENT_VS_SOLUTION_DIR='$DESTINATION_ROOT/solution', |
| 433 COMPONENT_VS_PROJECT_DIR='$COMPONENT_VS_SOLUTION_DIR/projects', |
| 434 COMPONENT_VS_SOLUTION_SUFFIX='.sln', |
| 435 COMPONENT_VS_PROJECT_SUFFIX='.vcproj', |
| 436 ) |
| 437 |
| 438 AddTargetGroup('all_solutions', 'solutions can be built') |
| 439 |
| 440 # Add builders |
| 441 vcprojaction = SCons.Script.Action(ComponentVSProjectBuilder, varlist=[ |
| 442 'TARGET_NAME', |
| 443 'TARGET_PATH', |
| 444 ]) |
| 445 vcprojbuilder = SCons.Script.Builder( |
| 446 action=vcprojaction, |
| 447 suffix='$COMPONENT_VS_PROJECT_SUFFIX') |
| 448 |
| 449 slnaction = SCons.Script.Action(ComponentVSSolutionBuilder, varlist=[ |
| 450 'SOLUTION_TARGETS', |
| 451 'SOLUTION_FOLDERS', |
| 452 'SOLUTION_PROJECTS', |
| 453 ]) |
| 454 slnbuilder = SCons.Script.Builder( |
| 455 action=slnaction, |
| 456 suffix='$COMPONENT_VS_SOLUTION_SUFFIX') |
| 457 |
| 458 env.Append(BUILDERS={ |
| 459 'ComponentVSProjectBuilder': vcprojbuilder, |
| 460 'ComponentVSSolutionBuilder': slnbuilder, |
| 461 }) |
| OLD | NEW |