之前写过一篇 源生成器生成自动注入的代码 主要是通过SyntaxProvider
查找标注特性实现
其实除了SyntaxProvider
之外还有几个很重要的Provider,比如:MetadataReferencesProvider
,AdditionalTextsProvider
,AnalyzerConfigOptionsProvider
等.
今天就讲一下AnalyzerConfigOptionsProvider
这个Provider,这里通过AnalyzerConfigOptionsProvider获取引用项目文件夹和顶级命名空间:
通过下面的代码我们就可以打印出来引用项目的GlobalOptions
:
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
或者正则表达式
,出于简洁高效,这里我使用的正则表达式获取:
//生成版本号
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));
});
然后就生成了需要的内容:
// <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包体验:
dotnet add package Biwen.AutoClassGen
源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.AutoClassGen