using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; namespace Semmle.BuildAnalyser { /// /// Represents a .csproj file and reads information from it. /// class CsProjFile { /// /// Reads the .csproj file. /// /// The .csproj file. public CsProjFile(FileInfo filename) { try { // This can fail if the .csproj is invalid or has // unrecognised content or is the wrong version. // This currently always fails on Linux because // Microsoft.Build is not cross platform. ReadMsBuildProject(filename); } catch { // There was some reason why the project couldn't be loaded. // Fall back to reading the Xml document directly. // This method however doesn't handle variable expansion. ReadProjectFileAsXml(filename); } } /// /// Read the .csproj file using Microsoft Build. /// This occasionally fails if the project file is incompatible for some reason, /// and there seems to be no way to make it succeed. Fails on Linux. /// /// The file to read. public void ReadMsBuildProject(FileInfo filename) { var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName); references = msbuildProject. Items. Where(item => item.ItemType == "Reference"). Select(item => item.EvaluatedInclude). ToArray(); csFiles = msbuildProject.Items .Where(item => item.ItemType == "Compile") .Select(item => item.GetMetadataValue("FullPath")) .Where(fn => fn.EndsWith(".cs")) .ToArray(); } /// /// Reads the .csproj file directly as XML. /// This doesn't handle variables etc, and should only used as a /// fallback if ReadMsBuildProject() fails. /// /// The .csproj file. public void ReadProjectFileAsXml(FileInfo filename) { var projFile = new XmlDocument(); var mgr = new XmlNamespaceManager(projFile.NameTable); mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003"); projFile.Load(filename.FullName); var projDir = filename.Directory; var root = projFile.DocumentElement; references = root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr). NodeList(). Select(node => node.Value). ToArray(); var relativeCsIncludes = root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr). NodeList(). Select(node => node.Value). ToArray(); csFiles = relativeCsIncludes. Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs). Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))). ToArray(); } string[] references; string[] csFiles; /// /// The list of references as a list of assembly IDs. /// public IEnumerable References => references; /// /// The list of C# source files in full path format. /// public IEnumerable Sources => csFiles; } static class XmlNodeHelper { /// /// Helper to convert an XmlNodeList into an IEnumerable. /// This allows it to be used with Linq. /// /// The list to convert. /// A more useful data type. public static IEnumerable NodeList(this XmlNodeList list) { foreach (var i in list) yield return i as XmlNode; } } }