Class ForgePrePatchJarReuse
为什么需要它
generateForgePrePatchJar 的产物落在跨项目共享的 <userCache>/<mcVer>/<platform>/<forgeVer>/,
属于「执行前就已存在、又不是本任务创建的」输出。Gradle 对这种输出的口径是
Gradle does not know how file … was created:构建缓存被直接关掉,up-to-date 也无法在跨工作树 / 跨
loom 版本的场景下稳定成立。而这条链是全仓库最贵的动作之一(downloadClient / extractServer /
mergeMappings / rename / mergetool / binpatch 等十余步外部进程,单步
extractServer 实测 3~4 分钟),于是「产物明明就在盘上、内容也对」时仍会重跑十几分钟。
本类就是那条 action 的前置判据:产物存在、可作为 jar 打开、且产出它的输入与本次输入逐项相同时 直接返回。判据纯读(stat + 小文件的内容摘要),不打开、不解压、不改写产物。
身份从哪来:旁车文件
MCP 链产出的 jar 里没有任何「我是用哪批输入做的」标记(META-INF/MANIFEST.MF 在绝大多数
1.16+ 版本上根本不存在),而往产物里写清单会改变产物的字节——那正是本仓库逐字节等价性测试所钉住的东西,
不能动。因此身份记在产物旁边的旁车文件里(<产物目录>/.identity/<产物名>.key),与
MappingsMigratorCache 对迁移器缓存的做法一致:旁车缺失、无法解析、键不符、产物与旁车不同代,
一律落到「不可复用」——方向永远是安全的(最多多跑一次链),绝不会把别代的产物当成这代的。
旁车只由生产成功之后写入(record(java.nio.file.Path, java.lang.String, org.gradle.api.logging.Logger)),判据本身(tryReuse(java.nio.file.Path, java.lang.String, org.gradle.api.logging.Logger))不写任何文件;
因此「判据会不会悄悄改产物」这个问题在结构上不存在。
为什么这个判据不会让用户拿到错误的 jar
键由两部分拼成,两者都必须逐字相等才复用:
- 身份前缀(配置期算好的纯值):链路算法版本(
Loom-Patch-Version)、分支 (mcp-rename/mcp-unobfuscated/neoforge-installer-tools)、平台、 Minecraft 版本、Forge 版本、side(client/server/merged)、中间命名空间。这些量决定「该产出什么」; - 输入指纹:决定产物内容的每一件输入文件(MCPConfig 的 zip 与解包出的
config.json、 合并后的 srg 映射、vanilla client/server jar、installer tools 的工具 classpath)。
于是「换 Forge 版本但旧产物还在」被两道锁挡住:新版本的产物路径本身就不同
(…/forge/<forgeVer>/minecraft-<type>-<ns>.jar),而即便有人把旧产物搬到新路径上,身份前缀里的
forge= 与输入指纹(新版本的 vanilla jar / MCPConfig 与旧版本不是同一批文件)都不会相等,
判据落回「照常执行」。同理,换 MC 版本、改 side、切 NeoForge、换 MCPConfig(步进集合或工具版本变了)都会
使键失效。
反方向(漏判导致不该跳过时跳过)只有一个入口:某件输入内容变了但 stat 与摘要都没变。大文件
(vanilla jar、srg 映射)取「大小 + 最后修改时间」,小文件(config.json 这类决定步进集合与工具版本的
清单)取内容摘要。这条取舍的依据与 SpecStore.inputIdentity 同源:链上的输入一律由下载器或
原子落位写入,不会出现「大小与时间不变而内容改变」;而每次构建真去哈希 25~50MB 的 vanilla jar,等于把
「便宜」这个前提丢掉。
产物自身也被钉住
旁车同时记下写入那一刻产物的大小与修改时间。产物后来被别的进程(另一个 loom 版本、另一棵工作树) 改写时两者不符,本判据判「不可复用」并照常执行——这是唯一会让复用失效的常见情形,代价是多跑一次链, 换来的是一直站在「宁可重跑」这一侧。
-
Field Details
-
FORMAT
判据的格式版本.键的组成、旁车语义或「产品是否同代」的口径一旦变化就递增它:旧旁车随即全部失效并自动重跑一次, 无需人工清理共享缓存目录。
- See Also:
-
-
Method Details
-
fingerprint
把身份前缀与各输入文件的指纹拼成本次执行的键.输入按绝对路径排序后参与拼接:调用方给的是集合语义(文件集合的迭代顺序不适合当键),而排序保证 同样的输入集合永远得到同样的键。
- Parameters:
identity- 身份前缀(配置期算好的纯值,见类注释)inputs- 决定产物内容的输入文件- Returns:
- 本次执行的键;有输入不可读时带
incomplete标记,永不与记录过的键相等
-
tryReuse
public static boolean tryReuse(Path output, String expectedKey, org.gradle.api.logging.Logger logger) 判据本体:产物是否已经就绪且与expectedKey同代.纯读。命中时打一条 lifecycle 级日志(Gradle 默认可见)——「这一轮没花十几分钟」必须让用户看得见; 未命中且产物存在时同样打 lifecycle,因为「产物明明在,为什么又跑了整条链」正是最难自查的那一类问题 (与
JarReusability的播报同一口径)。- Parameters:
output- 产物(pre-patch jar)expectedKey- 本次执行的键,见fingerprint(java.lang.String, java.util.List<java.nio.file.Path>)logger- 日志出口- Returns:
- 可以复用(即本次 action 应当直接返回)时为
true
-
record
生产成功后把本次键与产物指纹写进旁车.只在生产成功之后调用(调用点见
GenerateForgePrePatchJarTask):判据是纯读的,写只发生在「这一轮 确实产出了产物」之后,于是旁车永远描述一份真实存在的产物。写入失败只记 info 不抛——旁车缺失的后果只是下次 多跑一遍链,不该让构建失败。- Parameters:
output- 产物(已落位)expectedKey- 本次执行的键logger- 日志出口
-
sidecar
Returns 产物对应的旁车文件路径..- Returns:
- 产物对应的旁车文件路径
-