【发布时间】:2012-11-07 23:22:06
【问题描述】:
我有一个根据用户操作为 C# 项目生成代码的软件。我想创建一个 GUI 来自动编译解决方案,这样我就不必加载 Visual Studio 来触发重新编译。
我一直在寻找与 Roslyn 一起玩的机会,并决定尝试使用 Roslyn 而不是 msbuild 来执行此操作。不幸的是,我似乎找不到任何以这种方式使用 Roslyn 的好资源。
谁能指出我正确的方向?
【问题讨论】:
我有一个根据用户操作为 C# 项目生成代码的软件。我想创建一个 GUI 来自动编译解决方案,这样我就不必加载 Visual Studio 来触发重新编译。
我一直在寻找与 Roslyn 一起玩的机会,并决定尝试使用 Roslyn 而不是 msbuild 来执行此操作。不幸的是,我似乎找不到任何以这种方式使用 Roslyn 的好资源。
谁能指出我正确的方向?
【问题讨论】:
您可以使用Roslyn.Services.Workspace.LoadSolution 加载解决方案。完成此操作后,您需要按依赖顺序遍历每个项目,获取该项目的 Compilation 并在其上调用 Emit。
您可以使用下面的代码按依赖顺序获取编译。 (是的,我知道必须强制转换为 IHaveWorkspaceServices 很糟糕。我保证在下一个公开版本中会更好)。
using Roslyn.Services;
using Roslyn.Services.Host;
using System;
using System.Collections.Generic;
using System.IO;
class Program
{
static void Main(string[] args)
{
var solution = Solution.Create(SolutionId.CreateNewId()).AddCSharpProject("Foo", "Foo").Solution;
var workspaceServices = (IHaveWorkspaceServices)solution;
var projectDependencyService = workspaceServices.WorkspaceServices.GetService<IProjectDependencyService>();
var assemblies = new List<Stream>();
foreach (var projectId in projectDependencyService.GetDependencyGraph(solution).GetTopologicallySortedProjects())
{
using (var stream = new MemoryStream())
{
solution.GetProject(projectId).GetCompilation().Emit(stream);
assemblies.Add(stream);
}
}
}
}
注意 1:LoadSolution 仍然使用 msbuild 来解析 .csproj 文件并确定文件/引用/编译器选项。
注意 2:由于 Roslyn 尚未完成语言,因此在您尝试此操作时可能会有项目无法成功编译。
【讨论】:
sln.ProjectsInDependencyOrder),还是我必须自己实现? (例如,浏览项目引用并构建依赖树)
Roslyn.Services.Workspace.LoadSolution() 不存在,Solution.Create() 也不存在。我认为你必须使用MSBuildWorkspace.Create 和OpenSolutionAsync (对于现有的sln)或者只是workspace.CurrentSolution 用于空的。此外,您需要从microsoft.com/en-us/download/details.aspx?id=44931 安装 MSBuild 2015 工具才能使用 MSBuildWorkspace。或者,您可以 MEF 导出目录并使用 CustomWorkspace。整个过程似乎变得更加复杂
GetProjectDependencyGraph 方法,一旦你有了Solution 对象,它就可以完成所有这些工作。
我还想即时编译一个完整的解决方案。从Kevin Pilch-Bisson's answer 和Josh E's comment 构建,我编写了代码来编译自身并将其写入文件。
使用的软件
Visual Studio 社区 2015 更新 1
Microsoft.CodeAnalysis v1.1.0.0(使用包管理器控制台和命令Install-Package Microsoft.CodeAnalysis 安装)。
代码
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
namespace Roslyn.TryItOut
{
class Program
{
static void Main(string[] args)
{
string solutionUrl = "C:\\Dev\\Roslyn.TryItOut\\Roslyn.TryItOut.sln";
string outputDir = "C:\\Dev\\Roslyn.TryItOut\\output";
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
bool success = CompileSolution(solutionUrl, outputDir);
if (success)
{
Console.WriteLine("Compilation completed successfully.");
Console.WriteLine("Output directory:");
Console.WriteLine(outputDir);
}
else
{
Console.WriteLine("Compilation failed.");
}
Console.WriteLine("Press the any key to exit.");
Console.ReadKey();
}
private static bool CompileSolution(string solutionUrl, string outputDir)
{
bool success = true;
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
Dictionary<string, Stream> assemblies = new Dictionary<string, Stream>();
foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
{
Compilation projectCompilation = solution.GetProject(projectId).GetCompilationAsync().Result;
if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
{
using (var stream = new MemoryStream())
{
EmitResult result = projectCompilation.Emit(stream);
if (result.Success)
{
string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
using (FileStream file = File.Create(outputDir + '\\' + fileName))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
else
{
success = false;
}
}
}
else
{
success = false;
}
}
return success;
}
}
}
【讨论】:
Microsoft.Build.Locator并调用MSBuildLocator.RegisterDefaults()。根据我的经验,不这样做会导致Compilation 中没有任何框架引用。