Roslyn 分析器 讀取 csproj 專案檔案的 AdditionalFiles Item 的 Metadata 配置

lindexi發表於2024-10-12

上一篇部落格告訴大家如何在 IIncrementalGenerator 增量 Source Generator 裡讀取 csproj 專案檔案的屬性配置,詳細請看:
IIncrementalGenerator 增量 Source Generator 生成程式碼入門 讀取 csproj 專案檔案的屬性配置

在上一篇部落格裡面,核心是透過配置了 CompilerVisibleProperty 讓屬性可見,如下面程式碼所示

  <PropertyGroup>
    <MyCustomProperty>lindexi is doubi</MyCustomProperty>
  </PropertyGroup>

  <ItemGroup>
    <CompilerVisibleProperty Include="MyCustomProperty" />
  </ItemGroup>

在分析器裡面獲取屬性內容的核心程式碼如下

            context.RegisterImplementationSourceOutput(context.AnalyzerConfigOptionsProvider,
                (productionContext, provider) =>
                {
                    var text = string.Empty;

                    // 透過 csproj 等 PropertyGroup 裡面獲取
                    // 需要將可見的,放入到 CompilerVisibleProperty 裡面
                    // 需要加上 `build_property.` 字首
                    if (provider.GlobalOptions.TryGetValue("build_property.MyCustomProperty", out var myCustomProperty))
                    {
                        text += " " + myCustomProperty;
                    }
                });

本文將告訴大家如何獲取引用檔案的 ItemGroup 裡面的 Item 的 Metadata 內容如何獲取到。如下面專案檔案的程式碼,定義了名為 PaintStateDiagramMarkdownFile 的 Item 項,此項裡面包含了 Link 這個 Metadata 內容

  <ItemGroup>
    <PaintStateDiagramMarkdownFile Include="..\KereqeewahaihibayNohelqiji\*.txt" Link="Assets\StateDiagrams\%(RecursiveDir)%(Filename)%(Extension)" />
  </ItemGroup>

如期望讓其 PaintStateDiagramMarkdownFile 項在分析器可見,需要將其新增到 AdditionalFiles 裡面,如下面程式碼

  <ItemGroup>
    <AdditionalFiles Include="@(PaintStateDiagramMarkdownFile)"/>
  </ItemGroup>

為了讓 Link 這個 Metadata 內容同樣被分析器可見,需要在將 PaintStateDiagramMarkdownFile 新增到 AdditionalFiles 時,附帶將 Link 帶上,更新之後的程式碼如下

  <ItemGroup>
    <AdditionalFiles Include="@(PaintStateDiagramMarkdownFile)" Link="%(Link)"/>
  </ItemGroup>

再使用 CompilerVisibleItemMetadata 設定 AdditionalFiles 的 Link 也是對分析器可見,程式碼如下

  <ItemGroup>
    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Link" />
  </ItemGroup>

如此即可在後續分析器裡面裡面使用 AnalyzerConfigOptionsProvider 的 GetOptions 方法獲取到 Metadata 資訊

以上編輯之後的 csproj 專案檔案內容如下

  <ItemGroup>
    <PaintStateDiagramMarkdownFile Include="..\KereqeewahaihibayNohelqiji\*.txt" Link="Assets\StateDiagrams\%(RecursiveDir)%(Filename)%(Extension)" />

    <AdditionalFiles Include="@(PaintStateDiagramMarkdownFile)" Link="%(Link)"/>

    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Link" />
  </ItemGroup>

當然了,在真正的 NuGet 打包專案上,常常會將 AdditionalFiles 和 CompilerVisibleItemMetadata 寫到 target 或 props 檔案裡面,而不是放在 csproj 裡面。詳細請參閱我的 部落格導航 獲取分析器入門知識

在分析器裡面裡面,可先收集或用其他方式獲取到 AdditionalFiles 內容,將其傳入給到 AnalyzerConfigOptionsProvider 的 GetOptions 方法,即可獲取到 AnalyzerConfigOptions 物件。再對 AnalyzerConfigOptions 呼叫 TryGetValue 方法,傳入字串格式是 build_metadata.AdditionalFiles.[MetadataName] 即可獲取到 Metadata 資訊。以上字串格式的 [MetadataName] 還請替換為實際需要獲取的值,如本文以上例子裡面期望獲取到 Link 這個 Metadata 內容,可使用如下程式碼

AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider = ...
AdditionalText additionalText = ...

                        AnalyzerConfigOptions analyzerConfigOptions = analyzerConfigOptionsProvider.GetOptions(additionalText);
                        if (analyzerConfigOptions.TryGetValue("build_metadata.AdditionalFiles.Link", out var link))
                        {
                            
                        }

為了更好的說明使用方法,我建立了兩個專案,其中一個專案是分析器專案,另一個是控制檯專案。我將演示如何獲取控制檯專案所配置的 PaintStateDiagramMarkdownFile 項的 Link 資訊。本文內容裡面只給出關鍵程式碼片段,如需要全部的專案檔案,可到本文末尾找到本文所有程式碼的下載方法

以下是控制檯專案 CujelcijallChearjawjuja 的 csproj 檔案的程式碼

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PaintStateDiagramMarkdownFile Include="..\KereqeewahaihibayNohelqiji\*.txt" Link="Assets\StateDiagrams\%(RecursiveDir)%(Filename)%(Extension)" />
    <None Include="@(PaintStateDiagramMarkdownFile)"></None>
    <AdditionalFiles Include="@(PaintStateDiagramMarkdownFile)" Link="%(Link)"/>

    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Link" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\KereqeewahaihibayNohelqiji\KereqeewahaihibayNohelqiji.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>

</Project>

可以看到寫了 PaintStateDiagramMarkdownFile 項,且將其新增到 AdditionalFiles 裡面,新增的過程中還將 PaintStateDiagramMarkdownFile 的 Link 賦值給到 AdditionalFiles 的 Link 裡

最後配置 CompilerVisibleItemMetadata 讓 AdditionalFiles 的 Link 資訊被分析器可見

中間新增的 <None Include="@(PaintStateDiagramMarkdownFile)"></None> 只是一個簡單的除錯程式碼,用於讓我可以在 VisualStudio 專案裡面看到檔案而已,和本文實際的演示沒有關係

新增分析器 KereqeewahaihibayNohelqiji 專案,分析器專案的 csproj 專案檔案的程式碼如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" PrivateAssets="all" />
  </ItemGroup>

</Project>

以上程式碼的 EnforceExtendedAnalyzerRules 屬性含義請參閱 Roslyn 分析器 EnforceExtendedAnalyzerRules 屬性的作用

在 KereqeewahaihibayNohelqiji 專案放入 TextFile.txt 檔案用於給 CujelcijallChearjawjuja 專案引用

在 KereqeewahaihibayNohelqiji 編寫名為 YelgahainaljinalBehoballdewur 的 IIncrementalGenerator 增量 Source Generator 生成器,程式碼如下

namespace KereqeewahaihibayNohelqiji
{
    [Generator(LanguageNames.CSharp)]
    internal class YelgahainaljinalBehoballdewur : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            // 在這裡編寫程式碼
            context.RegisterImplementationSourceOutput(context.AdditionalTextsProvider.Collect().Combine(context.AnalyzerConfigOptionsProvider),
                (productionContext, provider) =>
                {
                    // 這裡的程式碼只有當配置初始化或變更時才會被執行
                    var additionalTextArray = provider.Left;
                    AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider = provider.Right;

                    var stringBuilder = new StringBuilder();
                    for (int i = 0; i < 3; i++)
                    {
                        stringBuilder.Append('"');
                    }
                    stringBuilder.AppendLine();

                    foreach (AdditionalText additionalText in additionalTextArray)
                    {
                        AnalyzerConfigOptions analyzerConfigOptions = analyzerConfigOptionsProvider.GetOptions(additionalText);
                        if (analyzerConfigOptions.TryGetValue("build_metadata.AdditionalFiles.Link", out var link))
                        {
                            stringBuilder.AppendLine($"File={additionalText.Path}");
                            stringBuilder.AppendLine($"Link={link}");
                        }
                    }

                    for (int i = 0; i < 3; i++)
                    {
                        stringBuilder.Append('"');
                    }

                    var code = @"using System;
namespace CujelcijallChearjawjuja
{
    public static class Foo
    {
        public static void F1()
        {
            Console.WriteLine(" + stringBuilder.ToString() + @");
        }
    }
}";
                    productionContext.AddSource("Foo.cs", code);
                });
        }
    }
}

如此期望能夠將控制檯專案裡面的 PaintStateDiagramMarkdownFile 作為 Foo 型別的 F1 方法輸出控制檯資訊的內容

編輯控制檯專案 CujelcijallChearjawjuja 的 Program.cs 檔案,新增如下程式碼

using CujelcijallChearjawjuja;

Foo.F1();

執行控制檯專案,大概可以看到如下輸出內容。如此可以證明此方法可以獲取 Item 項裡面配置的 Metadata 資訊

File=C:\lindexi\Workbench\CujelcijallChearjawjuja\..\KereqeewahaihibayNohelqiji\TextFile.txt
Link=Assets\StateDiagrams\TextFile.txt

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin eb119d9e64e387cea847aee79f4509744c349018

以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin eb119d9e64e387cea847aee79f4509744c349018

獲取程式碼之後,進入 Workbench/CujelcijallChearjawjuja 資料夾,即可獲取到原始碼

更多分析器和原始碼生成部落格,請參閱 部落格導航

相關文章