1 // ****************************************************************
\r
2 // Copyright 2002-2003, Charlie Poole
\r
3 // This is free software licensed under the NUnit license. You may
\r
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
\r
5 // ****************************************************************
\r
10 using System.Text.RegularExpressions;
\r
12 namespace NUnit.Util
\r
15 /// This class allows loading information about
\r
16 /// configurations and assemblies in a Visual
\r
17 /// Studio project file and inspecting them.
\r
18 /// Only the most common project types are
\r
19 /// supported and an exception is thrown if
\r
20 /// an attempt is made to load an invalid
\r
21 /// file or one of an unknown type.
\r
23 public class VSProject
\r
25 #region Static and Instance Variables
\r
28 /// VS Project extentions
\r
30 private static readonly string[] validExtensions = { ".csproj", ".vbproj", ".vjsproj", ".vcproj" };
\r
33 /// VS Solution extension
\r
35 private static readonly string solutionExtension = ".sln";
\r
38 /// Path to the file storing this project
\r
40 private string projectPath;
\r
43 /// Collection of configs for the project
\r
45 private VSProjectConfigCollection configs;
\r
51 public VSProject( string projectPath )
\r
53 this.projectPath = Path.GetFullPath( projectPath );
\r
54 configs = new VSProjectConfigCollection();
\r
64 /// The name of the project.
\r
68 get { return Path.GetFileNameWithoutExtension( projectPath ); }
\r
72 /// The path to the project
\r
74 public string ProjectPath
\r
76 get { return projectPath; }
\r
80 /// Our collection of configurations
\r
82 public VSProjectConfigCollection Configs
\r
84 get { return configs; }
\r
89 #region Static Methods
\r
91 public static bool IsProjectFile( string path )
\r
93 if ( path.IndexOfAny( Path.InvalidPathChars ) >= 0 )
\r
96 if ( path.ToLower().IndexOf( "http:" ) >= 0 )
\r
99 string extension = Path.GetExtension( path );
\r
101 foreach( string validExtension in validExtensions )
\r
102 if ( extension == validExtension )
\r
108 public static bool IsSolutionFile( string path )
\r
110 return Path.GetExtension( path ) == solutionExtension;
\r
115 #region Instance Methods
\r
117 private void Load()
\r
119 if ( !IsProjectFile( projectPath ) )
\r
120 ThrowInvalidFileType( projectPath );
\r
122 string projectDirectory = Path.GetFullPath( Path.GetDirectoryName( projectPath ) );
\r
123 StreamReader rdr = new StreamReader( projectPath, System.Text.Encoding.UTF8 );
\r
124 string[] extensions = {"", ".exe", ".dll", ".lib", "" };
\r
128 XmlDocument doc = new XmlDocument();
\r
131 string extension = Path.GetExtension( projectPath );
\r
132 string assemblyName = null;
\r
134 switch ( extension )
\r
137 XmlNode topNode = doc.SelectSingleNode( "/VisualStudioProject" );
\r
139 // TODO: This is all very hacked up... replace it.
\r
140 foreach ( XmlNode configNode in doc.SelectNodes( "/VisualStudioProject/Configurations/Configuration" ) )
\r
142 string name = RequiredAttributeValue( configNode, "Name" );
\r
143 int config_type = System.Convert.ToInt32(RequiredAttributeValue(configNode, "ConfigurationType" ) );
\r
144 string dirName = name;
\r
145 int bar = dirName.IndexOf( '|' );
\r
147 dirName = dirName.Substring( 0, bar );
\r
148 string outputPath = RequiredAttributeValue( configNode, "OutputDirectory" );
\r
149 outputPath = outputPath.Replace( "$(SolutionDir)", Path.GetFullPath( Path.GetDirectoryName( projectPath ) ) + Path.DirectorySeparatorChar );
\r
150 outputPath = outputPath.Replace( "$(ConfigurationName)", dirName );
\r
152 string outputDirectory = Path.Combine( projectDirectory, outputPath );
\r
153 XmlNode toolNode = configNode.SelectSingleNode( "Tool[@Name='VCLinkerTool']" );
\r
154 if ( toolNode != null )
\r
156 assemblyName = SafeAttributeValue( toolNode, "OutputFile" );
\r
157 if ( assemblyName != null )
\r
158 assemblyName = Path.GetFileName( assemblyName );
\r
160 assemblyName = Path.GetFileNameWithoutExtension(projectPath) + extensions[config_type];
\r
164 toolNode = configNode.SelectSingleNode( "Tool[@Name='VCNMakeTool']" );
\r
165 if ( toolNode != null )
\r
166 assemblyName = Path.GetFileName( RequiredAttributeValue( toolNode, "Output" ) );
\r
169 assemblyName = assemblyName.Replace( "$(OutDir)", outputPath );
\r
170 assemblyName = assemblyName.Replace( "$(ProjectName)", this.Name );
\r
172 VSProjectConfig config = new VSProjectConfig ( name );
\r
173 if ( assemblyName != null )
\r
174 config.Assemblies.Add( Path.Combine( outputDirectory, assemblyName ) );
\r
176 this.configs.Add( config );
\r
184 LoadProject( projectDirectory, doc );
\r
191 catch( FileNotFoundException )
\r
195 catch( Exception e )
\r
197 ThrowInvalidFormat( projectPath, e );
\r
205 private bool LoadProject(string projectDirectory, XmlDocument doc)
\r
207 bool loaded = LoadVS2003Project(projectDirectory, doc);
\r
208 if (loaded) return true;
\r
210 loaded = LoadMSBuildProject(projectDirectory, doc);
\r
211 if (loaded) return true;
\r
216 private bool LoadVS2003Project(string projectDirectory, XmlDocument doc)
\r
218 XmlNode settingsNode = doc.SelectSingleNode("/VisualStudioProject/*/Build/Settings");
\r
219 if (settingsNode == null)
\r
222 string assemblyName = RequiredAttributeValue( settingsNode, "AssemblyName" );
\r
223 string outputType = RequiredAttributeValue( settingsNode, "OutputType" );
\r
225 if (outputType == "Exe" || outputType == "WinExe")
\r
226 assemblyName = assemblyName + ".exe";
\r
228 assemblyName = assemblyName + ".dll";
\r
230 XmlNodeList nodes = settingsNode.SelectNodes("Config");
\r
232 foreach (XmlNode configNode in nodes)
\r
234 string name = RequiredAttributeValue( configNode, "Name" );
\r
235 string outputPath = RequiredAttributeValue( configNode, "OutputPath" );
\r
236 string outputDirectory = Path.Combine(projectDirectory, outputPath);
\r
237 string assemblyPath = Path.Combine(outputDirectory, assemblyName);
\r
239 VSProjectConfig config = new VSProjectConfig(name);
\r
240 config.Assemblies.Add(assemblyPath);
\r
242 configs.Add(config);
\r
248 private bool LoadMSBuildProject(string projectDirectory, XmlDocument doc)
\r
250 XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
\r
251 namespaceManager.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
\r
253 XmlNodeList nodes = doc.SelectNodes("/msbuild:Project/msbuild:PropertyGroup", namespaceManager);
\r
254 if (nodes == null) return false;
\r
256 XmlElement assemblyNameElement = (XmlElement)doc.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:AssemblyName", namespaceManager);
\r
257 string assemblyName = assemblyNameElement.InnerText;
\r
259 XmlElement outputTypeElement = (XmlElement)doc.SelectSingleNode("/msbuild:Project/msbuild:PropertyGroup/msbuild:OutputType", namespaceManager);
\r
260 string outputType = outputTypeElement.InnerText;
\r
262 if (outputType == "Exe" || outputType == "WinExe")
\r
263 assemblyName = assemblyName + ".exe";
\r
265 assemblyName = assemblyName + ".dll";
\r
267 foreach (XmlElement configNode in nodes)
\r
269 if (configNode.Name != "PropertyGroup")
\r
272 XmlAttribute conditionAttribute = configNode.Attributes["Condition"];
\r
273 if (conditionAttribute == null) continue;
\r
275 string condition = conditionAttribute.Value;
\r
276 int start = condition.IndexOf( "==" );
\r
277 if ( start < 0 ) continue;
\r
279 string configurationName = condition.Substring( start + 2 ).Trim(new char[] {' ', '\'' } );
\r
280 if ( configurationName.EndsWith( "|AnyCPU" ) )
\r
281 configurationName = configurationName.Substring( 0, configurationName.Length - 7 );
\r
283 XmlElement outputPathElement = (XmlElement)configNode.SelectSingleNode("msbuild:OutputPath", namespaceManager);
\r
284 string outputPath = outputPathElement.InnerText;
\r
286 string outputDirectory = Path.Combine(projectDirectory, outputPath);
\r
287 string assemblyPath = Path.Combine(outputDirectory, assemblyName);
\r
289 VSProjectConfig config = new VSProjectConfig(configurationName);
\r
290 config.Assemblies.Add(assemblyPath);
\r
292 configs.Add(config);
\r
298 private void ThrowInvalidFileType(string projectPath)
\r
300 throw new ArgumentException(
\r
301 string.Format( "Invalid project file type: {0}",
\r
302 Path.GetFileName( projectPath ) ) );
\r
305 private void ThrowInvalidFormat( string projectPath, Exception e )
\r
307 throw new ArgumentException(
\r
308 string.Format( "Invalid project file format: {0}",
\r
309 Path.GetFileName( projectPath ) ), e );
\r
312 private string SafeAttributeValue( XmlNode node, string attrName )
\r
314 XmlNode attrNode = node.Attributes[attrName];
\r
315 return attrNode == null ? null : attrNode.Value;
\r
318 private string RequiredAttributeValue( XmlNode node, string name )
\r
320 string result = SafeAttributeValue( node, name );
\r
321 if ( result != null )
\r
324 throw new ApplicationException( "Missing required attribute " + name );
\r