diff --git a/settings.gradle b/settings.gradle index c93bf5a7d..ff5c799c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,7 +24,7 @@ gradle.beforeProject { Project project -> //@formatter:off dependencyResolutionManagement.versionCatalogs.register('libs') { - version 'gradleutils', '3.3.42' + version 'gradleutils', '3.4.3' plugin 'licenser', 'net.minecraftforge.licenser' version '1.2.0' // https://plugins.gradle.org/plugin/net.minecraftforge.licenser plugin 'gradleutils', 'net.minecraftforge.gradleutils' versionRef 'gradleutils' diff --git a/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java new file mode 100644 index 000000000..7eed9db40 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradle; + +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderConvertible; + +import java.io.File; + +/// A Mavenizer instance is an abstraction over an invocation of the Minecraft Mavenizer and its output when generating +/// a Minecraft dependency. This is designed to be consumed by one of the various +/// [MinecraftExtensionForProject#dependency] methods as it is a [ProviderConvertible] of a dependency. +/// +/// This is not to be confused with [MinecraftDependency], which is designed to add additional DSL features on top of +/// [ExternalModuleDependency] when configuring a Minecraft dependency in your buildscript. +public interface MavenizerInstance extends ProviderConvertible { + /// Gets the dependency (used by Gradle) generated by the Mavenizer instance. + /// + /// @return The dependency generated by this instance + @Override Provider asProvider(); + + /// Gets the mappings version used by the Mavenizer instance. + /// + /// @return The mappings version used + /// @see MinecraftMappings#getVersion() + Provider getMappingVersion(); + + /// Gets the artifact coordinates for the SRG mappings used by the Mavenizer instance. + /// + /// @return The SRG mapping artifact coordinate + Provider getToSrg(); + + /// Gets the SRG mapping file used by the Mavenizer instance. + /// + /// @return The SRG mapping file + /// @apiNote Buildscript authors should prefer to use [#getToSrg()] with + /// [org.gradle.api.artifacts.dsl.DependencyFactory#create(CharSequence)] or + /// [org.gradle.api.artifacts.dsl.DependencyHandler#addProvider(String, Provider)]. + Provider getToSrgFile(); + + /// Gets the artifact coordinates for the SRG mappings used by the Mavenizer instance. + /// + /// @return The SRG mapping artifact coordinate + Provider getToObf(); + + /// Gets the notch mapping file used by the Mavenizer instance. + /// + /// @return The notch mapping file + /// @apiNote Buildscript authors should prefer to use [#getToObf()] with + /// [org.gradle.api.artifacts.dsl.DependencyFactory#create(CharSequence)] or + /// [org.gradle.api.artifacts.dsl.DependencyHandler#addProvider(String, Provider)]. + Provider getToObfFile(); +} diff --git a/src/main/java/net/minecraftforge/gradle/MinecraftExtensionForProject.java b/src/main/java/net/minecraftforge/gradle/MinecraftExtensionForProject.java index 116258821..339e60f01 100644 --- a/src/main/java/net/minecraftforge/gradle/MinecraftExtensionForProject.java +++ b/src/main/java/net/minecraftforge/gradle/MinecraftExtensionForProject.java @@ -7,13 +7,10 @@ import groovy.lang.Closure; import groovy.lang.DelegatesTo; import groovy.transform.stc.ClosureParams; -import groovy.transform.stc.FromString; import groovy.transform.stc.SimpleType; import net.minecraftforge.gradleutils.shared.Closures; import org.gradle.api.Action; -import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.provider.Provider; /// [Project][org.gradle.api.Project]-specific additions for the Minecraft extension. These will be accessible from the /// `minecraft` DSL object within your project's buildscript. @@ -28,7 +25,26 @@ public interface MinecraftExtensionForProject extends MinecraftExtension, Minecr /// @return The dependency /// @see Declaring Dependencies /// in Gradle - Provider dependency( + default MavenizerInstance dependency( + Object value, + @DelegatesTo(ExternalModuleDependency.class) + @ClosureParams(value = SimpleType.class, options = "net.minecraftforge.gradle.ClosureOwner.MinecraftDependency") + Closure closure + ) { + return this.dependency("default", value, closure); + } + + /// Creates (or marks if existing) the given dependency as a Minecraft dependency and configures it with the given + /// closure. + /// + /// @param name The name to give the Mavenizer instance used to generate the dependency + /// @param value The dependency + /// @param closure The closure to configure the dependency with + /// @return The dependency + /// @see Declaring Dependencies + /// in Gradle + MavenizerInstance dependency( + String name, Object value, @DelegatesTo(ExternalModuleDependency.class) @ClosureParams(value = SimpleType.class, options = "net.minecraftforge.gradle.ClosureOwner.MinecraftDependency") @@ -43,17 +59,56 @@ Provider dependency( /// @return The dependency /// @see Declaring Dependencies /// in Gradle - default Provider dependency(Object value, Action action) { - return this.dependency(value, Closures.action(this, action)); + default MavenizerInstance dependency(Object value, Action action) { + return this.dependency("default", value, action); + } + + /// Creates (or marks if existing) the given dependency as a Minecraft dependency and applies the given action to + /// it. + /// + /// @param name The name to give the Mavenizer instance used to generate the dependency + /// @param value The dependency + /// @param action The action to apply to the dependency attributes + /// @return The dependency + /// @see Declaring Dependencies + /// in Gradle + default MavenizerInstance dependency(String name, Object value, Action action) { + return this.dependency(name, value, Closures.action(this, action)); + } + + /// Creates (or marks if existing) the given dependency as a Minecraft dependency. + /// + /// @param value The dependency + /// @return The dependency + /// @see Declaring Dependencies + /// in Gradle + default MavenizerInstance dependency(Object value) { + return this.dependency("default", value); } /// Creates (or marks if existing) the given dependency as a Minecraft dependency. /// + /// @param name The name to give the Mavenizer instance used to generate the dependency /// @param value The dependency /// @return The dependency /// @see Declaring Dependencies /// in Gradle - default Provider dependency(Object value) { - return this.dependency(value, Closures.empty(this)); + default MavenizerInstance dependency(String name, Object value) { + return this.dependency(name, value, Closures.empty(this)); } + + /// Gets the default Minecraft dependency that was created using one of the [#dependency] methods. + /// + /// @return The default Minecraft dependency + /// @throws java.util.NoSuchElementException If the default Minecraft dependency has not yet been created. + /// @see #getDependency(String) + default MavenizerInstance getDependency() { + return this.getDependency("default"); + } + + /// Gets the named Minecraft dependency that was created using one of the [#dependency] methods. + /// + /// @return The default Minecraft dependency + /// @throws java.util.NoSuchElementException If the named Minecraft dependency has not yet been created. + MavenizerInstance getDependency(String name); } diff --git a/src/main/java/net/minecraftforge/gradle/internal/Constants.java b/src/main/java/net/minecraftforge/gradle/internal/Constants.java index 93da65625..04adce51a 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/Constants.java +++ b/src/main/java/net/minecraftforge/gradle/internal/Constants.java @@ -11,18 +11,6 @@ final class Constants { static final String FORGE_MAVEN = "https://maven.minecraftforge.net/"; static final String MC_LIBS_MAVEN = "https://libraries.minecraft.net/"; - static final String SLIMELAUNCHER_NAME = "slimelauncher"; - static final String SLIMELAUNCHER_VERSION = "0.1.6"; - static final String SLIMELAUNCHER_DL_URL = "https://maven.minecraftforge.net/net/minecraftforge/slime-launcher/" + SLIMELAUNCHER_VERSION + "/slime-launcher-" + SLIMELAUNCHER_VERSION + ".jar"; - static final int SLIMELAUNCHER_JAVA_VERSION = 8; - static final String SLIMELAUNCHER_MAIN = "net.minecraftforge.launcher.Main"; - - static final String MAVENIZER_NAME = "mavenizer"; - static final String MAVENIZER_VERSION = "0.4.24"; - static final String MAVENIZER_DL_URL = "https://maven.minecraftforge.net/net/minecraftforge/minecraft-mavenizer/" + MAVENIZER_VERSION + "/minecraft-mavenizer-" + MAVENIZER_VERSION + ".jar"; - static final int MAVENIZER_JAVA_VERSION = 25; - static final String MAVENIZER_MAIN = "net.minecraftforge.mcmaven.cli.Main"; - /// Use these with [java.text.MessageFormat#format(String, Object...)]. static final class Messages { static final String WELCOME = """ diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java index 4c784bae4..c099a6ec7 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java @@ -16,6 +16,7 @@ import javax.inject.Inject; import java.io.File; +import java.util.NoSuchElementException; import static net.minecraftforge.gradle.internal.ForgeGradlePlugin.LOGGER; @@ -213,6 +214,33 @@ void reportForgeMavenNotDeclared() { .solution(HELP_MESSAGE) ); } + + void reportDuplicateMavenizerNames(String name) { + this.report("mavenizer-instance-duplicate-name", "Duplicate Minecraft dependencies registered", spec -> spec + .details(""" + A Minecraft dependency was declared with a name already in use! + In order to manage access to mapping data each deobfuscated dependency needs to have a unique name. + Call the `minecraft.dependency` method with a unique name as the first parameter, the default when not specified is `default` + Name used: %s""" + .formatted(name)) + .severity(Severity.WARNING) + .solution("Call `minecraft.dependency` method with a unique name as the first parameter.") + .solution(HELP_MESSAGE) + ); + } + + RuntimeException mavenizerInstanceNotFound(NoSuchElementException e, String name) { + return this.throwing(e, "mavenizer-instance-not-found", "Minecraft dependency instance not found", spec -> spec + .details(""" + A Minecraft dependency instance was requested but not found! + The Minecraft dependency must be declared using `minecraft.dependency` first before it can be referenced. + Name requested: %s""" + .formatted(name)) + .severity(Severity.ERROR) + .solution("Call `minecraft.dependency` method before getting it using `minecraft.getDependency()`.") + .solution(HELP_MESSAGE) + ); + } //endregion //endregion diff --git a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java new file mode 100644 index 000000000..ab57a7dad --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradle.internal; + +import groovy.json.JsonSlurper; +import net.minecraftforge.gradle.MavenizerInstance; +import org.gradle.api.Transformer; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; +import org.jspecify.annotations.Nullable; + +import java.io.File; +import java.util.Map; + +class MavenizerInstanceImpl implements MavenizerInstance { + private final MinecraftExtensionImpl.ForProjectImpl extension; + private final Provider valueSource; + private final ExternalModuleDependency dependency; + private final File jsonFile; + + private final MapProperty invoke; + private @Nullable Map map; + + MavenizerInstanceImpl( + MinecraftExtensionImpl.ForProjectImpl extension, + Provider valueSource, + ExternalModuleDependency dependency, + File jsonFile + ) { + this.extension = extension; + this.dependency = dependency; + this.valueSource = valueSource; + this.jsonFile = jsonFile; + this.invoke = this.extension.getObjects().mapProperty(String.class, String.class) + .convention(this.extension.getProviders().provider(this::invoke)); + } + + @SuppressWarnings("unchecked") + private Map invoke() { + if (this.map == null) { + valueSource.get(); // Execute Mavenizer, probably called before, but just be sure. + this.map = (Map) new JsonSlurper().parse(this.jsonFile, "UTF-8"); + //this.map.forEach((k, v) -> this.extension.getProject().getLogger().lifecycle(k + " => " + v)); + } + return this.map; + } + + private Provider get(String key) { + return this.invoke.getting(key) + .orElse(this.extension.getProviders().provider(() -> { + throw new IllegalStateException("Mavenizer did not output expected json data " + key); + })); + } + + @Override + public Provider asProvider() { + return this.invoke.map(m -> this.dependency); + } + + @Override + public Provider getMappingVersion() { + return get("mappings.version"); + } + + @Override + public Provider getToSrg() { + return get("mappings.srg.artifact"); + } + + @Override + public Provider getToSrgFile() { + return get("mappings.srg.file").map(this.extension.getProject()::file); + } + + @Override + public Provider getToObf() { + return get("mappings.obf.artifact"); + } + + @Override + public Provider getToObfFile() { + return get("mappings.obf.file").map(this.extension.getProject()::file); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java index b76a21ba1..4ff9fa815 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java @@ -9,16 +9,15 @@ import groovy.transform.NamedVariant; import groovy.transform.stc.ClosureParams; import groovy.transform.stc.SimpleType; +import net.minecraftforge.gradle.MavenizerInstance; import net.minecraftforge.gradle.MinecraftExtension; import net.minecraftforge.gradle.MinecraftExtensionForProject; import net.minecraftforge.gradle.MinecraftMappings; import net.minecraftforge.gradle.SlimeLauncherOptions; -import org.codehaus.groovy.runtime.InvokerHelper; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; -import org.gradle.api.UnknownTaskException; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.ExternalModuleDependencyBundle; @@ -29,7 +28,6 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.ProjectLayout; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.flow.FlowProviders; import org.gradle.api.flow.FlowScope; import org.gradle.api.initialization.Settings; @@ -44,18 +42,17 @@ import org.gradle.api.reflect.TypeOf; import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.toolchain.JavaLauncher; -import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.plugins.ide.eclipse.model.EclipseModel; -import org.jetbrains.annotations.UnmodifiableView; -import org.jspecify.annotations.Nullable; import javax.inject.Inject; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; -import java.util.function.Predicate; import java.util.function.ToIntFunction; import java.util.stream.Collectors; @@ -177,6 +174,7 @@ static abstract class ForProjectImpl extends MinecraftExtensionImpl implements F // Dependencies private final List minecraftDependencies = new ArrayList<>(); + private final Map mavenizerRegistry = new HashMap<>(); // Access Transformers private final boolean hasAccessTransformersPlugin; @@ -380,7 +378,8 @@ public NamedDomainObjectContainer getRuns() { @SuppressWarnings({"UnstableApiUsage"}) @Override - public Provider dependency( + public MavenizerInstance dependency( + String name, Object value, @DelegatesTo(ExternalModuleDependency.class) @ClosureParams(value = SimpleType.class, options = "net.minecraftforge.gradle.MinecraftDependency.ClosureOwner") @@ -394,6 +393,8 @@ public Provider dependency( var minecraftDependency = this.getObjects().newInstance(MinecraftDependencyImpl.class, this.getMavenizerOutput()); this.minecraftDependencies.add(minecraftDependency); var dep = minecraftDependency.init(value, closure); + var outputJson = this.plugin.localCaches().file("mavenizer/" + name + ".json").get().getAsFile(); + var mavenizer = this.getProviders().of(MavenizerValueSource.class, spec -> { spec.parameters(params -> { var tool = this.plugin.getTool(Tools.MAVENIZER); @@ -405,15 +406,15 @@ public Provider dependency( .map(this.problems.ensureFileLocation()); var cache = toolCache.get().dir("caches").getAsFile().getAbsolutePath(); - var ret = new ArrayList(); - ret.addAll(List.of( + var ret = new ArrayList<>(List.of( "--maven", "--cache", cache, "--jdk-cache", cache, "--output", this.getMavenizerOutput().get().getAsFile().getAbsolutePath(), "--artifact", dep.getModule().toString(), "--version", Objects.requireNonNull(dep.getVersion()), - "--global-auxiliary-variants" + "--global-auxiliary-variants", + "--output-json", outputJson.getAbsolutePath() )); // If we are finding the access transformer from sourcesets, just find from any source set @@ -447,10 +448,18 @@ public Provider dependency( }); }); - return this.getProviders().provider(() -> { - mavenizer.get();// Invoke mavenizer, it should be invoked already by gradle config cache, but Force it to be - return dep; - }); + var instance = new MavenizerInstanceImpl(this, mavenizer, dep, outputJson); + if (this.mavenizerRegistry.put(name, instance) != null) + problems.reportDuplicateMavenizerNames(name); + return instance; + } + + @Override + public MavenizerInstance getDependency(String name) { + var ret = this.mavenizerRegistry.get(name); + if (ret == null) + throw problems.mavenizerInstanceNotFound(new NoSuchElementException("Mavenizer instance not found: " + name), name); + return ret; } private void checkRepos(List repos) { diff --git a/src/main/java/net/minecraftforge/gradle/internal/Tools.java b/src/main/java/net/minecraftforge/gradle/internal/Tools.java index 465b52bae..e5df35173 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/Tools.java +++ b/src/main/java/net/minecraftforge/gradle/internal/Tools.java @@ -9,7 +9,7 @@ final class Tools { private Tools() { } - static final Tool SLIMELAUNCHER = Tool.of(Constants.SLIMELAUNCHER_NAME, Constants.SLIMELAUNCHER_VERSION, Constants.SLIMELAUNCHER_DL_URL, Constants.SLIMELAUNCHER_JAVA_VERSION, Constants.SLIMELAUNCHER_MAIN); + static final Tool SLIMELAUNCHER = Tool.ofForge("slimelauncher", "net.minecraftforge:slimelauncher:0.1.6", 8, "net.minecraftforge.launcher.Main"); - static final Tool MAVENIZER = Tool.of(Constants.MAVENIZER_NAME, Constants.MAVENIZER_VERSION, Constants.MAVENIZER_DL_URL, Constants.MAVENIZER_JAVA_VERSION, Constants.MAVENIZER_MAIN); + static final Tool MAVENIZER = Tool.ofForge("mavenizer", "net.minecraftforge:minecraft-mavenizer:0.4.25", 25, "net.minecraftforge.mcmaven.cli.Main"); }