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.
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?
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()”.
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.
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.
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.
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.
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.