Class RemapMinecraftTaskRegistry
本表覆盖所有把产物写进**跨项目共享 maven 仓库**的任务——重映射任务(RemapMinecraftTask)、
处理链任务(ProcessMinecraftJarTask)、补出 pom/backup 的伴随任务
(WriteMinecraftJarSidecarsTask)与 mod 重映射任务(RemapModsTask,产物落在构建根的
remapped_mods 仓库,构建内所有项目共用同一个目录):MavenScope.GLOBAL 下是 Gradle 用户目录里的全局仓库
(同一台机器上的所有构建、所有工作树共用),MavenScope.LOCAL 下则是构建根目录下的仓库
(同一构建的多个子项目共用)。因此同一构建内两个配置相同的子项目会算出**同一条产物路径**——
处理链任务与伴随任务落在 LOCAL 仓库,同样受影响(它们的路径只由 MC 版本、映射标识、jar processor 哈希
与 jar 类型决定,与项目无关)。
既有实现靠「跨进程文件锁 + 原子落位」协调这件事,而不是靠「各任务各写各的产物」:同一条路径上本来就
允许出现多个生产者,谁先完成谁生效。改成任务后这条前提不再成立,而且失败是静默的——实测 Gradle 9.5 对
「不同项目的两个任务声明同一个 @OutputFile」既不报错也不排序,两个任务都会执行、后完成的覆盖
先完成的(本仓库 7436 class 的等价性对照正是建立在一份确定的产物上);只有在有人按该路径取产物时,Gradle 的
隐式依赖校验才会以「uses this output of task ... without declaring an explicit or implicit dependency」让构建失败。
也就是说:这个共享不是「可以容忍的重复」,而是必须显式建模的所有权关系。
本表如何建模
以产物路径为键做登记:第一次登记创建任务并成为该路径的**唯一生产者**,后续登记(同一构建内的其它项目) 只取回生产者的**任务路径**,不再创建第二个任务。消费方按该任务路径建依赖(dependsOn /
builtBy)时,Gradle 会把它解析成真正的跨项目任务依赖,从而满足上面那条隐式依赖校验;
只声明 mustRunAfter 不够,它只排序、不保证产物存在。
生产单位是**批次**时(RemapModsTask:一批 mod 必须整批重映射)用 claimAll(org.gradle.api.Project, java.util.List<java.nio.file.Path>, java.util.Map<java.lang.String, java.lang.String>, java.util.function.Function<java.util.List<java.nio.file.Path>, org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>>):
逐条判定、只接手尚无人认领的产物,任务只写出并只声明属于自己的那些文件。这样做而不是「按批次整体比对」,
是因为两个项目的批次往往只是部分重叠(多模块工程里各模块的 mod 集合不同),按批次比对要么漏判、
要么把重叠的那部分又变成两个生产者;批次本身也不因此被截断——任务仍按完整批次读取输入,
因此每条产物的字节与「谁先登记」无关。
为什么这张表必须跨 classloader 共享
同一构建里的各个项目未必由同一份 Loom 配置:约定插件/构建逻辑(included build、buildSrc)各自把 Loom 放到自己的 classpath 上时,不同项目拿到的是**不同 classloader 里的 Loom**。实测一个真实多模块工程 (146 个模块、两条 MC 版本车道)在一次构建里出现了三份 Loom classloader,其中“车道根项目”与“车道内的模块” 分属两份。按 classloader 分表就等于承认「同一条产物路径上有多个生产者」,于是两边各注册一个remapMinecraftNamedMerged 写同一个文件,消费方的隐式依赖校验直接把构建打挂——
这正是本表存在的理由,所以表必须落在**构建级**、且只放 classloader 无关的数据。
表放在 Gradle 的扩展属性里(gradle.ext):ExtraPropertiesExtension 是 Gradle
核心对象,同一构建的各个 classloader 拿到的是同一个实例;表内只允许出现
Map<String, Map<String, String>> 这类 JDK 类型——若表里放本 classloader 定义的对象,
另一份 classloader 取回来时会在强制转换处抛 ClassCastException(同一个类名在两份 classloader 里
不是同一个类型)。也正是这个理由让 RemapMinecraftTaskRegistry.Producer 只带任务**路径**而不是 TaskProvider。
为什么要比对输入指纹,而不是只按路径共享
复用同一个任务实例意味着第二个请求方拿到的是第一个请求方那次生产的产物。若两者输入不同(例如只有其中一个 项目改过loom.knownIndyBsms、中间映射开关或 classpath),共享就会把 A 的产物当成 B 的产物——
这正是配置期旧路径的静默行为(第二个项目会被 JarReusability 判为「已就绪」而直接复用),
换成任务后必须变成可见的失败。因此每个请求方都带一份自己的输入指纹,指纹不同即拒绝共享并指出差异项。
跨构建的那一半不在这里
本表的作用域是一次 Gradle 调用(同一构建的多个项目),覆盖不了「另一个工作树 / 另一个 daemon 正在写同一条 路径」:那一半仍由@OutputFile 的 up-to-date 判定与产物的原子落位承担(见
AtomicFiles)。本表只解决「同一构建内不该有两个生产者」,
且不改变任何已有产物的路径。-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final record一条共享产物的生产位置. -
Method Summary
Modifier and TypeMethodDescriptionclaim(org.gradle.api.Project project, Path artifact, Map<String, String> identity, Supplier<org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>> registrar) 登记一条产物路径的生产请求,返回该产物的唯一生产者.claimAll(org.gradle.api.Project project, List<Path> artifacts, Map<String, String> identity, Function<List<Path>, org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>> registrar) 登记一批产物路径的生产请求,返回**每一条**请求路径的生产者.
-
Method Details
-
claim
public static RemapMinecraftTaskRegistry.Producer claim(org.gradle.api.Project project, Path artifact, Map<String, String> identity, Supplier<org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>> registrar) 登记一条产物路径的生产请求,返回该产物的唯一生产者.第一次登记时调用
registrar创建任务;后续登记的路径相同且identity完全一致时 直接返回既有生产者的任务路径,registrar不会被调用(因此不会产生第二个「同一产物的生产者」)。- Parameters:
project- 发起登记的项目artifact- 产物路径(本任务的 jar 输出);路径本身相同即视为同一个构件identity- 本次登记的输入指纹,见类注释;键是输入名、值是它的可比较表示,值不得为null, 且键不得以@开头(那是本表的保留键)registrar- 创建任务的回调,仅在本次登记成为生产者时调用- Returns:
- 该产物的唯一生产者;产物路径已归一化为绝对路径
- Throws:
IllegalStateException- 同一构建内同一路径已被另一组输入登记为产物时
-
claimAll
public static Map<Path,RemapMinecraftTaskRegistry.Producer> claimAll(org.gradle.api.Project project, List<Path> artifacts, Map<String, String> identity, Function<List<Path>, org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>> registrar) 登记一批产物路径的生产请求,返回**每一条**请求路径的生产者.与
claim(org.gradle.api.Project, java.nio.file.Path, java.util.Map<java.lang.String, java.lang.String>, java.util.function.Supplier<org.gradle.api.tasks.TaskProvider<? extends org.gradle.api.Task>>)唯一的差别是「一次登记覆盖多条产物」。需要它是因为有些生产单位天然是批量的: mod 重映射必须整批进行(跨 mod 的类型上下文不能拆开逐个重映射,否则结果与既有实现不等价), 而这条批次里的每一条产物路径在别的项目里都可能已经被登记。逐条判定、只接手尚无人认领的那些,并且**只对认领到的产物负责**:任务仍然按完整批次读取输入 (这样每一条产物的字节与「谁先登记」无关),但只写出、也只声明属于自己的那些产物。 于是同一路径在同一构建内始终只有一个生产者,而批次本身不会因为共享而被截断。
- Parameters:
project- 发起登记的项目artifacts- 本批次会写出的全部产物路径identity- 本批次的输入指纹(见类注释);批内每条产物相同,且不得含对象身份registrar- 创建任务的回调,仅在存在无人认领的产物时调用一次,参数是**本任务拥有的产物路径**- Returns:
- 入参中每条路径(已归一化)的生产者;已被别人登记的返回既有的那个
- Throws:
IllegalStateException- 同一构建内同一路径已被另一组输入登记为产物时
-