Class RemapMinecraftTask

java.lang.Object
org.gradle.api.internal.AbstractTask
org.gradle.api.DefaultTask
net.fabricmc.loom.pipeline.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>

@CacheableTask public abstract class RemapMinecraftTask extends org.gradle.api.DefaultTask
L3 流水线:把 Minecraft jar 重映射到目标命名空间.

这是「把 mapped jar 的生产移出配置期」的落点。它取代的是 AbstractMappedMinecraftProvider.provide() 里那段配置期同步执行的 「判断是否需要重建 → 取跨进程锁 → 重映射 → 落位」。

旧路径 vs 本任务
旧路径本任务
是否需要重建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 仓库里的同一个构件目录,该目录里属于本任务的文件有三个:
  1. getOutputJar()——重映射产物本身;
  2. getOutputPom()——同名的 .pom,由 LocalMavenHelper.savePom() 写出 (本任务不另行生成内容,理由见该输出的说明),缺失会让坐标式依赖解析失败;
  3. 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
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    abstract 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.RegularFileProperty
    Returns 补充翻译用的 Forge 参考 jar(named 命名空间),与 getForgeSrgJar() 成对.
    abstract org.gradle.api.file.RegularFileProperty
    Returns 补充翻译用的 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.RegularFileProperty
    jar 的逐字节副本,作为 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.MapProperty<String,String>
    签名修复表;为空表示无需修复.
    abstract org.gradle.api.provider.Property<String>
    目标命名空间.
    abstract org.gradle.api.provider.Property<Boolean>
    是否校验目标命名空间存在.
    static Path
    pomPathFor(Path artifactJar)
    Returns 与产物 jar 同名同目录、只有扩展名不同的 pom 路径.
    void
     

    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, usesService

    Methods 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, setImpliesSubProjects

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface org.gradle.api.Task

    doNotTrackState, notCompatibleWithConfigurationCache
  • Constructor Details

    • RemapMinecraftTask

      public RemapMinecraftTask()
  • Method Details

    • getCopyOnly

      @Input public abstract org.gradle.api.provider.Property<Boolean> 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

      @Input public abstract org.gradle.api.provider.Property<String> getFromNamespace()
      源命名空间.
    • getToNamespace

      @Input public abstract org.gradle.api.provider.Property<String> getToNamespace()
      目标命名空间.
    • getFixRecords

      @Input public abstract org.gradle.api.provider.Property<Boolean> getFixRecords()
      是否修复 record 组件(Java 16+ 目标需要).
    • getForgeLike

      @Input public abstract org.gradle.api.provider.Property<Boolean> 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

      @Input public abstract org.gradle.api.provider.Property<Boolean> 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

      @Input public abstract org.gradle.api.provider.Property<Boolean> 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

      @Input public abstract org.gradle.api.provider.Property<Boolean> getValidateTargetNamespace()
      是否校验目标命名空间存在.
    • getInnerClassNames

      @Internal public abstract org.gradle.api.provider.SetProperty<String> 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

      @Input public abstract org.gradle.api.provider.SetProperty<String> getKnownIndyBsms()
      已知的 indy BSM 集合.
    • getSignatureFixes

      @Input public abstract org.gradle.api.provider.MapProperty<String,String> getSignatureFixes()
      签名修复表;为空表示无需修复.
    • getAnnotationsJson

      @Input @Optional public abstract org.gradle.api.provider.Property<String> 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

      @Input public abstract org.gradle.api.provider.Property<String> 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

      @Optional @Input public abstract org.gradle.api.provider.Property<String> getPomGroup()
      pom 的 group;只参与 pom 内容的生成与落位.

      @Optional 的理由见 getOutputPom() 的「可选性的边界」:只有声明了 pom 输出时 才需要坐标,未声明时留着默认空值。执行期对「声明了输出却缺坐标」的校验在 remap()。

    • getPomName

      @Optional @Input public abstract org.gradle.api.provider.Property<String> getPomName()
      pom 的 artifact 名,与构件 jar 的主名相同;可选性同 getPomGroup().
    • getPomVersion

      @Optional @Input public abstract org.gradle.api.provider.Property<String> getPomVersion()
      pom 的版本,即「MC 版本 + 映射标识」;可选性同 getPomGroup().
    • getPomMavenRoot

      @Internal public abstract org.gradle.api.provider.Property<File> 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

      public void remap() throws IOException
      Throws:
      IOException
    • pomPathFor

      public static Path pomPathFor(Path artifactJar)
      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 路径