AddDocumentCzęść NR.6Rolsyn API umożliwia z poziomu C# na kompilowanie kodu. Roslyn także zawiera klasy, które potrafią odwzorowywać projekty w solucji tak w Visual Studio.

W poprzednim wpisie opisałem jak użyć Workspace API, ale pracując z tymi klasami zauważyłem, że wiele rzeczy po prostu nie działa.

Po pierwsze wiele rzeczy obecnie nie jest wspieranych w tym API mimo iż metody istnieją.

Człowiek powinien być trochę zawiedziony, ale z drugiej strony Roslyn istnieje wciąż wersji beta.

Twórcy tych API nawet dodali metodę “CanApplyChange”, która potrafi wyświetlić co obecnie jest możliwe.

public static void WhatCanYouDo()
{
    var workspace = MSBuildWorkspace.Create();

    foreach (ApplyChangesKind changes in (ApplyChangesKind[])
        Enum.GetValues(typeof(ApplyChangesKind)))
    {
        if (workspace.CanApplyChange(changes))
            Console.ForegroundColor = ConsoleColor.Green;
        else
            Console.ForegroundColor = ConsoleColor.Gray;

        Console.WriteLine(changes.ToString() + "  " 
            + workspace.CanApplyChange(changes).ToString());
    }

    Console.ReadKey();
}

Przykładowo więc ten kod nie dodaje referencji obecnie. Kto wie w przyszłości będzie on działa.  Kod ten do  projektu “ConsoleApplication1”  ma dodać referencje do projektu “ClassLibrary1”, który też istnieje w solucji.

public static void AddReference()
{
    var solution= MSBuildWorkspace.Create();
    var sln = solution.OpenSolutionAsync
        (@"D:\PanNiebieski\Documents\visual studio 14\Projects\ConsoleApplicationWorkspaces"
        + @"\ConsoleApplicationWorkspaces.sln").Result;

    var project = sln.Projects.Where(k => k.Name != "ClassLibrary1");

    var classlibray = sln.Projects.First(k => k.Name == "ClassLibrary1").Id;
    var reference  = new ProjectReference(classlibray);

    Project p = sln.Projects.First(k => k.Name == "ConsoleApplication1");

    Project newproject = p.AddProjectReference(reference);

    var newSolution = newproject.Solution;

    var ok = solution.TryApplyChanges(newSolution);

    Console.WriteLine(p.Name + ":"+ok);
    Console.ReadKey();
}

Obecnie pisząc ten wpis jedyna operacja, która jest możliwa na solucjach i projektach to dodawanie/zmienianie/usuwanie dokumentów.

Co działa

Osobiście na początku miałem  nawet wątpliwości czy opcja też działa.

Poniższy kod nie będzie działać. Dlaczego?

public static void AddDoc()
{
    var solution = MSBuildWorkspace.Create();

    var sln = solution.OpenSolutionAsync
        (@"D:\PanNiebieski\Documents\Visual Studio 14\Projects\WebApplication1"
        + @"\WebApplication1.sln").Result;

    foreach (var p in sln.Projects)
    {
        p.AddDocument("ttt"+ ".cs", "public class gg { }  ");

        var ok = solution.TryApplyChanges(sln);

        Console.WriteLine(p.Name + ":" + ok);
    }

    Console.ReadKey();
}

Ponieważ każda operacja na projekcie tworzy nowy obiekt projektu i solucji. Stary obiekt projektu i solucji nie ma tych zmian. Podobna sprawa jest z napisami string.

Obiekty Syntax Tree i projekty,solucje w Roslyn APi są obiektami niezmiennymi (immutable*).

Co może wydawać się nie intuicyjnie zwłaszcza, że metoda AddDocument zwraca dokument.

Na podstawie jednak zwróconego dokumentu możemy uzyskać nowy obiekt projektu a z niego nowy obiekt solucji.

Na nowym obiekcie wykonujemy operacje zapisywania zmian.

public static void AddDoc()
{
    var workspace = MSBuildWorkspace.Create();

    var sln = workspace.OpenSolutionAsync
        (@"D:\PanNiebieski\Documents\visual studio 14\Projects\WebApplication1"
        + @"\WebApplication1.sln").Result;

    foreach (var p in sln.Projects)
    {
        Document doc = p.AddDocument(Guid.NewGuid().ToString(), "  ");

        var newsolutionThatWasCreated = doc.Project.Solution;

        var ok = workspace.TryApplyChanges(newsolutionThatWasCreated);

        Console.WriteLine(p.Name + ":" + ok);
    }

    Console.ReadKey();
}

Problemy nie kończą się tutaj. Do pierwszego projektu dodałem plik, ale już do następnych już nie.

W czym jest problem?

Problem

Otóż jak zajrzymy do wewnątrz metod “TryApplyChanges” zauważymy, że zmiany są odrzucane gdy obiekt solucji nie pochodzą od metody “MSBuildWorkspace.Create()”, a został on utworzony inaczej choćby pod wpływem zmiany.

Co to oznacza? Oznacza to, że można wprowadzić jedną zmianę, a potem trzeba pobrać obiekt solucji od nowa przy pomocy metody “MSBuildWorkspace.Create()”.

http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.Workspaces/Workspace/Workspace.cs,7d9370f0e9961b4c

TryApplyChanges

Pętla foreach tutaj się nie przyda. Zakładając jednak, że liczba projektów po dodawaniu dokumentów się nie zmienia problem ten można rozwiązać tak:

public static void AddDocumentToEveryProjectInSolution()
{
    var workspace = MSBuildWorkspace.Create();

    var sln = workspace.OpenSolutionAsync
        (@"D:\PanNiebieski\Documents\visual studio 14\Projects\WebApplication1"
        + @"\WebApplication1.sln").Result;

    int number = sln.Projects.Count();

    for (int i = 0; i < number; i++)
    {
        AddDocument(i, sln, workspace);

        workspace = MSBuildWorkspace.Create();

        sln = workspace.OpenSolutionAsync
            (@"D:\PanNiebieski\Documents\visual studio 14\Projects\WebApplication1"
            + @"\WebApplication1.sln").Result;
    }


    Console.ReadKey();
}

public static void AddDocument(int numberof,Solution sln, MSBuildWorkspace workspace)
{
    Project p = sln.Projects.ToList()[numberof];
    Document doc = p.AddDocument(Guid.NewGuid().ToString(), "  ");

    var newsolutionThatWasCreated = doc.Project.Solution;

    var ok = workspace.TryApplyChanges(newsolutionThatWasCreated);

    Console.WriteLine(p.Name + ":" + ok);
}

Po każdej operacji dodania dokumentu . Solucja jest otwierana od nowa.

Jak widać metoda “AddDocument” działa.

AddDocument

Kompilowanie projektów w solucji

A co z kompilowaniem projektów w solucji. Otóż ten kod działa, ale istnieją pewne ograniczenia.

O co chodzi?

Spójrzmy na kod:

public static void EmitSoltuion()
{
    var msBuild = MSBuildWorkspace.Create();

    var sln = msBuild.OpenSolutionAsync
        (@"D:\PanNiebieski\Documents\visual studio 14\Projects\ConsoleApplicationWorkspaces"
        + @"\ConsoleApplicationWorkspaces.sln").Result;

    foreach (var item in sln.Projects)
    {
        EmitProject(item);
    }
    Console.ReadKey();
}
public static async Task EmitProject(Project proj)
{
    var c = await proj.GetCompilationAsync();

    var r = c.Emit("my" + proj.Name );

    Console.ForegroundColor = ConsoleColor.White;
    Console.WriteLine(r.Success + " " + proj.Name);

    if (!r.Success)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(r.Diagnostics.First(k => k.WarningLevel == 0));
    }
}

Ten kod utworzy odpowiednie biblioteki projektów znajdujących się w tej solucji.

Projekty

Na początku myślałem, że to nie działa wcale, ale sęk w tym, że obecnie nie każdy projekt może być skompilowany.

Największe problemy znajdują się w projektach, które zawierają pliki XAML.

Error

Pliki XAML obecnie nie są kompilowane z kodem w C# co znaczy, że API Roslyn w tym miejscu nie zadziała.

Inną łamiącą zagadką okazał się projekt konsolowy, który uruchamia cały proces kompilacyjny. Inna aplikacja konsolowa kompiluje się bez problemu, więc co jest nie tak z projektem konsolowym z bibliotekami Roslyn.

Problem nie leży w tym, że konsolowa jest uruchamiana.

Problem leży w bibliotekach Portable Class Library. Czyli tak przez biblioteki Roslyn konsolowa nie może być skompilowana. Jest to jednak bug.

Szczerze doceniam pracę “Kevina Pilch-Bisson”, który odpowiada na takie dziwne pytania na StackOverflow.

Kevina Pilch-Bisson

Nie zmienia to faktu, że API Workspace jest obecnie niekompletne.

Chociaż daje nadzieję na naprawdę porządne i pomocne narzędzie do zmiany solucji i projektów bez otwierania ich w Visual Studio.