114 votes

Analyse des fichiers de solutions Visual Studio

Comment analyser les fichiers de solutions Visual Studio (SLN) en .NET ? J'aimerais écrire une application qui fusionne plusieurs solutions en une seule tout en conservant l'ordre de construction relatif.

118voto

John Leidegren Points 21951

La version .NET 4.0 de l'assemblage Microsoft.Build contient une classe SolutionParser dans l'espace de noms Microsoft.Build.Construction qui analyse les fichiers de solution Visual Studio.

Malheureusement cette classe est interne, mais j'ai enveloppé une partie de cette fonctionnalité dans une classe qui utilise la réflexion pour obtenir certaines propriétés communes que vous pourriez trouver utiles.

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();
    }
}

Notez que vous devez changer votre cadre cible en ".NET Framework 4" (pas le profil client) pour pouvoir ajouter la référence Microsoft.Build à votre projet.

0 votes

Existe-t-il quelque chose comme cela pour MSBuild 3.5 - Visual Studio 2008 ?

0 votes

Je ne cherchais pas mais je n'ai rien trouvé. Cependant, vous devriez être en mesure d'utiliser ce code avec la chaîne d'outils 3.5.

0 votes

Wow, merci. Quelqu'un a-t-il réussi à aller plus loin et à utiliser également le ProjectParser ? J'essaie d'analyser un gigantesque fichier de solution et de lister la TargetFrameworkVersion de chaque projet.

16voto

WiredWiz Points 180

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/ a été migré vers https://github.com/mtherien/slntools

L'une des fonctions de cet outil est de fusionner plusieurs solutions ensemble.

0 votes

Sur le fichier de solution que j'ai testé, ce slntools donnait en fait plus de détails que les librairies ReSharper.

15voto

Ohad Schneider Points 10485

JetBrains (les créateurs de Resharper) ont public sln dans leurs assemblages (sans réflexion). C'est probablement plus robuste que les solutions open source existantes suggérées ici (sans parler des bidouillages de ReGex). Tout ce que vous avez à faire est de

  • Télécharger le Outils de ligne de commande de ReSharper (gratuit).
  • Ajoutez les éléments suivants comme références à votre projet
    • 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(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));
                }
            }
        }
    }
}

5 votes

NOTE : A partir de ce post, les espaces de noms sont un peu différents [peut-être que JB a refacturé son matériel :) ] : ~JetBrains.Platform.ProjectModel ~JetBrains.Platform.Util ~JetBrains.Platform.Interop.WinApi

10voto

JaredPar Points 333733

Je ne peux pas vraiment vous proposer une bibliothèque et je pense qu'il n'en existe pas. Mais j'ai passé beaucoup de temps à manipuler des fichiers .sln dans des scénarios d'édition par 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 entièrement 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 très joli, mais c'est un moyen efficace de faire du traitement par lots.

0 votes

Oui, c'est ce que j'ai fait jusqu'à présent.

0 votes

Pour exclure les dossiers de solutions, cela fonctionne pour moi : (Get-Content MySolution.sln) | Where-Object { $_ -match '(?=^Project(?!\("\{2150E333-8FDC-42A3-9474-1A3956D46DE8\}"\)))^(( \w +)' } | ForEach-Object { $_ -match ".*=(.*)$" | out-null ; $matches[1] } | ForEach-Object { $_.Split(",")[1].Trim().Trim('"') }

6voto

Andy Lowry Points 847

Nous avons résolu un problème similaire de fusion automatique des solutions en écrivant un plugin Visual Studio qui crée une nouvelle solution puis recherche les fichiers *.sln et les importe dans la nouvelle solution en utilisant :

dte2.Solution.AddFromFile(solutionPath, false);

Notre problème était légèrement différent dans la mesure où nous voulions que VS détermine l'ordre de construction pour nous, nous avons donc converti toutes les références de dll en références de projet lorsque cela était possible.

Nous avons ensuite automatisé ce processus de construction en exécutant VS via COM automation.

Cette solution était un peu "Heath Robinson", mais avait l'avantage que VS faisait l'édition et que notre code ne dépendait pas du format du fichier sln. Ce qui a été utile lorsque nous sommes passés de VS 2005 à 2008, puis à 2010.

0 votes

Avez-vous eu des problèmes de performance en utilisant DTE et si oui, comment les avez-vous résolus ? stackoverflow.com/questions/1620199/

0 votes

@mouters Nous avons constaté que l'utilisation du DTE et de l'interface était à peu près la même en termes de performances. L'installation des options VS minimales a aidé un peu, mais pas beaucoup. Comme il s'agit d'un processus automatisé qui fonctionne pendant la nuit, les performances ne sont pas un problème pour nous.

0 votes

Si sln a le dossier qui inclut le projet, vous ne pouvez pas obtenir le projet qui est inclus dans le dossier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X