Skip to content

feat(build): 库可以替用户拉起工具链,构建程序可以声明「一组文件」(#359) (2026.8.6.2) - #366

Merged
Sunrisepeak merged 9 commits into
mainfrom
feat/359-provisions-and-glob-inputs
Aug 6, 2026
Merged

feat(build): 库可以替用户拉起工具链,构建程序可以声明「一组文件」(#359) (2026.8.6.2)#366
Sunrisepeak merged 9 commits into
mainfrom
feat/359-provisions-and-glob-inputs

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes #359.

#355 让依赖产出的 host 工具可用之后,grpc-m 成了第一个真实使用者,并暴露出两个缺口:工具被构建了却传不到消费者,以及 build.mcpp 无法安全地 glob 自己的输入。

两者形状相同——新增一种提供物/输入时,没有任何地方逼你回答「它怎么传播」「它的指纹怎么取」。这是本仓库反复付学费的「同一决策在 N 处推导」的镜像:一个必答问题在零处被表达。directives::kTable 已经用「Scope 是必填字段」解过一次。

设计:.agents/docs/2026-08-06-provisions-and-build-inputs.md

用户侧的结果

-[dependencies.mcpplibs]
-grpc        = "1.83.0"
-grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"] }
-grpcgen     = { version = "1.83.0", host-module = true }
-[dependencies.compat]
-protobuf    = { version = "35.1", tools = ["protoc"] }
+[dependencies.grpc]
+grpc = { version = "1.83.0", features = ["codegen"] }

且仍然保有别人没有的两条性质:工具与运行时版本错配不可表达交叉编译构造上正确

1. 提供物进表,传播由 reexport 决定

新增 src/build/provisions.cppm:ProvisionKind 表(tool / host-module / dep-dir),每行必须回答「裸名可寻址吗」「跨边要不要 reexport」;传播是一条不动点,与 computeUsageRequirements 同形。

依赖 spec 新增 reexport = true(默认 false)。

刻意不复用边上的 visibility:它默认就是 \"public\",搭车等于默认传播,而「把工具交给消费者」是供应链主张,必须显式写下来。(设计初稿以「include_dirs 默认 private」作类比,那条对本仓库不成立——prepare.cppm:2868 无条件把 privateBuild.includeDirs 拷进 publicUsage;真正 private-only 的是 build.mcpp 注入的那些。)

裸名不再靠追加顺序决定。 全限定 MCPP_DEP_<NS>_<NAME>_BIN_<TOOL> 总是发布;裸名按与包身份同一套阶梯绑定(mcpplibscompat → 无命名空间 → 唯一候选),争用时给出诊断。否则「版本错配不可表达」会从另一扇门跑回来:两个互不相识的库都提供尾名 protobuf 时,谁赢取决于 emplace_back 顺序。

dep_dir() 与 host 模块一并沿同一条规则传播(此前分别只覆盖直接依赖、只认 root 的 dependencies)。

2. rerun-if-changed-glob:输入可以是一个集合

此前重跑键只有「文件内容」和「环境变量」两种形态,于是 glob 结构性不安全:新增一个 .proto 不改变任何已声明文件的哈希,程序不重跑,新文件静默不生成(实测 Finished dev in 0.01s,产物 0 个)。这比「要求用户列名字」更坏,所以 grpc-m 当初选了显式列表。

指纹是排序后的匹配路径集合,不含内容、size、mtime——内容已由 File 条目覆盖,而 mtime 在 checkout / 容器 / rsync 下不稳定。构建输出目录与 .git 永不进入集合,否则宽模式会对着自己的产物无限重跑。

两层必须一起改:工程级 fast path 会整个跳过 prepare,而新增文件不移动任何 mtime——那正是它此前静默无效的地方。缓存记录因此新增 root 行,fast path 才能按每份缓存自己的根去求值。protocol → 2,cache epoch → 2。

3. 让库能按平台裁剪,让子构建说出真话

ConditionalConfig 带着三张依赖表却独缺 feature-deps,正是该类型注释为 #258 记录过的失败形状。补上 [target.<sel>.feature-deps.<f>]:否则一旦由决定请求什么,不支持的平台就变成用户改不掉的硬错(Windows 的工具子构建缺陷至今未定位)。feature 本身无条件注册,只有它拉进来的东西是条件性的。

顺带把条件依赖表收进同一条 funnel:此前三个调用点合并构建输入,只有 root 那个还额外合并依赖表,注释把它说成「out of scope」。

工具子构建失败现在带上 chain、scratch 目录和可直接重跑的命令,MCPP_TOOL_BUILD_VERBOSE=1 关掉内层输出过滤——Windows 那个缺陷至今未定位的直接原因就是真正的报错没进日志。

4. 其他

  • path_matches_glob 从 scanner 的匿名命名空间搬进 mcpp.modgraph.glob:glob 输入的指纹必须与 sources = [...] 选中同一批文件,两个「应该一致」的匹配器就是那个病。
  • 内置 xlings 升到 2026.8.6.2。

验证

  • 新单测:传播规则(未 reexport 不外泄 / 逐跳传递 / 环终止)、裸名阶梯四级、glob 指纹(增删变、改内容不变、输出目录不参与、跨平台排序一致)。
  • 新 e2e 193/194/195。193 已用「让 propagate 忽略 reexport」验证会变红。
  • 新增 Manifest.EveryDependencySpecKeyIsAccepted:dep-spec 的键必须同时写进两处,漏掉第一处时诊断是误导性的(bool 被告知「必须是字符串或嵌套表」)。本 PR 加 reexport 时就撞上了。
  • 本机 e2e 179 passed / 7 failed / 8 skipped;7 条已在 main 上逐条复现,均为本机环境性。

5. 实际去发布它,又逼出三条

前四节是 #359 本身。下面三条是试着把 reexport 写进一个真实的已发布包才暴露的,同属一个 PR 是因为缺任何一条,那个目标形态都写不出来或不能安全发布。

5.1 feature-dep 与已声明依赖同键时被丢弃

mergeActiveFeatureDepstry_emplace,键已存在就丢弃 feature 的 spec。这条规则对 version/path/git 是对的(条件段不该静默覆盖无条件段),对 tools/reexport 却丢掉了这个 feature 存在的全部理由

gRPC 正是反例,而且是本 issue 的头号用例:它无条件依赖 compat.protobuf,而 codegen feature 要往同一条边tools = ["protoc"], reexport = true。挪到无条件条目上不可行 —— 那会让每个 grpc 消费者都构建 protoc(约 157 个额外 TU),正是 tools 默认关闭要避免的。

现在:tools / features 取并集,host-module / reexport 取或,身份字段不合并。e2e 193 第 3 段覆盖,并已验证「不合并 tools」会变红(rulepkg: no codegen tool)。

5.2 不认识的依赖键让整份 manifest 加载失败

reexport 要写进已发布包的 manifest,于是有了一个此前没人问过的问题:比它旧的 mcpp 读到会怎样?答案曾经是整份加载失败,而且报错误导 —— reexport = true 被告知「must be a string, inline dep table, or nested table」。

根因是一个谓词兼任两职:looks_like_inline_dep_spec 既判定「内联 spec 还是嵌套命名空间表」,又枚举「哪些键有意义」。不认识的键因此走不到「未知选项」那条路上。

后果不是提示不友好,而是任何已发布包都永远无法采用新键。这与 #349 确立的是同一条性质:数据不得决定程序是否可用

判据换轴为「它是否指名了一个来源」(path/version/git/workspace)—— 嵌套命名空间表的键是包名,不会有包叫 version。识别为 spec 之后,不认识的键记为降级(--strict 仍拒绝),与 xpkg 读取器的 xpkgUnknownKeys 同一条纪律。救不了已经发布出去的旧客户端,但这类问题从此不再复发。

5.3 aarch64 上原生 GCC 的载荷是 musl-gcc,不是 glibc gcc(#367)

ci-aarch64-fresh-installdownload failed for xim:gcc@16.1.0: HTTP 404

第一次归因是错的,记录在此:我判成「[toolchain] 缺 arch 轴」,并给 manifest 加了一根选择器轴。生态其实早就支持 aarch64 —— xlings-res 的 musl-gcc 发行版里就有 musl-gcc-16.1.0-linux-aarch64.tar.gz,而 xim:gcc 声明的是 archs = { "x86_64" }。缺的不是让用户去写 arch,而是 mcpp 问错了包

同一次构建里 target 一侧本来就是对的:

Resolved gcc@16.1.0 → aarch64-linux-musl → xim-x-musl-gcc/…/aarch64-linux-musl-g++

target 一侧把 triple 注入了 spec,于是 to_xim_package 的 musl 分支按「同一 target,两种载荷形态」选中原生包。而 build.mcpphost 解析刻意不注入 target(代码里就写着 // Deliberately NO target injection),落到最后那条 glibc 分支,拿到只有 x86_64 资产的 gcc

「这个 spec 在这台机器上对应哪个载荷」正是 to_xim_package 存在的意义,它上面那条 musl 分支已经在回答同一问题的另一半。修复落在那里。判定抽成接收 host arch 的自由函数而非读编译期常量,于是 aarch64 的答案能在 x86_64 机器上被单测覆盖 —— 测一个手边没有的架构,正是需要的。

已撤回两处走偏的东西:manifest 的 arch 选择器轴(连同为它抽出的 cfgpred 模块),以及「host 工具链装不上就回退 target 工具链」的兜底 —— 后者是错误归因的产物,正确解析之后是死代码。#367 收窄成纯索引侧的一条:ximarchs 不含当前架构时应直接拒绝,而不是下载到 404。

同一条 job 还暴露出:mcpp 那一半用 "$m"(刚构建的)验证,紧接着的 xlings 构建却用裸 mcpp(已安装的)—— 这一半从来没看见过 PR,与该步注释里为 clone ref 记录过的缺陷是同一个,只是位置往下挪了几行。一并修掉。

5.4 自举 pin 的失败,又被归因成「索引删了版本」

09-release.md §5 已经为 3b1cb6b 记过一次这个更正,本 PR 的 c1a3e45 又写了一遍。它是错的:两个 xim-pkgindex 都列着 63 个 mcpp 版本,2026.8.5.3 在里面。available: 实际只列出一个版本、正是那次 job 刚装上的那个 —— 那是「已安装版本」的形状,而那一步解析的是 .xlings.jsonworkspace pin。真实机制未坐实,不写成结论。

pin 的 bump 本身没问题(§4 明确说这是合理做法,且确实解开了那条 job)。教训更窄:在断言「索引删了它」之前,先读索引。 已把这条补进 09-release.md

#355 让依赖产出的 host 工具可用之后,grpc-m 成了第一个真实使用者,并暴露出
两个缺口:工具**被构建了**却传不到消费者,以及 build.mcpp 无法安全地 glob
自己的输入。两者形状相同 —— **新增一种提供物/输入时,没有任何地方逼你回答
「它怎么传播」「它的指纹怎么取」**。这是本仓库反复付学费的「同一决策在 N 处
推导」的镜像:一个必答问题在**零处**被表达。

## 提供物进表,传播由 `reexport` 决定

新增 `src/build/provisions.cppm`:ProvisionKind 表(tool / host-module /
dep-dir),每行必须回答「裸名可寻址吗」「跨边要不要 reexport」;传播是一条
不动点,与 computeUsageRequirements 同形。

依赖 spec 新增 `reexport = true`(默认 false):把这条边的构建期提供物交给
**本包自己的消费者**。于是 grpc 可以在描述符里声明 protoc + 插件 + 规则模块,
它的用户只写一条依赖。

刻意不复用边上的 `visibility`:它默认就是 "public",搭车等于默认传播,而
「把工具交给消费者」是供应链主张,必须显式写下来。(设计初稿以「include_dirs
默认 private」类比,那条对本仓库不成立 —— prepare.cppm 无条件把
privateBuild.includeDirs 拷进 publicUsage;真正 private-only 的是 build.mcpp
注入的那些。)

裸名不再靠追加顺序决定。全限定 `MCPP_DEP_<NS>_<NAME>_BIN_<TOOL>` 总是发布;
裸名按与包身份同一套阶梯绑定(mcpplibs → compat → 无命名空间 → 唯一候选),
争用时给出诊断而不是默默选一个 —— 否则「版本错配不可表达」这条性质会从另一
扇门跑回来。

`dep_dir()` 与 host 模块一并沿同一条规则传播(此前分别只覆盖直接依赖、只认
root 的 dependencies)。

## `rerun-if-changed-glob`:输入可以是一个集合

`build.mcpp` 的重跑键此前只有「文件内容」和「环境变量」两种形态,于是 glob
**结构性不安全**:新增一个 .proto 不改变任何已声明文件的哈希,程序不重跑,
新文件静默不生成。

新指令的指纹是**排序后的匹配路径集合**,不含内容、size、mtime —— 内容已由
File 条目覆盖,而 mtime 在 checkout/容器/rsync 下不稳定(本仓库在
file_time_type 的 epoch 上摔过)。构建输出目录与 .git 永不进入集合,否则宽
模式会对着自己的产物无限重跑。

两层必须一起改:工程级 fast path 会整个跳过 prepare,而新增文件不移动任何
mtime —— 那正是它此前静默无效的地方。缓存记录因此新增 `root` 行,fast path
才能按每份缓存自己的根去求值。protocol → 2,cache epoch → 2。

## 让库能按平台裁剪,让子构建说出真话

`ConditionalConfig` 带着三张依赖表却独缺 `feature-deps`,正是该类型注释为
#258 记录过的失败形状。补上 `[target.<sel>.feature-deps.<f>]`:否则一旦由
库决定请求什么,不支持的平台就变成用户改不掉的硬错(Windows 的工具子构建
缺陷至今未定位)。feature 本身无条件注册,只有它拉进来的东西是条件性的。

顺带把条件依赖表收进同一条 funnel:此前三个调用点合并构建输入,只有 root
那个还额外合并依赖表,而注释把它说成「out of scope」。

工具子构建失败现在带上 chain、scratch 目录和可直接重跑的命令,
`MCPP_TOOL_BUILD_VERBOSE=1` 关掉内层输出过滤 —— Windows 那个缺陷至今未定位
的直接原因就是真正的报错没进日志。

## 其他

- `path_matches_glob` 从 scanner 的匿名命名空间搬进 `mcpp.modgraph.glob`:
  glob 输入的指纹必须与 `sources = [...]` 选中同一批文件,两个「应该一致」的
  匹配器就是那个病。
- 内置 xlings 升到 2026.8.6.2。

## 验证

- 新单测:传播规则(未 reexport 不外泄 / 逐跳传递 / 环终止)、裸名阶梯四级、
  glob 指纹(增删变、改内容不变、输出目录不参与、跨平台排序一致)。
- 新 e2e 193/194/195。193 已用「让 propagate 忽略 reexport」验证会变红。
- 新增 `EveryDependencySpecKeyIsAccepted`:dep-spec 的键必须同时写进两处,
  漏掉第一处时诊断是**误导性**的(bool 被告知「必须是字符串或嵌套表」),
  #359 加 `reexport` 时就撞上了。
- 本机 e2e 179 passed / 7 failed / 8 skipped;7 条在 main 上逐条复现,均为环境性。
`ci-aarch64-fresh-install` 的 self-host 步报 `xlings: version '2026.8.5.3'
not found for 'mcpp' — available: 2026.8.6.1`。

自举 pin 是「从哪个已发布版本开始自举」,按设计不随每次发布走;但它是**上界
之下的选择**,不是一个可以无限期不动的常量 —— xim-pkgindex 对 mcpp 只暴露最
新条目,pin 落在它之外就成了「装不上」。判据因此是「该版本此刻仍可解析」,
而不是「它是不是最新」。
`mergeActiveFeatureDeps` 用的是 `try_emplace`,键已存在就丢弃 feature 的
spec。这条规则对 version/path/git 是对的(条件段不该静默覆盖无条件段),对
`tools` / `reexport` 却丢掉了这个 feature 存在的全部理由。

gRPC 就是反例,而且是本 issue 的头号用例:它**无条件**依赖 compat.protobuf,
而 `codegen` feature 需要往**同一条边**加 `tools = ["protoc"], reexport = true`。
挪到无条件条目上不可行 —— 那会让每个 grpc 消费者都构建 protoc(~157 个额外
TU),而这正是 tools 默认关闭要避免的。

于是:tools / features 取并集,host-module / reexport 取或,身份字段不合并。
与逐边 feature 请求本来遵循的规则一致。

e2e 193 补第 3 段覆盖它,并已用「不合并 tools」验证会变红
(`rulepkg: no codegen tool`)。
`--strict` 会把**所有**降级提升为错误,包括与本测试无关的那些 —— Windows 上
clang 报「this toolchain and platform combination emits no GNU depfile」,于是
195 在 Windows 上因为一件它并不关心的事而红。

本测试要断言的是「没有任何谓词匹配的平台上,请求该 feature 不是未知 feature
错误」,那就直接断言那条诊断不出现。
@Sunrisepeak

Copy link
Copy Markdown
Member Author

补一条 CI 说明,避免把两件事混成一件。

fresh install + native build (aarch64 / glibc) 在这条分支上仍然红,但原因换了一层,而且不是本 PR 引入的。

第一轮红在自举 pin 上:

[error] xlings: version '2026.8.5.3' not found for 'mcpp'
[error]   available: 2026.8.6.1

.xlings.json 的 pin 落在 xim-pkgindex 对 mcpp 暴露的条目之外。自举 pin 按设计不随每次发布走,但它是上界之下的选择而不是一个可以无限期不动的常量 —— 判据是「该版本此刻仍可解析」。已在 c1a3e45 修掉。

修掉之后它走得更远,然后死在:

[error] download failed for xim:gcc@16.1.0: HTTP 404
error: host toolchain for build.mcpp ('gcc@16.1.0'): xlings install of 'xim:gcc@16.1.0' failed

xim-pkgindexgcc.lua 声明 archs = { "x86_64" } —— 这个包本来就没有 aarch64 载荷。mcpp 在 aarch64 上仍然请求它,是因为 host 工具链只按 OS 选择([toolchain] 的键是 default / macos / windows,没有 arch 这根轴),而 [target.aarch64-linux-musl] 只决定 target 一侧。

该 job 自 2026-08-03 起在 main 上一直红;本 PR 的 pin 修复把失败点往后推了一层,暴露了它,没有制造它。已单独开 #367 记录,那是 host 工具链选择缺 arch 轴的设计问题,不是一行修复,也与 #359 无关。

影响面:只影响在 aarch64 上从源码自举;aarch64 上已发布二进制的安装与使用不受影响(同一 job 里 "Fresh-install mcpp via xlings" 与 "Native build + run an import std program" 两步都是绿的)。

`reexport` 要写进**已发布包**的 manifest(grpc 的 [feature-deps.codegen]),
于是冒出一个此前没人问过的问题:比它旧的 mcpp 读到会怎样?

答案曾经是整份加载失败,而且报错误导 —— `reexport = true` 被告知
「must be a string, inline dep table, or nested table」。

根因是**一个谓词兼任两职**:`looks_like_inline_dep_spec` 既判定「内联 spec 还是
嵌套命名空间表」,又枚举「哪些键有意义」。不认识的键因此走不到「未知选项」那条
路上 —— 表直接判不出是 spec,被当成命名空间。

后果不是提示不友好,而是**任何已发布包都永远无法采用新键**。这与 #349 确立的
是同一条性质:数据不得决定程序是否可用。

判据换轴:内联 spec 的判定是「它是否指名了一个来源」(path/version/git/workspace)。
嵌套命名空间表的键是**包名**,不会有包叫 `version`,所以不会误判。识别为 spec
之后,不认识的键记为降级(--strict 仍拒绝),与 xpkg 读取器的 xpkgUnknownKeys
「record rather than swallow」同一条纪律。

救不了已经发布出去的旧客户端,但从这一版起这类问题不再复发。
`ci-aarch64-fresh-install` 在 aarch64 上构建 xlings 时死在:

    [error] download failed for xim:gcc@16.1.0: HTTP 404
    error: host toolchain for build.mcpp ('gcc@16.1.0'): ...

链条是:图里有包带 build.mcpp ⇒ 需要 HOST 编译器 ⇒ 取 `[toolchain] default`
⇒ 那是 `gcc@16.1.0`,而 xim 的 gcc 声明 `archs = { "x86_64" }` ⇒ 404。

`[toolchain]` 的键是 OS,**没有 arch 这根轴**(#367),所以 aarch64 问的和
x86_64 问的是同一个包。结果是:凡是依赖图里出现 build.mcpp 的工程,在 aarch64
上就是死路。

但这台机器上**已经有**一个能用的编译器:当解析出的 target 与 host 只差 libc
环境(aarch64-linux-musl 跑在 aarch64 Linux 上),target 工具链就是本机原生的
—— 它在这里能跑,它产出的二进制在这里也能跑,拿它编译一个构建程序完全成立。

因此:固定的 host 工具链取不到时,若 arch 与 os 与 host 相同,就用 target 工具
链,并发一条命名双方的告警。成功路径一字未改;此前的死路变成一次可解释的降级。

#367 记录的仍是根因(host 工具链选择缺 arch 轴),这条只是让它不再是绝路。
同一条 job 里,mcpp 用 "$m"(刚构建出来的)验证,紧接着的 xlings 构建却用
裸 `mcpp` —— 即已安装的那个。于是这一半从来没看见过 PR。

这正是上面那段注释为 clone ref 记录过的缺陷,只是位置往下挪了几行;暴露方式
也一样:一个**只在这条构建里出现**的 aarch64 失败,其修复在这里无法验证,因为
跑它的二进制早于该修复。

顺带两处:`m` 改为绝对路径(`cd /tmp/xlings-src` 之后还要用),并显式把
MCPP_HOME 传下去 —— mcpp 从**二进制所在位置**推导 home,放在 /tmp/mcpp-src
下的二进制否则会认领一个空 home,把整套生态重新自举一遍。
`ci-aarch64-fresh-install` 报 `download failed for xim:gcc@16.1.0: HTTP 404`。

**先前的归因是错的**:我判成「`[toolchain]` 缺 arch 轴」,并动手给 manifest 加了
一根选择器轴。生态其实**早就支持 aarch64** —— xlings-res 的 musl-gcc 发行版里
就有 `musl-gcc-16.1.0-linux-aarch64.tar.gz`,而 `xim:gcc` 声明的是
`archs = { "x86_64" }`。所以缺的不是让用户去写 arch,而是 mcpp 问错了包。

同一台机器上 target 一侧是对的:
`gcc@16.1.0 → aarch64-linux-musl → xim-x-musl-gcc`。因为 target 一侧把 triple
注入了 spec,`to_xim_package` 的 musl 分支便按「同一 target,两种载荷形态」选中
了原生包。而 build.mcpp 的 **host** 解析刻意不注入 target,于是落到最后那条
glibc 分支,拿到只有 x86_64 资产的 `gcc`。

「这个 spec 在这台机器上对应哪个载荷」正是 `to_xim_package` 存在的意义,上面
那条 musl 分支已经在回答它的另一半。所以修在这里:Linux 上瞄准本机的 GCC spec,
在非 x86_64 架构上解析到 `musl-gcc`。用户不该被要求去编码「某个工具链包是为
哪些架构构建的」。

同时撤回上一版那个「host 工具链装不上就回退到 target 工具链」的兜底 —— 它是
错误归因的产物;正确解析之后它是死代码,而一条失败路径上的隐式回退不该白留。

判定抽成自由函数并接收 host arch,而不是读编译期常量:这样 aarch64 的答案能在
x86_64 机器上被测到 —— 而「测一个手边没有的架构」正是重点。
09-release.md §5 已经为 3b1cb6b 记过一次这个更正,而本 PR 的 c1a3e45 又写了一遍
「指向索引里已经不存在的版本」。它是错的:openxlings/xim-pkgindex 与
d2learn/xim-pkgindex 都列着 63 个 mcpp 版本,`2026.8.5.3` 在里面。

值得注意的是 `available:` 实际列出的东西 —— 只有一个版本,正是那次 job 刚装上
的那个。这是「已安装版本」的形状,不是索引清单;而那一步解析的是 .xlings.json
的 **workspace** pin,workspace 作用域正是既有的那个陷阱。真实机制未坐实,不写
成结论。

pin 的 bump 本身没问题(§4 明确说这是合理的做法,而且它确实解开了那条 job)。
教训更窄,而且反复被重学:**在断言「索引删了它」之前,先读索引。** 一条 curl
就能定。
@Sunrisepeak
Sunrisepeak merged commit df4f75e into main Aug 6, 2026
19 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/359-provisions-and-glob-inputs branch August 6, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegen 类库的使用者体验:依赖声明从 4 条降到 1 条、proto 支持通配符(tools 传播 + 目录级 rerun)

2 participants