Class ForgePrePatchJarReuse

java.lang.Object
net.fabricmc.loom.pipeline.ForgePrePatchJarReuse

public final class ForgePrePatchJarReuse extends Object
「pre-patch jar 已就绪且与本次输入同代」的判据:命中即跳过整条 MCP 链.

为什么需要它

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

键由两部分拼成,两者都必须逐字相等才复用:

  1. 身份前缀(配置期算好的纯值):链路算法版本(Loom-Patch-Version)、分支 (mcp-rename / mcp-unobfuscated / neoforge-installer-tools)、平台、 Minecraft 版本、Forge 版本、side(client/server/merged)、中间命名空间。这些量决定「该产出什么」;
  2. 输入指纹:决定产物内容的每一件输入文件(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 Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    判据的格式版本.
  • Method Summary

    Modifier and Type
    Method
    Description
    static String
    fingerprint(String identity, List<Path> inputs)
    把身份前缀与各输入文件的指纹拼成本次执行的键.
    static void
    record(Path output, String expectedKey, org.gradle.api.logging.Logger logger)
    生产成功后把本次键与产物指纹写进旁车.
    static Path
    sidecar(Path output)
    Returns 产物对应的旁车文件路径.
    static boolean
    tryReuse(Path output, String expectedKey, org.gradle.api.logging.Logger logger)
    判据本体:产物是否已经就绪且与 expectedKey 同代.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • FORMAT

      public static final String FORMAT
      判据的格式版本.

      键的组成、旁车语义或「产品是否同代」的口径一旦变化就递增它:旧旁车随即全部失效并自动重跑一次, 无需人工清理共享缓存目录。

      See Also:
  • Method Details

    • fingerprint

      public static String fingerprint(String identity, List<Path> inputs)
      把身份前缀与各输入文件的指纹拼成本次执行的键.

      输入按绝对路径排序后参与拼接:调用方给的是集合语义(文件集合的迭代顺序不适合当键),而排序保证 同样的输入集合永远得到同样的键。

      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

      public static void record(Path output, String expectedKey, org.gradle.api.logging.Logger logger)
      生产成功后把本次键与产物指纹写进旁车.

      只在生产成功之后调用(调用点见 GenerateForgePrePatchJarTask):判据是纯读的,写只发生在「这一轮 确实产出了产物」之后,于是旁车永远描述一份真实存在的产物。写入失败只记 info 不抛——旁车缺失的后果只是下次 多跑一遍链,不该让构建失败。

      Parameters:
      output - 产物(已落位)
      expectedKey - 本次执行的键
      logger - 日志出口
    • sidecar

      public static Path sidecar(Path output)
      Returns 产物对应的旁车文件路径..
      Returns:
      产物对应的旁车文件路径