Appearance
扩展专题 3:附加文件与配置输入
这个专题只回答一个问题:如果你的生成器不只读取 C# 源码,而是还要读 .graphql、.json、模板文件或项目配置,应该怎么接进增量管道。
本页里的路径都按“仓库根目录相对路径”书写。
什么时候需要看这个专题
当你遇到下面这些需求时,就进入这一层:
- 读取
.graphql文件生成客户端代码 - 读取
.json、模板文件、协议文件生成类型 - 根据项目配置决定是否生成、生成到哪里、用什么模式
最短理解链路
text
<AdditionalFiles Include="..." />
-> AdditionalTextsProvider
-> AdditionalText
-> GetText(...)如果进一步接项目配置,链路通常会变成:
text
AnalyzerConfigOptionsProvider
-> GlobalOptions / GetOptions(...)
-> TryGetValue(...)但要先说明清楚一件事:
- 当前仓库里有真实的
AdditionalFiles样例 - 当前仓库里没有完整消费
AnalyzerConfigOptionsProvider的正式 sample
所以这页会把“附加文件”讲实,“配置输入”讲成带边界的补充说明。
1. 附加文件样例:GraphQL 生成器
这部分最值得对照的就是 sample/09-graphql-query-generator。
源码路径 1:示例项目如何声明输入文件
sample/09-graphql-query-generator/SampleApp/SampleApp.csproj
关键片段:
xml
<ItemGroup>
<AdditionalFiles Include="schema.graphql" />
</ItemGroup>为什么看它:
- 这一步决定
schema.graphql能不能进入生成器输入 - 如果这里不写
AdditionalFiles,生成器里的AdditionalTextsProvider通常看不见这个文件 - 这是“项目侧声明输入”的起点,不是生成器代码,但和生成器是否生效直接相关
源码路径 2:输入文件本身长什么样
sample/09-graphql-query-generator/SampleApp/schema.graphql
关键片段:
graphql
type User {
id: ID!
name: String!
email: String!
}
type Query {
getUser(id: ID!): User!
getAllUsers: [User!]!
}为什么看它:
- 你要先意识到,这里输入给生成器的已经不是 C#,而是另一种文本格式
- 生成器负责“读取文本并理解它”,而不是编译器天然会理解
.graphql - 看到这个文件以后,你会更容易理解为什么生成器侧要先过滤文件、再读文本、再解析
源码路径 3:生成器如何把附加文件接进增量管道
sample/09-graphql-query-generator/GraphQLGenerator/GraphQLGenerator.cs
关键片段:
csharp
var graphqlFiles = context.AdditionalTextsProvider
.Where(file => file.Path.EndsWith(".graphql"));
var allFiles = graphqlFiles.Collect();
context.RegisterSourceOutput(allFiles, (spc, files) =>
{
GenerateCode(spc, files);
});为什么看它:
AdditionalTextsProvider是附加文件进入增量管道的入口Where(...)负责筛出真正要处理的文件类型Collect()把多个文件合并成一批输入,便于一次性分析整个 schema 集合- 这是你从“C# 语法树输入”走向“任意文本输入”的第一步
源码路径 4:生成器如何读取文本并处理错误
sample/09-graphql-query-generator/GraphQLGenerator/GraphQLGenerator.cs
关键片段:
csharp
foreach (var file in files)
{
var text = file.GetText(context.CancellationToken);
if (text == null)
{
continue;
}
var content = text.ToString();
if (string.IsNullOrWhiteSpace(content))
{
continue;
}
var schema = parser.Parse(content, file.Path);
if (schema == null)
{
diagnosticReporter.ReportSyntaxError(file.Path, "无法解析 GraphQL schema 文件");
continue;
}
}为什么看它:
GetText(...)是从AdditionalText读取文本内容的关键方法- 这里顺手展示了一个真实工程里很常见的顺序:
- 先读文本
- 再跳过空输入
- 再解析
- 失败时记录诊断
- 和主案例相比,这里多了一层“文件格式解析”,这正是附加文件场景的真实复杂度
源码路径 5:读取完文本后如何输出源码
sample/09-graphql-query-generator/GraphQLGenerator/GraphQLGenerator.cs
关键片段:
csharp
var code = generator.GenerateModel(type, rootNamespace, typeMapper, knownTypes, circularTypes);
context.AddSource($"{type.Name}.g.cs", SourceText.From(code, Encoding.UTF8));为什么看它:
- 这说明附加文件输入最终仍然会回到普通的
AddSource(...)输出 - 也就是说,输入通道变复杂了,但输出通道没有本质变化
- 你会更清楚“读取额外输入”和“生成
.g.cs”其实是两段不同职责
2. 当前仓库里关于配置输入的现实情况
这一点需要明确说清楚:
- 当前仓库没有正式 sample 展示
context.AnalyzerConfigOptionsProvider - 所以这里不能假装它已经有完整实战案例
也就是说,这一页里“附加文件”有真实源码支撑,但“配置输入”暂时只有模式说明,没有配套 sample 可对照。
3. 配置输入的最短写法
如果你后续自己要补一个配置输入生成器,最短模式通常长这样:
csharp
var options = context.AnalyzerConfigOptionsProvider.GlobalOptions;
if (options.TryGetValue("build_property.RootNamespace", out var rootNamespace))
{
// 使用 rootNamespace
}你应该重点记住这几点:
AnalyzerConfigOptionsProvider是配置入口GlobalOptions用于读取全局项目属性TryGetValue(...)读出来的通常是字符串- 你需要自己做布尔、整数、枚举等转换
4. 如果你以后要补一个真实配置输入 sample,优先看什么
建议按下面顺序设计:
- 在
.csproj里先定义一个明确的 MSBuild 属性 - 在生成器里通过
AnalyzerConfigOptionsProvider.GlobalOptions读取它 - 把读取结果接入增量管道,而不是在输出阶段到处散读
- 用测试验证“不同配置值 -> 不同生成结果”
也就是说,配置输入不是另一个完全独立的体系,它只是“另一类输入源”。
5. 最容易混淆的点
- 普通项目文件不等于
AdditionalFiles AdditionalTextsProvider负责接入文件,不负责自动理解文件含义GetText(...)读出来的是文本,不是语法树- 当前仓库没有正式演示
AnalyzerConfigOptionsProvider的 sample,不要把理论示例误当成现有实现
6. 最推荐的配套文档
- 术语解释:看 术语分册 7:附加文件、配置、诊断与文本
- 常见问题:看 FAQ 分册 5:高级输入与扩展链路
- 返回主教程收口:看 第 12 章