使用.NET源生成器(SG)生成项目的版本号信息

之前写过一篇 源生成器生成自动注入的代码 主要是通过SyntaxProvider查找标注特性实现

其实除了SyntaxProvider之外还有几个很重要的Provider,比如:MetadataReferencesProvider,AdditionalTextsProvider,AnalyzerConfigOptionsProvider等.

今天就讲一下AnalyzerConfigOptionsProvider这个Provider,这里通过AnalyzerConfigOptionsProvider获取引用项目文件夹和顶级命名空间:

通过下面的代码我们就可以打印出来引用项目的GlobalOptions:

csharp 复制代码
var projectKeysProvider = context.AnalyzerConfigOptionsProvider
    .Select((options, _) =>
    {
        var keys = options.GlobalOptions.Keys;
        List<(string? Key, string? Value)> keyValues = [];
        foreach (var key in keys)
        {
            options.GlobalOptions.TryGetValue(key, out var value);
            keyValues.Add((key, value));
        }
        return keyValues;
    });
context.RegisterSourceOutput(projectKeysProvider, (ctx, projectKeys) =>
{
    // 生成代码
    StringBuilder stringBuilder = new();
    foreach (var (Key, Value) in projectKeys)
    {
        stringBuilder.AppendLine($"// {Key} {Value}");
    }
});

不难看出项目文件夹和顶级命名空间的key为build_property.projectdir,build_property.rootnamespace,

取到了项目文件夹地址我们就可以读取对应的*.csproj项目文件了.这里我们通过IO读取文件并取到配置的AssemblyVersion,FileVersion,Version项,然后就可以生成版本信息了,

项目文件本身是一个Xml文件,因此读取配置项可以使用XPath或者正则表达式,出于简洁高效,这里我使用的正则表达式获取:

csharp 复制代码
//生成版本号
var inc = context.AnalyzerConfigOptionsProvider.Select((pvd, _) =>
{
    //取得项目目录
    var flag = pvd.GlobalOptions.TryGetValue("build_property.projectdir", out var root);
    if (!flag)
        return new VersionInfo(null, null);

    //取得命名空间
    pvd.GlobalOptions.TryGetValue("build_property.rootnamespace", out var @namespace);

    //var file = Path.Combine(root, $"*.csproj");
    //查找csproj文件
    var files = Directory.GetFiles(root, "*.csproj", SearchOption.TopDirectoryOnly);

    return new VersionInfo(@namespace, files.Length == 0 ? null : files[0]);
});

//生成
context.RegisterSourceOutput(inc, (ctx, info) =>
{
    if (info.Namespace == null || info.File == null)
        return;

    string version = DefaultVersion;
    string fileVersion = DefaultVersion;
    string assemblyVersion = DefaultVersion;

    // 获取不含扩展名的文件名
    //var @namespace = Path.GetFileNameWithoutExtension(info.Item2);

    // 读取文件
    var text = File.ReadAllText(info.File);

    // 载入Import的文件,例如 : <Import Project="..\Version.props" />
    // 使用正则表达式匹配Project:
    var importMatchs = Regex.Matches(text, "<Import Project=\"(.*?)\"");

    foreach (Match importMatch in importMatchs)
    {
        var importFile = Path.Combine(Path.GetDirectoryName(info.File), importMatch.Groups[1].Value);
        if (File.Exists(importFile))
        {
            text += File.ReadAllText(importFile);
        }
    }

    var match = Regex.Match(text, "<Version>(.*?)</Version>");
    var fileVersionMatch = Regex.Match(text, "<FileVersion>(.*?)</FileVersion>");
    var assemblyVersionMatch = Regex.Match(text, "<AssemblyVersion>(.*?)</AssemblyVersion>");
    if (match.Success)
    {
        version = match.Groups[1].Value;
    }
    if (fileVersionMatch.Success)
    {
        fileVersion = fileVersionMatch.Groups[1].Value;
    }
    if (assemblyVersionMatch.Success)
    {
        assemblyVersion = assemblyVersionMatch.Groups[1].Value;
    }

    string source = $@"// <auto-generated/>
namespace {info.Namespace}.Generated
{{
/// <summary>
/// The version class
/// </summary>
public static class Version
{{
/// <summary>
/// The current version
/// </summary>
public static System.Version Current => System.Version.Parse(""{version}"");

/// <summary>
/// The file version
/// </summary>
public static System.Version FileVersion => System.Version.Parse(""{fileVersion}"");

/// <summary>
/// The assembly version
/// </summary>
public static System.Version AssemblyVersion => System.Version.Parse(""{assemblyVersion}"");

}}
}}
";
    // 输出代码
    ctx.AddSource("version.g.cs", SourceText.From(source, Encoding.UTF8));
});

然后就生成了需要的内容:

csharp 复制代码
// <auto-generated/>
namespace Biwen.QuickApi.Generated
{
    /// <summary>
    /// The version class
    /// </summary>
    public static class Version
    {
        /// <summary>
        /// The current version
        /// </summary>
        public static System.Version Current => System.Version.Parse("2.0.0");

        /// <summary>
        /// The file version
        /// </summary>
        public static System.Version FileVersion => System.Version.Parse("2.0.0");

        /// <summary>
        /// The assembly version
        /// </summary>
        public static System.Version AssemblyVersion => System.Version.Parse("2.0.0");

    }
}

最后通过{namespace}.Generated.Version.*就可以取得版本信息了

透过上面的代码我们理论上就可以读取项目文件夹下所有文件的内容了,当然除了AnalyzerConfigOptionsProvider外,我们也可以使用AdditionalTextsProvider读取附加文件的内容,由于当前文章不涉及有时间我再讲!

以上代码就完成了整个源生成步骤,最后你可以使用我发布的nuget包体验:

bash 复制代码
dotnet add package Biwen.AutoClassGen

源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.AutoClassGen

https://github.com/vipwan/Biwen.AutoClassGen/blob/master/Biwen.AutoClassGen.Gen/VersionSourceGenerator.cs

相关推荐
AzulX20 天前
Natasha v9.0 为 .NET 开发者提供 [热执行] 方案.
c#·.net·roslyn·natasha
Flamesky20 天前
dotnet core微服务框架Jimu ~ 基础网关
微服务·c#·service·dotnet·csharp·micro·micro service
万雅虎1 个月前
使用Roslyn的源生成器生成DTO
roslyn·csharp·sg
万雅虎3 个月前
使用 `Roslyn` 分析器和修复器 对异步方法规范化返回Async结尾
netcore·roslyn·csharp·sg
@背包4 个月前
C# 版本八大排序算法|.Net 架构
算法·排序算法·csharp
小康师兄5 个月前
IoTDB 入门教程 实战篇④——C#示例(开源)
开源·c#·时序数据库·iotdb·csharp
万雅虎5 个月前
C#中使用 record 的好处 因为好用所以推荐~
csharp
万雅虎5 个月前
如何在.NET Framework,或NET8以前的项目中使用C# 12的新特性
csharp
万雅虎5 个月前
C# 使用模式匹配的好处,因为好用所以推荐~
csharp