dotnet 通过引用 msbuild 程序集实现自己定制编译器
本來我想說的是基于引用 msbuild 程序集來自己做一個編譯器,但是想想好像本文做的,和造編譯器沒啥關系,咱自己調用 msbuild 的 API 而已。本文來告訴大家如何引用 msbuild 程序集,如何在自己的應用程序里面嵌入 msbuild 的構建代碼,實現 dotnet build 的效果
大部分的代碼都是采用命令行的方式去調用 dotnet build 或 msbuild 命令,然而通過命令行調用用的是跨進程的方式,如果期望做更多的定制化,最好還是放在相同的進程,此時可以更改構建的各個步驟
自己制作一個編譯器最簡單的方法就是引用現有的成熟的編譯器作為組件,剛好 msbuild 最新版本也是使用 dotnet 框架編寫的,咱的 dotnet 應用可以非常方便將 msbuild 引用進來。當然了,本文不討論如何自己發布 msbuild 的問題,因為這又是另一個坑了。本文的方法是引用本機已安裝好的 msbuild 程序集
在開始之前,請新建一個控制臺項目。當然了,你要是新建一個 WPF 項目也沒啥問題
編輯 csproj 文件,添加如下代碼
<ItemGroup><PackageReference Include="Microsoft.Build" Version="16.10.0" ExcludeAssets="runtime" /><PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10.0" ExcludeAssets="runtime" /><PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" /></ItemGroup>添加完成之后的 csproj 文件代碼如下
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net6.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Build" Version="16.10.0" ExcludeAssets="runtime" /><PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10.0" ExcludeAssets="runtime" /><PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" /></ItemGroup> </Project>第一步是先獲取本機已安裝好的 msbuild 實例,如下代碼
static void Main(string[] args){var instances = MSBuildLocator.QueryVisualStudioInstances().ToList();}以上代碼要能運行,需要加上如下命名空間
using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Locator;以上拿到的 instances 就是本機安裝的 msbuild 實例,也就是 dotnet sdk 的各個版本,可以使用如下代碼輸出
for (var i = 1; i <= instances.Count; i++){var instance = instances[i - 1];var recommended = string.Empty;// The dev console is probably always the right choice because the user explicitly opened// one associated with a Visual Studio install. It will always be first in the list.if (instance.DiscoveryType == DiscoveryType.DeveloperConsole)recommended = " (Recommended!)";Console.WriteLine($"{i}) {instance.Name} - {instance.Version}{recommended}");}下一步是有點黑科技的部分,也就是為什么我會寫本文的原因。使用下面代碼注冊 msbuild 實例,如果沒有使用下面這句代碼注冊,那么在后續調用 msbuild 相關類型時,將會因為找不到 msbuild 的程序集而失敗
// 必須調用 RegisterInstance 方法,否則將提示找不到 msbuild 文件MSBuildLocator.RegisterInstance(instances.First());注冊完成之后,將可以使用 msbuild 提供的各個類來實現構建,請新建一個方法用來編寫調用 msbuild 各個類的構建代碼。如以下代碼
private static void Build(){var projectFile = new FileInfo(@"..\..\..\RalboleaballNeeqaqiku.csproj");var projectRootElement = ProjectRootElement.Open(projectFile.FullName);var project = new Project(projectRootElement);project.Build(new Logger());}為什么需要這部分構建代碼放在另一個方法里面?原因是在碰到了 ProjectRootElement 類型的時候,就需要開始加載程序集,然而在調用 MSBuildLocator.RegisterInstance 之前,還是找不到程序集的哦。因此為了讓 MSBuildLocator.RegisterInstance 能被執行,就需要讓包含 MSBuildLocator.RegisterInstance 代碼的方法不會在執行之前碰到還沒有存在的程序集,因此就需要將碰到構建相關邏輯的代碼放在獨立的方法或者獨立的類型里面,這樣就能讓包含 MSBuildLocator.RegisterInstance 的代碼不會因為找不到程序集而不執行
以上代碼是通過調用 ProjectRootElement.Open 方法加載了 csproj 文件,此步驟是反序列化過程。接著新建 Project 實例,在新建方法里面將會進行初始化,可以拿到輸入的 csproj 將有哪些導入等信息
最后一步是通過調用 Project 的 Build 方法進行構建,此時將會執行一次構建,構建的信息通過傳入的 Logger 進行輸出,以下是 Logger 的代碼
private class Logger : ILogger{public void Initialize(IEventSource eventSource){eventSource.AnyEventRaised += (_, args) => { Console.WriteLine(args.Message); };}public void Shutdown(){}public LoggerVerbosity Verbosity { get; set; }public string Parameters { get; set; }}全部代碼如下
using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Locator;namespace RalboleaballNeeqaqiku {class Program{static void Main(string[] args){var instances = MSBuildLocator.QueryVisualStudioInstances().ToList();for (var i = 1; i <= instances.Count; i++){var instance = instances[i - 1];var recommended = string.Empty;// The dev console is probably always the right choice because the user explicitly opened// one associated with a Visual Studio install. It will always be first in the list.if (instance.DiscoveryType == DiscoveryType.DeveloperConsole)recommended = " (Recommended!)";Console.WriteLine($"{i}) {instance.Name} - {instance.Version}{recommended}");}// 必須調用 RegisterInstance 方法,否則將提示找不到 msbuild 文件MSBuildLocator.RegisterInstance(instances.First());// 需要將構建的代碼放在另一個方法里面,否則將會因為放在相同的方法,沒有加上程序集Build();}private static void Build(){var projectFile = new FileInfo(@"..\..\..\RalboleaballNeeqaqiku.csproj");var projectRootElement = ProjectRootElement.Open(projectFile.FullName);var project = new Project(projectRootElement);project.Build(new Logger());}private class Logger : ILogger{public void Initialize(IEventSource eventSource){eventSource.AnyEventRaised += (_, args) => { Console.WriteLine(args.Message); };}public void Shutdown(){}public LoggerVerbosity Verbosity { get; set; }public string Parameters { get; set; }}} }本文所有代碼放在?github?和?gitee?歡迎訪問
可以通過如下方式獲取本文的源代碼,先創建一個空文件夾,接著使用命令行 cd 命令進入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init git remote add origin https://gitee.com/lindexi/lindexi_gd.git git pull origin b6171297d4200586d135a8c5c0d7376df7ee7c6a以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源
git remote remove origin git remote add origin https://github.com/lindexi/lindexi_gd.git獲取代碼之后,進入 RalboleaballNeeqaqiku 文件夾
更多關于 Roslyn 請看?手把手教你寫 Roslyn 修改編譯
本文會經常更新,請閱讀原文:?https://blog.lindexi.com/post/dotnet-%E9%80%9A%E8%BF%87%E5%BC%95%E7%94%A8-msbuild-%E7%A8%8B%E5%BA%8F%E9%9B%86%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E5%AE%9A%E5%88%B6%E7%BC%96%E8%AF%91%E5%99%A8.html?,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。
總結
以上是生活随笔為你收集整理的dotnet 通过引用 msbuild 程序集实现自己定制编译器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NET问答: 如何让 HttpClien
- 下一篇: Scott Hanselman 喊你来看