Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(505)

Unified Diff: site_scons/site_tools/_Node_MSVS.py

Issue 14467: Underlying functionality for generating native Visual Studio solution files:... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 12 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « site_scons/site_tools/MSVSNew.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « site_scons/site_tools/MSVSNew.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698