parsingVisual Studio解决scheme文件
如何parsing.NET中的Visual Studio解决scheme(SLN)文件? 我想编写一个应用程序,将多个解决scheme合并为一个,同时保存相对的构build顺序。
Microsoft.Build程序集的.NET 4.0版本在parsingVisual Studio解决scheme文件的Microsoft.Build.Construction名称空间中包含一个SolutionParser类。
不幸的是,这个类是内部的,但是我已经在一个使用reflection的类中封装了一些function,以获得一些您可能会发现有用的常见属性。
public class Solution { //internal class SolutionParser //Name: Microsoft.Build.Construction.SolutionParser //Assembly: Microsoft.Build, Version=4.0.0.0 static readonly Type s_SolutionParser; static readonly PropertyInfo s_SolutionParser_solutionReader; static readonly MethodInfo s_SolutionParser_parseSolution; static readonly PropertyInfo s_SolutionParser_projects; static Solution() { s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); if (s_SolutionParser != null) { s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance); } } public List<SolutionProject> Projects { get; private set; } public Solution(string solutionFileName) { if (s_SolutionParser == null) { throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?"); } var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null); using (var streamReader = new StreamReader(solutionFileName)) { s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null); s_SolutionParser_parseSolution.Invoke(solutionParser, null); } var projects = new List<SolutionProject>(); var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null); for (int i = 0; i < array.Length; i++) { projects.Add(new SolutionProject(array.GetValue(i))); } this.Projects = projects; } } [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")] public class SolutionProject { static readonly Type s_ProjectInSolution; static readonly PropertyInfo s_ProjectInSolution_ProjectName; static readonly PropertyInfo s_ProjectInSolution_RelativePath; static readonly PropertyInfo s_ProjectInSolution_ProjectGuid; static readonly PropertyInfo s_ProjectInSolution_ProjectType; static SolutionProject() { s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); if (s_ProjectInSolution != null) { s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance); } } public string ProjectName { get; private set; } public string RelativePath { get; private set; } public string ProjectGuid { get; private set; } public string ProjectType { get; private set; } public SolutionProject(object solutionProject) { this.ProjectName = s_ProjectInSolution_ProjectName.GetValue(solutionProject, null) as string; this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(solutionProject, null) as string; this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(solutionProject, null) as string; this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(solutionProject, null).ToString(); } }
请注意,您必须将目标框架更改为“.NET Framework 4”(而不是客户端configuration文件)才能将Microsoft.Build引用添加到您的项目。
使用Visual Studio 2015,现在有一个可公开访问的SolutionFile
类,可用于parsing解决scheme文件:
using Microsoft.Build.Construction; var _solutionFile = SolutionFile.Parse(path);
这个类是在Microsoft.Build.dll 14.0.0.0程序集中find的。 在我的情况下,它位于:
C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll
感谢菲尔 指出这一点 !
我不知道是否有人还在寻找解决这个问题的办法,但是我碰到了一个似乎只是需要的项目。 https://slntools.codeplex.com/这个工具的function之一是将多个解决scheme合并在一起。;
JetBrains (Resharper的创build者)在其程序集中具有公共 slnparsing能力(不需要reflection)。 它可能比现有的开源解决scheme更强大(更不用说ReGex黑客)。 所有你需要做的是:
- 下载ReSharper命令行工具 (免费)。
- 添加以下作为您的项目的参考
-
JetBrains.Platform.ProjectModel
-
JetBrains.Platform.Util
-
JetBrains.Platform.Interop.WinApi
-
库没有logging,但reflection(或确实,dotPeek)是你的朋友。 例如:
public static void PrintProjects(string solutionPath) { var slnFile = SolutionFileParser.ParseFile(FileSystemPath.Parse(solutionPath)); foreach (var project in slnFile.Projects) { Console.WriteLine(project.ProjectName); Console.WriteLine(project.ProjectGuid); Console.WriteLine(project.ProjectTypeGuid); foreach (var kvp in project.ProjectSections) { Console.WriteLine(kvp.Key); foreach (var projectSection in kvp.Value) { Console.WriteLine(projectSection.SectionName); Console.WriteLine(projectSection.SectionValue); foreach (var kvpp in projectSection.Properties) { Console.WriteLine(kvpp.Key); Console.WriteLine(string.Join(",", kvpp.Value)); } } } } }
我真的不能给你一个图书馆,我的猜测是没有一个存在。 但是我在批处理编辑场景中花费了一些时间来处理.sln文件,我发现PowerShell是一个非常有用的工具。 .SLN格式非常简单,几乎可以用几个快速和脏的expression式来完全parsing。 例如
包含项目文件。
gc ConsoleApplication30.sln | ? { $_ -match "^Project" } | %{ $_ -match ".*=(.*)$" | out-null ; $matches[1] } | %{ $_.Split(",")[1].Trim().Trim('"') }
这并不总是漂亮,但它是批量处理的有效方法。
我们通过编写一个创build新解决scheme的Visual Studio插件,然后search* .sln文件并使用以下命令将它们导入到新的解决scheme中,从而自动解决了类似的解决scheme合并问题:
dte2.Solution.AddFromFile(solutionPath, false);
我们的问题稍微有点不同,因为我们希望VS能够为我们整理构build顺序,所以我们在可能的情况下将任何dll引用转换为项目引用。
然后,我们通过运行COM自动化来将VS自动化到构build过程中。
这个解决scheme有一点希思罗宾逊,但有优势,VS正在做编辑,所以我们的代码是不依赖于sln文件的格式。 从VS 2005到2008年再次到2010年,这是有帮助的。
一切都很好,但我也希望得到sln代的能力 – 在代码快照上面,你只是parsing.sln文件 – 我想做类似的事情,除了能够重新生成sln稍作修改回.sln文件。 这种情况可能是为不同的.NET平台移植同一个项目。 现在只是重新生成,但后来我将把它扩展到项目中。
我想我也想展示正则expression式和本地接口的力量。 (具有更多function的更less量的代码)
更新4.1.2017我创build了单独的svn仓库parsing.sln解决scheme: https : //sourceforge.net/p/syncproj/code/HEAD/tree/
以下是我自己的代码示例片段(前身)。 你可以自由使用它们中的任何一个。
未来基于svn的解决schemeparsing代码也有可能会随着生成function而更新。
更新4.2.2017 SVN的源代码也支持.sln生成。
using System; using System.Linq; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Text.RegularExpressions; using System.Text; public class Program { [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")] public class SolutionProject { public string ParentProjectGuid; public string ProjectName; public string RelativePath; public string ProjectGuid; public string AsSlnString() { return "Project(\"" + ParentProjectGuid + "\") = \"" + ProjectName + "\", \"" + RelativePath + "\", \"" + ProjectGuid + "\""; } } /// <summary> /// .sln loaded into class. /// </summary> public class Solution { public List<object> slnLines; // List of either String (line format is not intresting to us), or SolutionProject. /// <summary> /// Loads visual studio .sln solution /// </summary> /// <param name="solutionFileName"></param> /// <exception cref="System.IO.FileNotFoundException">The file specified in path was not found.</exception> public Solution( string solutionFileName ) { slnLines = new List<object>(); String slnTxt = File.ReadAllText(solutionFileName); string[] lines = slnTxt.Split('\n'); //Match string like: Project("{66666666-7777-8888-9999-AAAAAAAAAAAA}") = "ProjectName", "projectpath.csproj", "{11111111-2222-3333-4444-555555555555}" Regex projMatcher = new Regex("Project\\(\"(?<ParentProjectGuid>{[A-F0-9-]+})\"\\) = \"(?<ProjectName>.*?)\", \"(?<RelativePath>.*?)\", \"(?<ProjectGuid>{[A-F0-9-]+})"); Regex.Replace(slnTxt, "^(.*?)[\n\r]*$", new MatchEvaluator(m => { String line = m.Groups[1].Value; Match m2 = projMatcher.Match(line); if (m2.Groups.Count < 2) { slnLines.Add(line); return ""; } SolutionProject s = new SolutionProject(); foreach (String g in projMatcher.GetGroupNames().Where(x => x != "0")) /* "0" - RegEx special kind of group */ s.GetType().GetField(g).SetValue(s, m2.Groups[g].ToString()); slnLines.Add(s); return ""; }), RegexOptions.Multiline ); } /// <summary> /// Gets list of sub-projects in solution. /// </summary> /// <param name="bGetAlsoFolders">true if get also sub-folders.</param> public List<SolutionProject> GetProjects( bool bGetAlsoFolders = false ) { var q = slnLines.Where( x => x is SolutionProject ).Select( i => i as SolutionProject ); if( !bGetAlsoFolders ) // Filter away folder names in solution. q = q.Where( x => x.RelativePath != x.ProjectName ); return q.ToList(); } /// <summary> /// Saves solution as file. /// </summary> public void SaveAs( String asFilename ) { StringBuilder s = new StringBuilder(); for( int i = 0; i < slnLines.Count; i++ ) { if( slnLines[i] is String ) s.Append(slnLines[i]); else s.Append((slnLines[i] as SolutionProject).AsSlnString() ); if( i != slnLines.Count ) s.AppendLine(); } File.WriteAllText(asFilename, s.ToString()); } } static void Main() { String projectFile = @"yourown.sln"; try { String outProjectFile = Path.Combine(Path.GetDirectoryName(projectFile), Path.GetFileNameWithoutExtension(projectFile) + "_2.sln"); Solution s = new Solution(projectFile); foreach( var proj in s.GetProjects() ) { Console.WriteLine( proj.RelativePath ); } SolutionProject p = s.GetProjects().Where( x => x.ProjectName.Contains("Plugin") ).First(); p.RelativePath = Path.Combine( Path.GetDirectoryName(p.RelativePath) , Path.GetFileNameWithoutExtension(p.RelativePath) + "_Variation" + ".csproj"); s.SaveAs(outProjectFile); } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); } } }
我阐述了,确定MSBuild类可以用来操纵底层结构。 稍后,我将在我的网站上添加更多代码。
// VSSolution using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using System.IO; using AbstractX.Contracts; namespace VSProvider { public class VSSolution : IVSSolution { //internal class SolutionParser //Name: Microsoft.Build.Construction.SolutionParser //Assembly: Microsoft.Build, Version=4.0.0.0 static readonly Type s_SolutionParser; static readonly PropertyInfo s_SolutionParser_solutionReader; static readonly MethodInfo s_SolutionParser_parseSolution; static readonly PropertyInfo s_SolutionParser_projects; private string solutionFileName; private List<VSProject> projects; public string Name { get { return Path.GetFileNameWithoutExtension(solutionFileName); } } public IEnumerable<IVSProject> Projects { get { return projects; } } static VSSolution() { s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance); } public string SolutionPath { get { var file = new FileInfo(solutionFileName); return file.DirectoryName; } } public VSSolution(string solutionFileName) { if (s_SolutionParser == null) { throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?"); } var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null); using (var streamReader = new StreamReader(solutionFileName)) { s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null); s_SolutionParser_parseSolution.Invoke(solutionParser, null); } this.solutionFileName = solutionFileName; projects = new List<VSProject>(); var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null); for (int i = 0; i < array.Length; i++) { projects.Add(new VSProject(this, array.GetValue(i))); } } public void Dispose() { } } } // VSProject using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using System.IO; using System.Xml; using AbstractX.Contracts; using System.Collections; namespace VSProvider { [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")] public class VSProject : IVSProject { static readonly Type s_ProjectInSolution; static readonly Type s_RootElement; static readonly Type s_ProjectRootElement; static readonly Type s_ProjectRootElementCache; static readonly PropertyInfo s_ProjectInSolution_ProjectName; static readonly PropertyInfo s_ProjectInSolution_ProjectType; static readonly PropertyInfo s_ProjectInSolution_RelativePath; static readonly PropertyInfo s_ProjectInSolution_ProjectGuid; static readonly PropertyInfo s_ProjectRootElement_Items; private VSSolution solution; private string projectFileName; private object internalSolutionProject; private List<VSProjectItem> items; public string Name { get; private set; } public string ProjectType { get; private set; } public string RelativePath { get; private set; } public string ProjectGuid { get; private set; } public string FileName { get { return projectFileName; } } static VSProject() { s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance); s_ProjectRootElement = Type.GetType("Microsoft.Build.Construction.ProjectRootElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); s_ProjectRootElementCache = Type.GetType("Microsoft.Build.Evaluation.ProjectRootElementCache, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); s_ProjectRootElement_Items = s_ProjectRootElement.GetProperty("Items", BindingFlags.Public | BindingFlags.Instance); } public IEnumerable<IVSProjectItem> Items { get { return items; } } public VSProject(VSSolution solution, object internalSolutionProject) { this.Name = s_ProjectInSolution_ProjectName.GetValue(internalSolutionProject, null) as string; this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(internalSolutionProject, null).ToString(); this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(internalSolutionProject, null) as string; this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(internalSolutionProject, null) as string; this.solution = solution; this.internalSolutionProject = internalSolutionProject; this.projectFileName = Path.Combine(solution.SolutionPath, this.RelativePath); items = new List<VSProjectItem>(); if (this.ProjectType == "KnownToBeMSBuildFormat") { this.Parse(); } } private void Parse() { var stream = File.OpenRead(projectFileName); var reader = XmlReader.Create(stream); var cache = s_ProjectRootElementCache.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { true }); var rootElement = s_ProjectRootElement.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { reader, cache }); stream.Close(); var collection = (ICollection)s_ProjectRootElement_Items.GetValue(rootElement, null); foreach (var item in collection) { items.Add(new VSProjectItem(this, item)); } } public IEnumerable<IVSProjectItem> EDMXModels { get { return this.items.Where(i => i.ItemType == "EntityDeploy"); } } public void Dispose() { } } } // VSProjectItem using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using System.IO; using System.Xml; using AbstractX.Contracts; namespace VSProvider { [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")] public class VSProjectItem : IVSProjectItem { static readonly Type s_ProjectItemElement; static readonly PropertyInfo s_ProjectItemElement_ItemType; static readonly PropertyInfo s_ProjectItemElement_Include; private VSProject project; private object internalProjectItem; private string fileName; static VSProjectItem() { s_ProjectItemElement = Type.GetType("Microsoft.Build.Construction.ProjectItemElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); s_ProjectItemElement_ItemType = s_ProjectItemElement.GetProperty("ItemType", BindingFlags.Public | BindingFlags.Instance); s_ProjectItemElement_Include = s_ProjectItemElement.GetProperty("Include", BindingFlags.Public | BindingFlags.Instance); } public string ItemType { get; private set; } public string Include { get; private set; } public VSProjectItem(VSProject project, object internalProjectItem) { this.ItemType = s_ProjectItemElement_ItemType.GetValue(internalProjectItem, null) as string; this.Include = s_ProjectItemElement_Include.GetValue(internalProjectItem, null) as string; this.project = project; this.internalProjectItem = internalProjectItem; // todo - expand this if (this.ItemType == "Compile" || this.ItemType == "EntityDeploy") { var file = new FileInfo(project.FileName); fileName = Path.Combine(file.DirectoryName, this.Include); } } public byte[] FileContents { get { return File.ReadAllBytes(fileName); } } public string Name { get { if (fileName != null) { var file = new FileInfo(fileName); return file.Name; } else { return this.Include; } } } } }
通过回答 @ john-leidegren是伟大的。 对于VS2015以前的版本,这个function非常有用。 但是有一个小小的错误,因为检索configuration的代码丢失了。 所以想要添加它,以防有人正在努力使用此代码。
增强非常简单:
public class Solution { //internal class SolutionParser //Name: Microsoft.Build.Construction.SolutionParser //Assembly: Microsoft.Build, Version=4.0.0.0 static readonly Type s_SolutionParser; static readonly PropertyInfo s_SolutionParser_solutionReader; static readonly MethodInfo s_SolutionParser_parseSolution; static readonly PropertyInfo s_SolutionParser_projects; static readonly PropertyInfo s_SolutionParser_configurations;//this was missing in john's answer static Solution() { s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false); if ( s_SolutionParser != null ) { s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance); s_SolutionParser_configurations = s_SolutionParser.GetProperty("SolutionConfigurations", BindingFlags.NonPublic | BindingFlags.Instance); //this was missing in john's answer // additional info: var PropNameLst = GenHlp_PropBrowser.PropNamesOfType(s_SolutionParser); // the above call would yield something like this: // [ 0] "SolutionParserWarnings" string // [ 1] "SolutionParserComments" string // [ 2] "SolutionParserErrorCodes" string // [ 3] "Version" string // [ 4] "ContainsWebProjects" string // [ 5] "ContainsWebDeploymentProjects" string // [ 6] "ProjectsInOrder" string // [ 7] "ProjectsByGuid" string // [ 8] "SolutionFile" string // [ 9] "SolutionFileDirectory" string // [10] "SolutionReader" string // [11] "Projects" string // [12] "SolutionConfigurations" string } } public List<SolutionProject> Projects { get; private set; } public List<SolutionConfiguration> Configurations { get; private set; } //... //... //... no change in the rest of the code }
作为额外的帮助,提供简单的代码来浏览@oastenbuild议的System.Type
的属性。
public class GenHlp_PropBrowser { public static List<string> PropNamesOfClass(object anObj) { return anObj == null ? null : PropNamesOfType(anObj.GetType()); } public static List<String> PropNamesOfType(System.Type aTyp) { List<string> retLst = new List<string>(); foreach ( var p in aTyp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) ) { retLst.Add(p.Name); } return retLst; } }
感谢@John Leidegren他提供了一个有效的方法。 我写了一个hlper类,因为我不能使用他的代码找不到s_SolutionParser_configurations
和没有FullName的项目。
代码在github中 ,可以用FullName获得项目。
代码不能得到SolutionConfiguration。
但是当你开发一个vsx时,vs会说找不到Microsoft.Build.dll
,所以你可以尝试使用dte来获取所有的项目。
使用dte获取所有项目的代码在github中
请参阅: http : //www.wwwlicious.com/2011/03/29/envdte-getting-all-projects-html/