Chromium Code Reviews| Index: site_scons/site_tools/_Node_MSVS.py |
| =================================================================== |
| --- site_scons/site_tools/_Node_MSVS.py (revision 0) |
| +++ site_scons/site_tools/_Node_MSVS.py (revision 0) |
| @@ -0,0 +1,434 @@ |
| +#!/usr/bin/python2.4 |
| +# Copyright 2008, Google Inc. |
| +# All rights reserved. |
| + |
| +__doc__ = """SCons.Node.MSVS |
| + |
| +Microsoft Visual Studio nodes. |
| +""" |
| + |
| +import SCons.Node.FS |
| +import SCons.Script |
| + |
| + |
| +"""New implementation of Visual Studio project generation for SCons.""" |
| + |
| +import md5 |
| +import os |
| +import random |
| + |
| + |
| +# Initialize random number generator |
| +random.seed() |
| + |
| + |
| +#------------------------------------------------------------------------------ |
| +# Entry point for supplying a fixed map of GUIDs for testing. |
| + |
| +GUIDMap = {} |
| + |
| + |
| +#------------------------------------------------------------------------------ |
| +# Helper functions |
| + |
| + |
| +def MakeGuid(name, seed='msvs_new'): |
| + """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 |
| + |
| + |
| +#------------------------------------------------------------------------------ |
| +# Global look up of string names. |
| + |
| +class LookupError(Exception): |
| + def __str__(self): |
| + string, expanded = self.args |
| + if string == expanded: |
| + return string |
| + else: |
| + return '%s (%s)' % (string, expanded) |
| + |
| +_lookup_dict = {} |
| + |
| +def LookupAdd(item, result): |
| + _lookup_dict[item] = result |
| + _lookup_dict[result] = result |
| + |
| +def Lookup(item): |
| + """Looks up an MSVS item in the global dictionary. |
| + |
| + Args: |
| + item: A path (string) or instance for looking up. |
| + Returns: |
| + An instance from the global _lookup_dict. |
| + |
| + Raises an exception if the item does not exist in the _lookup_dict. |
| + """ |
| + global _lookup_dict |
| + try: |
| + return _lookup_dict[item] |
| + except KeyError: |
| + return SCons.Node.FS.default_fs.Entry(item, create=False) |
| + |
| +def LookupCreate(klass, item, *args, **kw): |
| + """Looks up an MSVS item, creating it if it doesn't already exist. |
| + |
| + Args: |
| + klass: The class of item being looked up, or created if it |
| + doesn't already exist in the global _lookup_dict. |
| + item: The a string (or instance) being looked up. |
| + *args: positional arguments passed to the klass.initialize() method. |
| + **kw: keyword arguments passed to the klass.initialize() method. |
| + Returns: |
| + An instance from the global _lookup_dict, or None if the item does |
| + not exist in the _lookup_dict. |
| + |
| + This raises a LookupError if the found instance doesn't match the |
| + requested klass. |
| + |
| + When creating a new instance, this populates the _lookup_dict with |
| + both the item and the instance itself as keys, so that looking up |
| + the instance will return itself. |
| + """ |
| + global _lookup_dict |
| + result = _lookup_dict.get(item) |
| + if result: |
| + if not isinstance(result, klass): |
| + raise LookupError, "tried to redefine %s as a %s" % (item, klass) |
| + return result |
| + result = klass() |
| + result.initialize(item, *args, **kw) |
| + LookupAdd(item, result) |
| + return result |
| + |
| + |
| +#------------------------------------------------------------------------------ |
| + |
| +class _MSVSFolder(SCons.Node.Node): |
| + """Folder in a Visual Studio project or solution.""" |
| + |
| + entry_type_guid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}' |
| + |
| + def initialize(self, path, name = None, entries = None, guid = None, |
| + items = None): |
| + """Initializes the folder. |
| + |
| + Args: |
| + path: The unique name of the folder, by which other MSVS Nodes can |
| + refer to it. This is not necessarily the name that gets printed |
| + in the .sln file. |
| + name: The name of this folder as actually written in a generated |
| + .sln file. The default is |
| + entries: List of folder entries to nest inside this folder. May contain |
| + Folder or Project objects. May be None, if the folder is empty. |
| + guid: GUID to use for folder, if not None. |
| + items: List of solution items to include in the folder project. May be |
| + None, if the folder does not directly contain items. |
| + """ |
| + # For folder entries, the path is the same as the name |
| + self.msvs_path = path |
| + self.msvs_name = name or path |
| + |
| + self.guid = guid |
| + |
| + # Copy passed lists (or set to empty lists) |
| + self.entries = list(entries or []) |
| + self.items = list(items or []) |
| + |
| + def get_guid(self): |
| + if self.guid is None: |
| + guid = GUIDMap.get(self.msvs_path) |
| + if not guid: |
| + # The GUID for the folder can be random, since it's used only inside |
| + # solution files and doesn't need to be consistent across runs. |
| + guid = MakeGuid(random.random()) |
| + self.guid = guid |
| + return self.guid |
| + |
| + def get_msvs_path(self, sln): |
| + return self.msvs_name |
| + |
| +def MSVSFolder(env, item, *args, **kw): |
| + return LookupCreate(_MSVSFolder, item, *args, **kw) |
| + |
| +#------------------------------------------------------------------------------ |
| + |
| + |
| +class _MSVSProject(SCons.Node.FS.File): |
| + """Visual Studio project.""" |
| + |
| + entry_type_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' |
| + |
| + def initialize(self, path, name = None, dependencies = None, guid = None): |
| + """Initializes the project. |
| + |
| + Args: |
| + path: Relative path to project file. |
| + name: Name of project. If None, the name will be the same as the base |
| + name of the project file. |
| + dependencies: List of other Project objects this project is dependent |
| + upon, if not None. |
| + guid: GUID to use for project, if not None. |
| + """ |
| + self.msvs_path = path |
| + self.msvs_name = name or os.path.splitext(os.path.basename(self.name))[0] |
| + |
| + self.guid = guid |
| + |
| + # Copy passed lists (or set to empty lists) |
| + self.dependencies = list(dependencies or []) |
| + |
| + def get_guid(self): |
| + if self.guid is None: |
| + guid = GUIDMap.get(self.msvs_path) |
| + if not guid: |
| + # Set GUID from path |
| + # TODO(rspangler): This is fragile. |
| + # 1. We can't just use the project filename sans path, since there |
| + # could be multiple projects with the same base name (for example, |
| + # foo/unittest.vcproj and bar/unittest.vcproj). |
| + # 2. The path needs to be relative to $SOURCE_ROOT, so that the project |
| + # GUID is the same whether it's included from base/base.sln or |
| + # foo/bar/baz/baz.sln. |
| + # 3. The GUID needs to be the same each time this builder is invoked, |
| + # so that we don't need to rebuild the solution when the |
| + # project changes. |
| + # 4. We should be able to handle pre-built project files by reading the |
| + # GUID from the files. |
| + guid = MakeGuid(self.msvs_path) |
| + self.guid = guid |
| + return self.guid |
| + |
| + def get_msvs_path(self, sln): |
| + return sln.rel_path(self).replace('/', '\\') |
| + |
| +def MSVSProject(env, item, *args, **kw): |
| + if not SCons.Util.is_String(item): |
| + return item |
| + item = env.subst(item) |
| + result = env.fs._lookup(item, None, _MSVSProject, create=1) |
| + result.initialize(item, *args, **kw) |
| + LookupAdd(item, result) |
| + return result |
| + |
| +#------------------------------------------------------------------------------ |
| + |
| +def MSVSAction(target, source, env): |
| + target[0].Write(env) |
| + |
| +MSVSSolutionAction = SCons.Script.Action(MSVSAction, |
| + "Generating Visual Studio solution `$TARGET' ...") |
| + |
| +class _MSVSSolution(SCons.Node.FS.File): |
| + """Visual Studio solution.""" |
| + |
| + def initialize(self, env, path, entries = None, variants = None, |
| + websiteProperties = True): |
| + """Initializes the solution. |
| + |
| + Args: |
| + path: Path to solution file. |
| + entries: List of entries in solution. May contain Folder or Project |
| + objects. May be None, if the folder is empty. |
| + variants: List of build variant strings. If none, a default list will |
| + be used. |
| + """ |
| + self.msvs_path = path |
| + self.websiteProperties = websiteProperties |
| + |
| + # Copy passed lists (or set to empty lists) |
| + self.entries = list(entries or []) |
| + |
| + if variants: |
| + # Copy passed list |
| + self.variants = variants[:] |
| + else: |
| + # Use default |
| + self.variants = ['Debug|Win32', 'Release|Win32'] |
| + # TODO(rspangler): Need to be able to handle a mapping of solution config |
| + # to project config. Should we be able to handle variants being a dict, |
| + # or add a separate variant_map variable? If it's a dict, we can't |
| + # guarantee the order of variants since dict keys aren't ordered. |
| + |
| + env.Command(self, [], MSVSSolutionAction) |
| + |
| + def Write(self, env): |
| + """Writes the solution file to disk. |
| + |
| + Raises: |
| + IndexError: An entry appears multiple times. |
| + """ |
| + r = [] |
| + errors = [] |
| + |
| + def lookup_subst(item, env=env, errors=errors): |
| + if SCons.Util.is_String(item): |
| + lookup_item = env.subst(item) |
| + else: |
| + lookup_item = item |
| + try: |
| + return Lookup(lookup_item) |
| + except SCons.Errors.UserError: |
| + raise LookupError(item, lookup_item) |
| + |
| + # Walk the entry tree and collect all the folders and projects. |
| + all_entries = [] |
| + entries_to_check = self.entries[:] |
| + while entries_to_check: |
| + # Pop from the beginning of the list to preserve the user's order. |
| + entry = entries_to_check.pop(0) |
| + try: |
| + entry = lookup_subst(entry) |
| + except LookupError, e: |
| + errors.append("Could not look up entry `%s'." % e) |
| + continue |
| + |
| + # A project or folder can only appear once in the solution's folder tree. |
| + # This also protects from cycles. |
| + if entry in all_entries: |
| + #raise IndexError('Entry "%s" appears more than once in solution' % |
| + # e.name) |
| + continue |
| + |
| + all_entries.append(entry) |
| + |
| + # If this is a folder, check its entries too. |
| + if isinstance(entry, _MSVSFolder): |
| + entries_to_check += entry.entries |
| + |
| + # Header |
| + r.append('Microsoft Visual Studio Solution File, Format Version 9.00\n') |
| + r.append('# Visual Studio 2005\n') |
| + |
| + # Project entries |
| + for e in all_entries: |
| + r.append('Project("%s") = "%s", "%s", "%s"\n' % ( |
| + e.entry_type_guid, # Entry type GUID |
| + e.msvs_name, # Folder name |
| + e.get_msvs_path(self), # Folder name (again) |
| + e.get_guid(), # Entry GUID |
| + )) |
| + |
| + # TODO(rspangler): Need a way to configure this stuff |
| + if self.websiteProperties: |
|
Nicolas Sylvain
2008/12/17 00:49:27
Please don't add this stuff. It burns my eyes to s
|
| + r.append('\tProjectSection(WebsiteProperties) = preProject\n' |
| + '\t\tDebug.AspNetCompiler.Debug = "True"\n' |
| + '\t\tRelease.AspNetCompiler.Debug = "False"\n' |
| + '\tEndProjectSection\n') |
| + |
| + if isinstance(e, _MSVSFolder): |
| + if e.items: |
| + r.append('\tProjectSection(SolutionItems) = preProject\n') |
| + for i in e.items: |
| + i = i.replace('/', '\\') |
| + r.append('\t\t%s = %s\n' % (i, i)) |
| + r.append('\tEndProjectSection\n') |
| + |
| + if isinstance(e, _MSVSProject): |
| + if e.dependencies: |
| + r.append('\tProjectSection(ProjectDependencies) = postProject\n') |
| + for d in e.dependencies: |
| + try: |
| + d = lookup_subst(d) |
| + except LookupError, e: |
| + errors.append("Could not look up dependency `%s'." % e) |
| + else: |
| + r.append('\t\t%s = %s\n' % (d.get_guid(), d.get_guid())) |
| + r.append('\tEndProjectSection\n') |
| + |
| + r.append('EndProject\n') |
| + |
| + # Global section |
| + r.append('Global\n') |
| + |
| + # Configurations (variants) |
| + r.append('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') |
| + for v in self.variants: |
| + r.append('\t\t%s = %s\n' % (v, v)) |
| + r.append('\tEndGlobalSection\n') |
| + |
| + # Sort config guids for easier diffing of solution changes. |
| + config_guids = [] |
| + for e in all_entries: |
| + if isinstance(e, _MSVSProject): |
| + config_guids.append(e.get_guid()) |
| + config_guids.sort() |
| + |
| + r.append('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') |
| + for g in config_guids: |
| + for v in self.variants: |
| + r.append('\t\t%s.%s.ActiveCfg = %s\n' % ( |
| + g, # Project GUID |
| + v, # Solution build configuration |
| + v, # Project build config for that solution config |
| + )) |
| + |
| + # Enable project in this solution configuratation |
| + r.append('\t\t%s.%s.Build.0 = %s\n' % ( |
| + g, # Project GUID |
| + v, # Solution build configuration |
| + v, # Project build config for that solution config |
| + )) |
| + r.append('\tEndGlobalSection\n') |
| + |
| + # TODO(rspangler): Should be able to configure this stuff too (though I've |
| + # never seen this be any different) |
| + r.append('\tGlobalSection(SolutionProperties) = preSolution\n') |
| + r.append('\t\tHideSolutionNode = FALSE\n') |
| + r.append('\tEndGlobalSection\n') |
| + |
| + # Folder mappings |
| + # TODO(rspangler): Should omit this section if there are no folders |
| + folder_mappings = [] |
| + for e in all_entries: |
| + if not isinstance(e, _MSVSFolder): |
| + continue # Does not apply to projects, only folders |
| + for subentry in e.entries: |
| + try: |
| + subentry = lookup_subst(subentry) |
| + except LookupError, e: |
| + errors.append("Could not look up subentry `%s'." % subentry) |
| + else: |
| + folder_mappings.append((subentry.get_guid(), e.get_guid())) |
| + folder_mappings.sort() |
| + r.append('\tGlobalSection(NestedProjects) = preSolution\n') |
| + for fm in folder_mappings: |
| + r.append('\t\t%s = %s\n' % fm) |
| + r.append('\tEndGlobalSection\n') |
| + |
| + r.append('EndGlobal\n') |
| + |
| + if errors: |
| + errors = ['Errors while generating solution file:'] + errors |
| + raise SCons.Errors.UserError, '\n\t'.join(errors) |
| + |
| + f = open(self.path, 'wt') |
| + f.write(''.join(r)) |
| + f.close() |
| + |
| +def MSVSSolution(env, item, *args, **kw): |
| + if not SCons.Util.is_String(item): |
| + return item |
| + item = env.subst(item) |
| + result = env.fs._lookup(item, None, _MSVSSolution, create=1) |
| + result.initialize(env, item, *args, **kw) |
| + LookupAdd(item, result) |
| + return result |
| Property changes on: site_scons\site_tools\_Node_MSVS.py |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |