Comment parsingr les fichiers de la solution Visual Studio (SLN) dans .NET? Je voudrais écrire une application qui fusionne plusieurs solutions en une tout en sauvegardant l’ordre de construction relatif.
Avec Visual Studio 2015, il existe désormais une classe SolutionFile
accessible au public qui peut être utilisée pour parsingr les fichiers de solution:
using Microsoft.Build.Construction; var _solutionFile = SolutionFile.Parse(path);
Cette classe se trouve dans l’ assembly Microsoft.Build.dll 14.0.0.0 . Dans mon cas, il était situé à:
C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll
Merci à Phil de l’ avoir signalé !
Je ne sais pas si quelqu’un cherche encore des solutions à ce problème, mais je suis tombé sur un projet qui semble faire exactement ce qu’il faut. https://slntools.codeplex.com/ L’une des fonctions de cet outil est de fusionner plusieurs solutions ensemble.
JetBrains (les créateurs de Resharper) ont des capacités d’parsing de sln publiques dans leurs assemblys (pas de reflection nécessaire). C’est probablement plus robuste que les solutions open source proposées ici (sans parler des hacks ReGex). Tout ce que vous avez à faire est de:
JetBrains.Platform.ProjectModel
JetBrains.Platform.Util
JetBrains.Platform.Interop.WinApi
La bibliothèque n’est pas documentée, mais Reflector (ou même dotPeek) est votre ami. Par exemple:
public static void PrintProjects(ssortingng 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(ssortingng.Join(",", kvpp.Value)); } } } } }
Je ne peux pas vraiment vous offrir une bibliothèque et je pense qu’il n’y en a pas une qui existe. Mais j’ai passé beaucoup de temps à jouer avec les fichiers .sln dans les scénarios d’édition de lots et j’ai trouvé que Powershell était un outil très utile pour cette tâche. Le format .SLN est assez simple et peut être presque complètement analysé avec quelques expressions rapides et sales. Par exemple
Fichiers de projet inclus.
gc ConsoleApplication30.sln | ? { $_ -match "^Project" } | %{ $_ -match ".*=(.*)$" | out-null ; $matches[1] } | %{ $_.Split(",")[1].Trim().Trim('"') }
Ce n’est pas toujours joli, mais c’est un moyen efficace de faire un traitement par lots.
Nous avons résolu un problème similaire de fusion automatique de solutions en écrivant un plugin Visual Studio qui créait une nouvelle solution, puis cherchait un fichier * .sln et les importait dans le nouveau en utilisant:
dte2.Solution.AddFromFile(solutionPath, false);
Notre problème était légèrement différent en ce sens que nous voulions que VS sortinge l’ordre de construction pour nous, nous avons donc converti toutes les références de DLL en références de projet, si possible.
Nous avons ensuite automatisé cela dans un processus de compilation en exécutant VS via l’automatisation COM.
Cette solution était un peu Heath Robinson, mais avait l’avantage de permettre à VS de procéder à l’édition. Notre code ne dépendait donc pas du format du fichier sln. Ce qui a été utile lorsque nous sums passés de VS 2005 à 2008 et de nouveau à 2010.
Tout est génial, mais je voulais aussi avoir la capacité de génération de sln – dans le snapshot de code ci-dessus, vous ne parsingz que les fichiers .sln – je voulais faire la même chose . De tels cas pourraient par exemple porter sur un même projet pour une plate-forme .NET différente. Pour l’instant, il ne s’agit que d’une nouvelle génération, mais plus tard, je l’étendrai également à des projets.
Je suppose que je voulais aussi démontrer la puissance des expressions régulières et des interfaces natives. (Petite quantité de code avec plus de fonctionnalités)
Mise à jour 4.1.2017 J’ai créé un repository svn séparé pour l’parsing de la solution .sln: https://sourceforge.net/p/syncproj/code/HEAD/tree/
Vous trouverez ci-dessous mon propre extrait de code (prédécesseur). Vous êtes libre d’utiliser l’un d’entre eux.
Il est possible que dans le futur, le code d’parsing de la solution basée sur svn soit également mis à jour avec les capacités de génération.
Mise à jour 4.2.2017 Le code source dans SVN prend également en charge la génération .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 ssortingng ParentProjectGuid; public ssortingng ProjectName; public ssortingng RelativePath; public ssortingng ProjectGuid; public ssortingng AsSlnSsortingng() { return "Project(\"" + ParentProjectGuid + "\") = \"" + ProjectName + "\", \"" + RelativePath + "\", \"" + ProjectGuid + "\""; } } /// /// .sln loaded into class. /// public class Solution { public List
J’ai expliqué que les classes MSBuild pouvaient être utilisées pour manipuler les structures sous-jacentes. J’aurai plus de code sur mon site web plus tard.
// 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 ssortingng solutionFileName; private List projects; public ssortingng Name { get { return Path.GetFileNameWithoutExtension(solutionFileName); } } public IEnumerable 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 ssortingng SolutionPath { get { var file = new FileInfo(solutionFileName); return file.DirectoryName; } } public VSSolution(ssortingng 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(); 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 items; public ssortingng Name { get; private set; } public ssortingng ProjectType { get; private set; } public ssortingng RelativePath { get; private set; } public ssortingng ProjectGuid { get; private set; } public ssortingng 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 Items { get { return items; } } public VSProject(VSSolution solution, object internalSolutionProject) { this.Name = s_ProjectInSolution_ProjectName.GetValue(internalSolutionProject, null) as ssortingng; this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(internalSolutionProject, null).ToSsortingng(); this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(internalSolutionProject, null) as ssortingng; this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(internalSolutionProject, null) as ssortingng; this.solution = solution; this.internalSolutionProject = internalSolutionProject; this.projectFileName = Path.Combine(solution.SolutionPath, this.RelativePath); items = new List(); 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 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 ssortingng 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 ssortingng ItemType { get; private set; } public ssortingng Include { get; private set; } public VSProjectItem(VSProject project, object internalProjectItem) { this.ItemType = s_ProjectItemElement_ItemType.GetValue(internalProjectItem, null) as ssortingng; this.Include = s_ProjectItemElement_Include.GetValue(internalProjectItem, null) as ssortingng; this.project = project; this.internalProjectItem = internalProjectItem; // todo - expand this if (this.ItemType == "Comstack" || 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 ssortingng Name { get { if (fileName != null) { var file = new FileInfo(fileName); return file.Name; } else { return this.Include; } } } } }
Réponse de @ john-leidegren est génial. Pour les versions antérieures à VS2015, cela est très utile. Mais il y avait une petite erreur, car le code pour récupérer les configurations manquait. Je voulais donc l’append, au cas où quelqu’un aurait du mal à utiliser ce code.
L’amélioration est très simple:
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" ssortingng // [ 1] "SolutionParserComments" ssortingng // [ 2] "SolutionParserErrorCodes" ssortingng // [ 3] "Version" ssortingng // [ 4] "ContainsWebProjects" ssortingng // [ 5] "ContainsWebDeploymentProjects" ssortingng // [ 6] "ProjectsInOrder" ssortingng // [ 7] "ProjectsByGuid" ssortingng // [ 8] "SolutionFile" ssortingng // [ 9] "SolutionFileDirectory" ssortingng // [10] "SolutionReader" ssortingng // [11] "Projects" ssortingng // [12] "SolutionConfigurations" ssortingng } } public List Projects { get; private set; } public List Configurations { get; private set; } //... //... //... no change in the rest of the code }
Comme aide supplémentaire, fournir un code simple pour parcourir les propriétés d’un System.Type
comme suggéré par @oasten.
public class GenHlp_PropBrowser { public static List PropNamesOfClass(object anObj) { return anObj == null ? null : PropNamesOfType(anObj.GetType()); } public static List PropNamesOfType(System.Type aTyp) { List retLst = new List (); foreach ( var p in aTyp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) ) { retLst.Add(p.Name); } return retLst; } }
Merci @John Leidegren, il offre un moyen efficace. J’écris une classe hlper car je ne peux pas utiliser son code qui ne trouve pas la s_SolutionParser_configurations
et les projets sans FullName.
Le code est dans github qui peut obtenir les projets avec le FullName.
Et le code ne peut pas obtenir SolutionConfiguration.
Mais lorsque vous devenez un vsx, le vs dira que vous ne pouvez pas trouver Microsoft.Build.dll
, alors vous pouvez essayer d’utiliser dte pour obtenir tous les projets.
Le code qui utilise dte pour obtenir tous les projets est en github
Voir: http://www.wwwlicious.com/2011/03/29/envdte-getting-all-projects-html/