dotnet 通过引用 msbuild 程序集实现自己定制编译器
本來(lái)我想說(shuō)的是基于引用 msbuild 程序集來(lái)自己做一個(gè)編譯器,但是想想好像本文做的,和造編譯器沒(méi)啥關(guān)系,咱自己調(diào)用 msbuild 的 API 而已。本文來(lái)告訴大家如何引用 msbuild 程序集,如何在自己的應(yīng)用程序里面嵌入 msbuild 的構(gòu)建代碼,實(shí)現(xiàn) dotnet build 的效果
大部分的代碼都是采用命令行的方式去調(diào)用 dotnet build 或 msbuild 命令,然而通過(guò)命令行調(diào)用用的是跨進(jìn)程的方式,如果期望做更多的定制化,最好還是放在相同的進(jìn)程,此時(shí)可以更改構(gòu)建的各個(gè)步驟
自己制作一個(gè)編譯器最簡(jiǎn)單的方法就是引用現(xiàn)有的成熟的編譯器作為組件,剛好 msbuild 最新版本也是使用 dotnet 框架編寫的,咱的 dotnet 應(yīng)用可以非常方便將 msbuild 引用進(jìn)來(lái)。當(dāng)然了,本文不討論如何自己發(fā)布 msbuild 的問(wèn)題,因?yàn)檫@又是另一個(gè)坑了。本文的方法是引用本機(jī)已安裝好的 msbuild 程序集
在開始之前,請(qǐng)新建一個(gè)控制臺(tái)項(xiàng)目。當(dāng)然了,你要是新建一個(gè) WPF 項(xiàng)目也沒(méi)啥問(wèn)題
編輯 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>第一步是先獲取本機(jī)已安裝好的 msbuild 實(shí)例,如下代碼
static void Main(string[] args){var instances = MSBuildLocator.QueryVisualStudioInstances().ToList();}以上代碼要能運(yùn)行,需要加上如下命名空間
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 就是本機(jī)安裝的 msbuild 實(shí)例,也就是 dotnet sdk 的各個(gè)版本,可以使用如下代碼輸出
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}");}下一步是有點(diǎn)黑科技的部分,也就是為什么我會(huì)寫本文的原因。使用下面代碼注冊(cè) msbuild 實(shí)例,如果沒(méi)有使用下面這句代碼注冊(cè),那么在后續(xù)調(diào)用 msbuild 相關(guān)類型時(shí),將會(huì)因?yàn)檎也坏?msbuild 的程序集而失敗
// 必須調(diào)用 RegisterInstance 方法,否則將提示找不到 msbuild 文件MSBuildLocator.RegisterInstance(instances.First());注冊(cè)完成之后,將可以使用 msbuild 提供的各個(gè)類來(lái)實(shí)現(xiàn)構(gòu)建,請(qǐng)新建一個(gè)方法用來(lái)編寫調(diào)用 msbuild 各個(gè)類的構(gòu)建代碼。如以下代碼
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());}為什么需要這部分構(gòu)建代碼放在另一個(gè)方法里面?原因是在碰到了 ProjectRootElement 類型的時(shí)候,就需要開始加載程序集,然而在調(diào)用 MSBuildLocator.RegisterInstance 之前,還是找不到程序集的哦。因此為了讓 MSBuildLocator.RegisterInstance 能被執(zhí)行,就需要讓包含 MSBuildLocator.RegisterInstance 代碼的方法不會(huì)在執(zhí)行之前碰到還沒(méi)有存在的程序集,因此就需要將碰到構(gòu)建相關(guān)邏輯的代碼放在獨(dú)立的方法或者獨(dú)立的類型里面,這樣就能讓包含 MSBuildLocator.RegisterInstance 的代碼不會(huì)因?yàn)檎也坏匠绦蚣粓?zhí)行
以上代碼是通過(guò)調(diào)用 ProjectRootElement.Open 方法加載了 csproj 文件,此步驟是反序列化過(guò)程。接著新建 Project 實(shí)例,在新建方法里面將會(huì)進(jìn)行初始化,可以拿到輸入的 csproj 將有哪些導(dǎo)入等信息
最后一步是通過(guò)調(diào)用 Project 的 Build 方法進(jìn)行構(gòu)建,此時(shí)將會(huì)執(zhí)行一次構(gòu)建,構(gòu)建的信息通過(guò)傳入的 Logger 進(jìn)行輸出,以下是 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}");}// 必須調(diào)用 RegisterInstance 方法,否則將提示找不到 msbuild 文件MSBuildLocator.RegisterInstance(instances.First());// 需要將構(gòu)建的代碼放在另一個(gè)方法里面,否則將會(huì)因?yàn)榉旁谙嗤姆椒?#xff0c;沒(méi)有加上程序集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?歡迎訪問(wèn)
可以通過(guò)如下方式獲取本文的源代碼,先創(chuàng)建一個(gè)空文件夾,接著使用命令行 cd 命令進(jìn)入此空文件夾,在命令行里面輸入以下代碼,即可獲取到本文的代碼
git init git remote add origin https://gitee.com/lindexi/lindexi_gd.git git pull origin b6171297d4200586d135a8c5c0d7376df7ee7c6a以上使用的是 gitee 的源,如果 gitee 不能訪問(wèn),請(qǐng)?zhí)鎿Q為 github 的源
git remote remove origin git remote add origin https://github.com/lindexi/lindexi_gd.git獲取代碼之后,進(jìn)入 RalboleaballNeeqaqiku 文件夾
更多關(guān)于 Roslyn 請(qǐng)看?手把手教你寫 Roslyn 修改編譯
本文會(huì)經(jīng)常更新,請(qǐng)閱讀原文:?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?,以避免陳舊錯(cuò)誤知識(shí)的誤導(dǎo),同時(shí)有更好的閱讀體驗(yàn)。
總結(jié)
以上是生活随笔為你收集整理的dotnet 通过引用 msbuild 程序集实现自己定制编译器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: NET问答: 如何让 HttpClien
- 下一篇: .NET 6 Preview 6 正式发