Class RemapMinecraftTask
- All Implemented Interfaces:
Comparable<org.gradle.api.Task>,org.gradle.api.internal.DynamicObjectAware,org.gradle.api.internal.TaskInternal,org.gradle.api.Named,org.gradle.api.plugins.ExtensionAware,org.gradle.api.Task,org.gradle.util.Configurable<org.gradle.api.Task>
这是「把 mapped jar 的生产移出配置期」的落点。它取代的是
AbstractMappedMinecraftProvider.provide() 里那段配置期同步执行的
「判断是否需要重建 → 取跨进程锁 → 重映射 → 落位」。
| 旧路径 | 本任务 | |
|---|---|---|
| 是否需要重建 | shouldRefreshOutputs(配置期开 zipfs 逐产物判定) |
Gradle up-to-date 检查 |
| 并发保护 | 跨进程文件锁 + 锁内二次确认 | 任务图 + 构建缓存 |
| 产物损坏 | JarReusability 手工判残骸 | 构建缓存重新恢复 |
| 执行时机 | 配置期 afterEvaluate | 执行期 |
为什么 remapper 由 service 装配而不是本类自建
remapper 的构造需要映射服务、已知 BSB、以及第三方 remapper 扩展 (RemapperExtensionHolder)。这些在既有实现里统一由
TinyRemapperService 装配,并通过 @Nested 的 Options 序列化进任务输入。
本类沿用该路径,以避免出现「两套构造逻辑产生不同 remapper」的分叉。
实际的重映射 I/O 交给 L4 的 MinecraftJarRemap——它不认识 Gradle,
且已保留「原子发布 + 发布终态」的契约。
配置期的钩子与分支如何变成输入
配置期AbstractMappedMinecraftProvider 有三处语义不在「输入 jar + 映射」之内。
本任务用三个 @Input 显式承载它们,而不是在任务里重新判断——任务只拿到一对路径,
平台配置(是否 NeoForge、是否是 split 形态的 client-only jar)它无从得知:
configureRemapper钩子的效果 →getInjectClientSidedVisitor()。 四处覆写(各 provider 的SplitImpl)都只给 client-only jar 追加@Environment(CLIENT),故可收敛成一个布尔值。disableObfuscation下remapJar走原子复制而非重映射 →getCopyOnly()。remapJar把isNeoForge()当作injectMixinExtension交给 L4 →getInjectMixinExtension()。它与forgeLike不同源,理由见该输入的说明。
false,即「重映射 + 无额外 remapper 配置」——与本次改造前任务的唯一行为
逐位一致;需要非缺省取值的接线路径必须显式设置。
产物落位:三个具体文件,绝不声明目录
一个RemappedJars 对应 maven 仓库里的同一个构件目录,该目录里属于本任务的文件有三个:
getOutputJar()——重映射产物本身;getOutputPom()——同名的.pom,由LocalMavenHelper.savePom()写出 (本任务不另行生成内容,理由见该输出的说明),缺失会让坐标式依赖解析失败;getOutputBackupJar()——jar 的逐字节副本,是 genSources 的@Classpath输入, 缺失会让GenerateSourcesTask直接抛错;provider 不需要备份时(中间映射产物不会被反编译) 不设置该输出,此时本任务不产出它。
@OutputFile,本任务不声明任何 @OutputDirectory:构件目录位于共享 maven
仓库,仓库根与版本目录里还有别的 provider、别的 MC 版本的产物,把它们纳入本任务的快照与清理范围会互相破坏
(既有实现正是为此在「按前缀扫描清理同目录」上踩过坑)。
该共享仓库决定了「同一条产物路径上不该有两个生产者」:MavenScope.GLOBAL 下路径跨项目、跨工作树
共享。这件事不在本任务里协调——由 RemapMinecraftTaskRegistry 在同一构建内保证唯一生产者,
跨构建则由 @OutputFile 的 up-to-date 判定与原子落位承担。
-
Nested Class Summary
Nested classes/interfaces inherited from interface org.gradle.api.Named
org.gradle.api.Named.Namer -
Field Summary
Fields inherited from interface org.gradle.api.Task
TASK_ACTION, TASK_CONSTRUCTOR_ARGS, TASK_DEPENDS_ON, TASK_DESCRIPTION, TASK_GROUP, TASK_NAME, TASK_OVERWRITE, TASK_TYPE -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionabstract org.gradle.api.provider.Property<String> 注解重映射数据的 JSON;为空表示无.abstract org.gradle.api.provider.Property<Boolean> 是否只做原子复制、不重映射(直通模式).abstract org.gradle.api.provider.Property<Boolean> 是否修复 record 组件(Java 16+ 目标需要).abstract org.gradle.api.provider.Property<Boolean> 是否 Forge/NeoForge 系,影响内部类重映射.abstract org.gradle.api.file.RegularFilePropertyReturns 补充翻译用的 Forge 参考 jar(named 命名空间),与getForgeSrgJar()成对.abstract org.gradle.api.file.RegularFilePropertyReturns 补充翻译用的 Forge 参考 jar(srg 命名空间),未设置时不做补充翻译.abstract org.gradle.api.provider.Property<String> 源命名空间.abstract org.gradle.api.provider.Property<Boolean> 是否给产物追加@Environment(CLIENT)注解(split jar 的 client-only 那一个).abstract org.gradle.api.provider.Property<Boolean> 是否向 remapper 注入 mixin 扩展(NeoForge 系需要).abstract org.gradle.api.provider.SetProperty<String> Forge 系内部类重映射所需的类名集合:显式覆盖项,接线侧通常留空.abstract org.gradle.api.file.RegularFileProperty待重映射的 jar.abstract org.gradle.api.provider.SetProperty<String> 已知的 indy BSM 集合.abstract org.gradle.api.provider.Property<TinyMappingsService.Options> 映射服务配置;执行期据此取出映射树.abstract org.gradle.api.provider.Property<String> object holder 改写的类名;为空表示不做该步骤.abstract org.gradle.api.provider.Property<String> object holder 改写所用的源命名空间;为空表示不做该步骤.abstract org.gradle.api.provider.Property<String> object holder 改写所用的目标命名空间.abstract org.gradle.api.file.RegularFilePropertyjar 的逐字节副本,作为 genSources 的输入.abstract org.gradle.api.file.RegularFileProperty产物.abstract org.gradle.api.file.RegularFileProperty产物的 pom:maven 仓库里与 jar 同名同目录、只有扩展名不同的那个文件.abstract org.gradle.api.provider.Property<String> pom 的 group;只参与 pom 内容的生成与落位.abstract org.gradle.api.provider.Property<File> pom 所在的 maven 仓库根.abstract org.gradle.api.provider.Property<String> pom 的 artifact 名,与构件 jar 的主名相同;可选性同getPomGroup().abstract org.gradle.api.provider.Property<String> pom 的版本,即「MC 版本 + 映射标识」;可选性同getPomGroup().abstract org.gradle.api.file.ConfigurableFileCollection重映射 classpath(通常是上一阶段的产物 + 编译期库).签名修复表;为空表示无需修复.abstract org.gradle.api.provider.Property<String> 目标命名空间.abstract org.gradle.api.provider.Property<Boolean> 是否校验目标命名空间存在.static PathpomPathFor(Path artifactJar) Returns 与产物 jar 同名同目录、只有扩展名不同的 pom 路径.voidremap()Methods inherited from class org.gradle.api.DefaultTask
compareTo, configure, dependsOn, doFirst, doFirst, doFirst, doLast, doLast, doLast, finalizedBy, getActions, getAnt, getDependsOn, getDescription, getDestroyables, getDidWork, getEnabled, getExtensions, getFinalizedBy, getGroup, getInputs, getLocalState, getLogger, getLogging, getMustRunAfter, getName, getOutputs, getPath, getProject, getShouldRunAfter, getState, getTaskDependencies, getTemporaryDir, getTimeout, hasProperty, mustRunAfter, onlyIf, onlyIf, onlyIf, property, setActions, setDependsOn, setDescription, setDidWork, setEnabled, setFinalizedBy, setGroup, setMustRunAfter, setOnlyIf, setOnlyIf, setOnlyIf, setProperty, setShouldRunAfter, shouldRunAfter, usesServiceMethods inherited from class org.gradle.api.internal.AbstractTask
acceptServiceReferences, appendParallelSafeAction, doNotTrackState, doNotTrackStateIf, getAsDynamicObject, getIdentityPath, getImpliesSubProjects, getLifecycleDependencies, getOnlyIf, getReasonNotToTrackState, getReasonsNotToTrackState, getReasonTaskIsIncompatibleWithConfigurationCache, getRequiredServices, getServices, getSharedResources, getStandardOutputCapture, getTaskActions, getTaskIdentity, getTemporaryDirFactory, hasTaskActions, injectIntoNewInstance, isCompatibleWithConfigurationCache, isEnabled, isHasCustomActions, notCompatibleWithConfigurationCache, prependParallelSafeAction, restoreOnlyIf, restoreTaskActions, setImpliesSubProjectsMethods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface org.gradle.api.Task
doNotTrackState, notCompatibleWithConfigurationCache
-
Constructor Details
-
RemapMinecraftTask
public RemapMinecraftTask()
-
-
Method Details
-
getCopyOnly
是否只做原子复制、不重映射(直通模式).开启后本任务与配置期
AbstractMappedMinecraftProvider.remapJar在disableObfuscation分支下的行为一致:不改写字节码,只把输入 jar 原子复制到产物位置; 不开启则走重映射。两种产物不同,任务自身无从分辨(它只有一对输入/输出路径), 因此该判断只能由掌握平台配置的接线侧给出。为什么用 AtomicFiles 而不是裸 Files.copy
产物位于跨进程、跨 daemon 共享的 maven 仓库,落位必须原子:否则读方会读到半写的 jar, 而内容判定只能发现截断、发现不了「完整但没写完」。这条契约是仓库既有的 (见AtomicFiles的类注释),复制模式同样受它约束。与其它输入的关系
复制模式下不读取映射、也不装配 remapper 扩展,但任务协议不变:映射、命名空间等输入照常由接线侧 提供(它们是任务身份的一部分,不该随模式变化而消失)。 -
getInputJar
@InputFile @PathSensitive(NONE) public abstract org.gradle.api.file.RegularFileProperty getInputJar()待重映射的 jar. -
getRemapClasspath
@Classpath public abstract org.gradle.api.file.ConfigurableFileCollection getRemapClasspath()重映射 classpath(通常是上一阶段的产物 + 编译期库). -
getMappingsServiceOptions
public abstract org.gradle.api.provider.Property<TinyMappingsService.Options> getMappingsServiceOptions()映射服务配置;执行期据此取出映射树.刻意用
TinyMappingsService.Options而不是别处的映射来源:配置期的AbstractMappedMinecraftProvider.remapJar正是通过MappingConfiguration.getMappingsService(project, serviceFactory, MappingOption.forPlatform(extension))拿到这一份映射树。换用其它来源(例如MappingsService.createOptionsWithProjectMappings) 会解析到不同的映射文件,产出与配置期不等价——这正是等价性测试发现的问题。 -
getFromNamespace
源命名空间. -
getToNamespace
目标命名空间. -
getFixRecords
是否修复 record 组件(Java 16+ 目标需要). -
getForgeLike
是否 Forge/NeoForge 系,影响内部类重映射. -
getForgeSrgJar
@Optional @InputFile @PathSensitive(NONE) public abstract org.gradle.api.file.RegularFileProperty getForgeSrgJar()Returns 补充翻译用的 Forge 参考 jar(srg 命名空间),未设置时不做补充翻译..与
getForgeNamedJar()成对使用:两者都设置时,重映射落位后会把产物里 「指向 Forge 类的成员引用」按两份 jar 的成员名差异再翻译一次。为什么需要这一步
TinyRemapper 只按映射表翻译。legacy Forge(1.12.2)的 Forge 类不在 MC 映射表内, 于是 MC 类里那些「Forge 补丁新增的、指向 Forge 成员的引用」保持 srg 名(如ModelLoader.a),而编译期类路径上的 forge jar 是 named 形(setupModelRegistry), 运行期就会 AbstractMethodError。补充翻译不依赖 MC 映射表,只依赖 Forge 自身两版 jar 的成员名差异。只有 legacy Forge 同时存在「universal(srg)+ named」两份 jar;现代 Forge/NeoForge 车道 拿不到可对比的两份,故该输入在这些车道上不会被设置,工序自动不执行。
- Returns:
- 补充翻译用的 Forge 参考 jar(srg 命名空间),未设置时不做补充翻译
-
getForgeNamedJar
@Optional @InputFile @PathSensitive(NONE) public abstract org.gradle.api.file.RegularFileProperty getForgeNamedJar()Returns 补充翻译用的 Forge 参考 jar(named 命名空间),与getForgeSrgJar()成对..- Returns:
- 补充翻译用的 Forge 参考 jar(named 命名空间),与
getForgeSrgJar()成对
-
getInjectMixinExtension
是否向 remapper 注入 mixin 扩展(NeoForge 系需要).对应配置期
remapJar传给 L4 的extension.isNeoForge():为真时 L4 会往 remapper 上挂new MixinExtension(inputTag -> true)。该扩展只作用于「带 mixin 注解的类」: 硬目标(@Shadow/@Overwrite/@Accessor/@Invoker/@Implements) 用于解析目标成员,软目标(@Mixin/@Inject/@At等注解里的字符串)用于改写 注解内的类名与方法描述符;它不处理 mixin 配置 JSON,也不生成 refmap。与 forgeLike 不是同一件事
getForgeLike()是isForgeLike()(影响冲突忽略策略与内部类映射),本输入是isNeoForge()。NeoForge 是 forgeLike 的真子集:「Forge 但非 NeoForge」这一支两者取值不同, 故本输入无法由getForgeLike()推导,必须独立承载。它的实际影响随 NeoForge 版本而变
实测:NeoForge 21.0.77-beta 的 jar 内含net/neoforged/neoforge/mixins/*访问器 mixin (类常量池里带 mixin 注解描述符),NeoForge 自身的类又会被并进 MC jar,故这一支上该扩展有实际作用; 而 NeoForge 20.2.93 及更早的 jar 里不存在任何 mixin 注解,此时它是空操作。因为作用随版本而变、 不能假定为零,任务必须把它当输入承载,不能写死false。缺省 false 的边界
缺省false与 Fabric、Forge 路径的配置期取值一致;NeoForge 系接线时必须显式设为true,否则 NeoForge 自身那些 mixin 类在重映射后会与配置期产物不同。 -
getInjectClientSidedVisitor
是否给产物追加@Environment(CLIENT)注解(split jar 的 client-only 那一个).对应配置期的
configureRemapper钩子。该钩子是实例方法、效果不可序列化, 但四处覆写(intermediary/named/srg/mojang 的SplitImpl)都只做一件事——给 client-only 的 jar 追加SidedClassVisitor.CLIENT——因此它的效果可收敛为一个布尔值。装配路径与配置期同源
为真时任务挂的是SidedClassVisitor.CLIENT,即configureSplitRemapper与各覆写 使用的同一个常量,走同一条extraPostApplyVisitor装配,不存在第二套 visitor 构造逻辑。缺省 false 的边界
缺省false:对 merged/single jar 就是配置期的取值(基类钩子是空实现)。split 形态下 只有 client-only 那个 jar 的任务需要设为true;漏设会让客户端产物缺少该注解——产物仍能 构建成功,只是语义与配置期不同,故接线时必须按 provider 形态显式设置。 -
getValidateTargetNamespace
是否校验目标命名空间存在. -
getInnerClassNames
Forge 系内部类重映射所需的类名集合:显式覆盖项,接线侧通常留空.缺省由本任务在执行期现算
未设置时,本任务在remap()里由getInputJar()与getForgeLike()现算: Forge 系取InnerClassRemapper.readClassNames(inputJar),非 Forge 系取空集。接线侧 (AbstractMappedMinecraftProvider.registerRemapTask)因此**不设置本属性**。为什么不能由接线侧挂惰性 provider
曾经的做法是接线侧 set 一个inputJar.zip(forgeLike, …)惰性 provider,意图是「配置期不打开 输入 jar」。但SetProperty是 Gradle 托管属性,**配置缓存写入任务状态时会求值并序列化它**: 读 jar 于是从配置期挪到了配置缓存序列化期——那仍然是配置期,而且此时输入 jar 的产出任务尚未运行, 冷缓存下直接抛NoSuchFileException,使整次配置缓存写入失败(Forge 全链用例在 CI 上正是这样 成片失败的)。只有让该值**不经过任何任务属性**,读取才真正推迟到执行期。改判
本集合是(@Internal不降低判别力getInputJar()的内容、getForgeLike())的纯函数,而这一对判据本身已是本任务 的输入(前者是内容哈希的@InputFile,后者是@Input):同一对取值必然算出同一个集合。 故它无须再登记为输入,up-to-date 判定与构建缓存键的判别力不变。显式设置仍然有效
不经接线侧、直接注册本任务的场景(例如与配置期做对照的探针)可显式设置本属性以覆盖现算结果。 所设的值会照常参与配置缓存序列化,因此必须是普通值,不得是读文件的惰性 provider。 -
getKnownIndyBsms
已知的 indy BSM 集合. -
getSignatureFixes
签名修复表;为空表示无需修复. -
getAnnotationsJson
注解重映射数据的 JSON;为空表示无. -
getObjectHolderClassName
@Input @Optional public abstract org.gradle.api.provider.Property<String> getObjectHolderClassName()object holder 改写的类名;为空表示不做该步骤. -
getObjectHolderSourceNamespace
@Input @Optional public abstract org.gradle.api.provider.Property<String> getObjectHolderSourceNamespace()object holder 改写所用的源命名空间;为空表示不做该步骤. -
getObjectHolderTargetNamespace
object holder 改写所用的目标命名空间. -
getOutputJar
@OutputFile public abstract org.gradle.api.file.RegularFileProperty getOutputJar()产物. -
getOutputPom
@Optional @OutputFile public abstract org.gradle.api.file.RegularFileProperty getOutputPom()产物的 pom:maven 仓库里与 jar 同名同目录、只有扩展名不同的那个文件.内容与落位都由
LocalMavenHelper.savePom()决定——本任务按坐标调用它,不另行拼模板、 也不另写一份格式,因此与配置期写出的 pom 逐字节一致。缺失它会让按坐标 (net.minecraft:<name>:<version>)解析该构件的依赖直接失败:pom 是坐标式解析的必要条件, 不是可选的附产物。为什么坐标是输入而不是从输出路径反推
pom 的内容只由group/name/version决定,是这三个值的函数,因此三者必须是@Input, 否则改坐标不会让本任务失效。getPomMavenRoot()只决定落位、不决定内容,故为@Internal。可选性的边界
未设置表示本次不产出 pom。经registerRemapTask接线时必定设置(配置期两条分支都会写 pom); 直接注册本任务的场景(例如与配置期做等价性对照的探针)不设置它,因此这里保持可选而非必填。 一旦设置了它,getPomGroup()等坐标就必须同时给出,否则执行期直接失败——见remap()。 -
getPomGroup
pom 的 group;只参与 pom 内容的生成与落位.@Optional的理由见getOutputPom()的「可选性的边界」:只有声明了 pom 输出时 才需要坐标,未声明时留着默认空值。执行期对「声明了输出却缺坐标」的校验在remap()。 -
getPomName
pom 的 artifact 名,与构件 jar 的主名相同;可选性同getPomGroup(). -
getPomVersion
pom 的版本,即「MC 版本 + 映射标识」;可选性同getPomGroup(). -
getPomMavenRoot
pom 所在的 maven 仓库根.只用于让
LocalMavenHelper复算落位(它把 group、name、version 展开成目录,本任务不复算 这套布局规则),不参与 pom 内容的判定,故为@Internal:同一坐标落在哪个仓库根,pom 的内容都一样。 它与声明出来的getOutputPom()是否指向同一处,执行期会校验。 -
getOutputBackupJar
@Optional @OutputFile public abstract org.gradle.api.file.RegularFileProperty getOutputBackupJar()jar 的逐字节副本,作为 genSources 的输入.genSources 按
AbstractMappedMinecraftProvider.getBackupJarPath找这个文件 (jar 路径加上.backup),找不到就直接抛错;配置期也是先出 jar、再复制出 backup, 且 backup 被当作「产物齐了」的标志(见createBackupJars的注释)。为什么用 AtomicFiles 而不是裸 Files.copy
副本与正本同处跨进程共享的 maven 仓库,落位必须原子,否则读方会读到半写的 backup 而误判产物就绪。 这条契约与getCopyOnly()的复制模式同源,见AtomicFiles的类注释。可选性的边界
未设置表示本 provider 不需要 backup(中间映射产物不会被反编译,见AbstractMappedMinecraftProvider.requiresBackupJars)。设置之后本任务按 jar 的逐字节副本写出, 并在执行期校验路径确实是<jar>.backup,避免写出一份没人读的副本而让 genSources 找不到输入。 -
remap
- Throws:
IOException
-
pomPathFor
Returns 与产物 jar 同名同目录、只有扩展名不同的 pom 路径..LocalMavenHelper用同一对name/version计算构件的 jar 与 pom (jar 是%s-%s.jar、pom 是%s-%s.pom),两者文件名主干完全相同,只差扩展名。 因此「pom 是 jar 的同名兄弟」这一条就足以确定 pom 位置,不需要在任务侧复算 maven 目录布局 (group 的点号转斜杠等规则仍只由 LocalMavenHelper 负责)。只认构件可能的扩展名(
LocalMavenHelper.copyToMaven(java.nio.file.Path, java.lang.String)接受的.jar/.zip): 若按「最后一个点」盲切,像minecraft-merged-1.20.1-yarn这种没有扩展名、但名字里带版本号点号的 路径会被切出一个看似合理却错误的 pom 名——那会让产物写到没人读的位置,且不报错。- Parameters:
artifactJar- 构件 jar 的路径- Returns:
- 与产物 jar 同名同目录、只有扩展名不同的 pom 路径
-