// **************************************************************** // Copyright 2002-2003, Charlie Poole // This is free software licensed under the NUnit license. You may // obtain a copy of the license at http://nunit.org/?p=license&r=2.4 // **************************************************************** using System; using System.IO; using System.Xml; using System.Text.RegularExpressions; namespace NUnit.Util { /// /// This class allows loading information about /// configurations and assemblies in a Visual /// Studio project file and inspecting them. /// Only the most common project types are /// supported and an exception is thrown if /// an attempt is made to load an invalid /// file or one of an unknown type. /// public class VSProject { #region Static and Instance Variables /// /// VS Project extentions /// private static readonly string[] validExtensions = { ".csproj", ".vbproj", ".vjsproj", ".vcproj" }; /// /// VS Solution extension /// private static readonly string solutionExtension = ".sln"; /// /// Path to the file storing this project /// private string projectPath; /// /// Collection of configs for the project /// private VSProjectConfigCollection configs; #endregion #region Constructor public VSProject( string projectPath ) { this.projectPath = Path.GetFullPath( projectPath ); configs = new VSProjectConfigCollection(); Load(); } #endregion #region Properties /// /// The name of the project. /// public string Name { get { return Path.GetFileNameWithoutExtension( projectPath ); } } /// /// The path to the project /// public string ProjectPath { get { return projectPath; } } /// /// Our collection of configurations /// public VSProjectConfigCollection Configs { get { return configs; } } #endregion #region Static Methods public static bool IsProjectFile( string path ) { if ( path.IndexOfAny( Path.InvalidPathChars ) >= 0 ) return false; if ( path.ToLower().IndexOf( "http:" ) >= 0 ) return false; string extension = Path.GetExtension( path ); foreach( string validExtension in validExtensions ) if ( extension == validExtension ) return true; return false; } public static bool IsSolutionFile( string path ) { return Path.GetExtension( path ) == solutionExtension; } #endregion #region Instance Methods private void Load() { if ( !IsProjectFile( projectPath ) ) ThrowInvalidFileType( projectPath ); string projectDirectory = Path.GetFullPath( Path.GetDirectoryName( projectPath ) ); StreamReader rdr = new StreamReader( projectPath, System.Text.Encoding.UTF8 ); string[] extensions = {"", ".exe", ".dll", ".lib", "" }; try { XmlDocument doc = new XmlDocument(); doc.Load( rdr ); string extension = Path.GetExtension( projectPath ); string assemblyName = null; switch ( extension ) { case ".vcproj": XmlNode topNode = doc.SelectSingleNode( "/VisualStudioProject" ); // TODO: This is all very hacked up... replace it. foreach ( XmlNode configNode in doc.SelectNodes( "/VisualStudioProject/Configurations/Configuration" ) ) { string name = RequiredAttributeValue( configNode, "Name" ); int config_type = System.Convert.ToInt32(RequiredAttributeValue(configNode, "ConfigurationType" ) ); string dirName = name; int bar = dirName.IndexOf( '|' ); if ( bar >= 0 ) dirName = dirName.Substring( 0, bar ); string outputPath = RequiredAttributeValue( configNode, "OutputDirectory" ); outputPath = outputPath.Replace( "$(SolutionDir)", Path.GetFullPath( Path.GetDirectoryName( projectPath ) ) + Path.DirectorySeparatorChar ); outputPath = outputPath.Replace( "$(ConfigurationName)", dirName ); string outputDirectory = Path.Combine( projectDirectory, outputPath ); XmlNode toolNode = configNode.SelectSingleNode( "Tool[@Name='VCLinkerTool']" ); if ( toolNode != null ) { assemblyName = SafeAttributeValue( toolNode, "OutputFile" ); if ( assemblyName != null ) assemblyName = Path.GetFileName( assemblyName ); else assemblyName = Path.GetFileNameWithoutExtension(projectPath) + extensions[config_type]; } else { toolNode = configNode.SelectSingleNode( "Tool[@Name='VCNMakeTool']" ); if ( toolNode != null ) assemblyName = Path.GetFileName( RequiredAttributeValue( toolNode, "Output" ) ); } assemblyName = assemblyName.Replace( "$(OutDir)", outputPath ); assemblyName = assemblyName.Replace( "$(ProjectName)", this.Name ); VSProjectConfig config = new VSProjectConfig ( name ); if ( assemblyName != null ) config.Assemblies.Add( Path.Combine( outputDirectory, assemblyName ) ); this.configs.Add( config ); } break; case ".csproj": case ".vbproj": case ".vjsproj": LoadProject( projectDirectory, doc ); break; default: break; } } catch( FileNotFoundException ) { throw; } catch( Exception e ) { ThrowInvalidFormat( projectPath, e ); } finally { rdr.Close(); } } private bool LoadProject(string projectDirectory, XmlDocument doc) { bool loaded = LoadVS2003Project(projectDirectory, doc); if (loaded) return true; loaded = LoadMSBuildProject(projectDirectory, doc); if (loaded) return true; return false; } private bool LoadVS2003Project(string projectDirectory, XmlDocument doc) { XmlNode settingsNode = doc.SelectSingleNode("/VisualStudioProject/*/Build/Settings"); if (settingsNode == null) return false; string assemblyName = RequiredAttributeValue( settingsNode, "AssemblyName" ); string outputType = RequiredAttributeValue( settingsNode, "OutputType" ); if (outputType == "Exe" || outputType == "WinExe") assemblyName = assemblyName + ".exe"; else assemblyName = assemblyName + ".dll"; XmlNodeList nodes = settingsNode.SelectNodes("Config"); if (nodes != null) foreach (XmlNode configNode in nodes) { string name = RequiredAttributeValue( configNode, "Name" ); string outputPath = RequiredAttributeValue( configNode, "OutputPath" ); string outputDirectory = Path.Combine(projectDirectory, outputPath); string assemblyPath = Path.Combine(outputDirectory, assemblyName); VSProjectConfig config = new VSProjectConfig(name); config.Assemblies.Add(assemblyPath); configs.Add(config); } return true; } private bool LoadMSBuildProject(string projectDirectory, XmlDocument doc) { XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable); namespaceManager.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003"); XmlNodeList nodes = doc.SelectNodes("/msbuild:Project/msbuild:PropertyGroup", namespaceManager); if (nodes == null) return false; XmlElement assemblyNameElement = (XmlElement)doc.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:AssemblyName", namespaceManager); string assemblyName = assemblyNameElement.InnerText; XmlElement outputTypeElement = (XmlElement)doc.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:OutputType", namespaceManager); string outputType = outputTypeElement.InnerText; if (outputType == "Exe" || outputType == "WinExe") assemblyName = assemblyName + ".exe"; else assemblyName = assemblyName + ".dll"; foreach (XmlElement configNode in nodes) { if (configNode.Name != "PropertyGroup") continue; XmlAttribute conditionAttribute = configNode.Attributes["Condition"]; if (conditionAttribute == null) continue; string condition = conditionAttribute.Value; int start = condition.IndexOf( "==" ); if ( start < 0 ) continue; string configurationName = condition.Substring( start + 2 ).Trim(new char[] {' ', '\'' } ); if ( configurationName.EndsWith( "|AnyCPU" ) ) configurationName = configurationName.Substring( 0, configurationName.Length - 7 ); XmlElement outputPathElement = (XmlElement)configNode.SelectSingleNode("msbuild:OutputPath", namespaceManager); string outputPath = outputPathElement.InnerText; string outputDirectory = Path.Combine(projectDirectory, outputPath); string assemblyPath = Path.Combine(outputDirectory, assemblyName); VSProjectConfig config = new VSProjectConfig(configurationName); config.Assemblies.Add(assemblyPath); configs.Add(config); } return true; } private void ThrowInvalidFileType(string projectPath) { throw new ArgumentException( string.Format( "Invalid project file type: {0}", Path.GetFileName( projectPath ) ) ); } private void ThrowInvalidFormat( string projectPath, Exception e ) { throw new ArgumentException( string.Format( "Invalid project file format: {0}", Path.GetFileName( projectPath ) ), e ); } private string SafeAttributeValue( XmlNode node, string attrName ) { XmlNode attrNode = node.Attributes[attrName]; return attrNode == null ? null : attrNode.Value; } private string RequiredAttributeValue( XmlNode node, string name ) { string result = SafeAttributeValue( node, name ); if ( result != null ) return result; throw new ApplicationException( "Missing required attribute " + name ); } #endregion } }