From f8ff404c677d11aeb4b34dad0ed6074b1f655972 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Mon, 14 Oct 2024 23:36:21 +0200 Subject: [PATCH 01/25] Configure imperatively --- legacytest/build.gradle | 6 +- legacytest/forge/build.gradle | 4 +- .../legacyforge/dsl/LegacyForgeExtension.java | 55 ++ .../dsl/LegacyForgeModdingSettings.java | 57 ++ .../legacyforge/dsl/Obfuscation.java | 28 +- .../internal/LegacyForgeModDevPlugin.java | 212 +++-- .../legacyforge/tasks/RemapOperation.java | 3 +- .../dsl/ModdingVersionSettings.java | 43 + .../moddevgradle/dsl/NeoForgeExtension.java | 192 +--- .../neoforged/moddevgradle/dsl/UnitTest.java | 7 +- .../internal/ArtifactNamingStrategy.java | 16 + .../moddevgradle/internal/Branding.java | 5 +- .../internal/DataFileCollectionFactory.java | 124 +++ .../internal/EclipseIntegration.java | 6 +- .../moddevgradle/internal/IdeIntegration.java | 7 +- .../internal/IntelliJIntegration.java | 8 +- .../moddevgradle/internal/JarJarPlugin.java | 27 + .../internal/ModDevArtifactsWorkflow.java | 327 +++++++ .../internal/ModDevExtension.java | 145 +++ .../moddevgradle/internal/ModDevPlugin.java | 874 ++---------------- .../internal/ModDevRunWorkflow.java | 490 ++++++++++ .../moddevgradle/internal/NeoDevFacade.java | 16 +- .../internal/PrepareRunOrTest.java | 3 +- .../internal/WorkflowArtifact.java | 16 + .../jarjar/ResolvedJarJarArtifact.java | 4 +- .../neoforged/moddevgradle/tasks/JarJar.java | 2 +- .../nfrtgradle/CreateMinecraftArtifacts.java | 3 +- .../nfrtgradle/NeoFormRuntimeTask.java | 2 +- .../DataFileCollectionFunctionalTest.java | 4 +- .../functional/GroovyScriptTest.java | 2 +- .../functional/KotlinScriptTest.java | 2 +- .../AccessTransformerConventionTest.java | 2 + testproject/build.gradle | 6 +- testproject/common/build.gradle | 4 +- testproject/jijtest/build.gradle | 4 +- testproject/subproject/build.gradle | 4 +- 36 files changed, 1632 insertions(+), 1078 deletions(-) create mode 100644 src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java create mode 100644 src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java create mode 100644 src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ArtifactNamingStrategy.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java diff --git a/legacytest/build.gradle b/legacytest/build.gradle index 95b0ef83..b8ebdc4e 100644 --- a/legacytest/build.gradle +++ b/legacytest/build.gradle @@ -12,8 +12,10 @@ java { } } -neoForge { - neoFormVersion = '1.19.2' +legacyForge { + enableModding { + mcpVersion = '1.19.2' + } } publishing { diff --git a/legacytest/forge/build.gradle b/legacytest/forge/build.gradle index 67a4847b..f465bbda 100644 --- a/legacytest/forge/build.gradle +++ b/legacytest/forge/build.gradle @@ -30,8 +30,8 @@ dependencies { modImplementation('curse.maven:applied-energistics-2-223794:5641282') } -neoForge { - version = '1.20.1-47.3.0' +legacyForge { + version = '1.20.1-47.3.12' runs { client { client() diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java new file mode 100644 index 00000000..97287d96 --- /dev/null +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -0,0 +1,55 @@ +package net.neoforged.moddevgradle.legacyforge.dsl; + +import net.neoforged.moddevgradle.dsl.DataFileCollection; +import net.neoforged.moddevgradle.internal.ModDevExtension; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; + +import javax.inject.Inject; +import java.util.List; + +/** + * This is the top-level {@code legacyForge} extension, used to configure the moddev plugin. + */ +public abstract class LegacyForgeExtension extends ModDevExtension { + private final Project project; + + private final Obfuscation obfuscation; + + @Inject + public LegacyForgeExtension(Project project, + DataFileCollection accessTransformers, + DataFileCollection interfaceInjectionData, + Obfuscation obfuscation) { + super(project, accessTransformers, interfaceInjectionData); + this.project = project; + this.obfuscation = obfuscation; + } + + public void setVersion(String version) { + enableModding(settings -> { + settings.setForgeVersion(version); + }); + } + + public void enableModding(Action customizer) { + var plugin = project.getPlugins().getPlugin(LegacyForgeModDevPlugin.class); + + var settings = project.getObjects().newInstance(LegacyForgeModdingSettings.class); + // By default, enable modding deps only for the main source set + settings.getEnabledSourceSets().convention(project.provider(() -> { + var sourceSets = ExtensionUtils.getSourceSets(project); + return List.of(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)); + })); + customizer.execute(settings); + + plugin.enableModding(project, settings, this); + } + + public Obfuscation getObfuscation() { + return obfuscation; + } +} diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java new file mode 100644 index 00000000..22ba415a --- /dev/null +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java @@ -0,0 +1,57 @@ +package net.neoforged.moddevgradle.legacyforge.dsl; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.Nullable; + +public abstract class LegacyForgeModdingSettings { + @Nullable + private String neoForgeVersion; + + private String forgeVersion; + + @Nullable + private String mcpVersion; + + public @Nullable String getNeoForgeVersion() { + return neoForgeVersion; + } + + public @Nullable String getForgeVersion() { + return forgeVersion; + } + + public @Nullable String getMcpVersion() { + return mcpVersion; + } + + /** + * NeoForge version number. You have to set either this, {@link #setForgeVersion} or {@link #setMcpVersion}. + * Only NeoForge for Minecraft 1.20.1 is supported when using this plugin. + */ + public void setNeoForgeVersion(String version) { + this.neoForgeVersion = version; + } + + /** + * Minecraft Forge version. You have to set either this, {@link #setNeoForgeVersion} or {@link #setMcpVersion}. + */ + public void setForgeVersion(String version) { + this.forgeVersion = version; + } + + /** + * You can set this property to a version of MCP + * to either override the version used in the version of Forge you set, or to compile against + * Vanilla artifacts that have no Forge code added. + */ + public void setMcpVersion(String version) { + this.mcpVersion = version; + } + + /** + * Contains the list of source sets for which access to Minecraft classes should be configured. + * Defaults to the main source set, but can also be set to an empty list. + */ + public abstract ListProperty getEnabledSourceSets(); +} diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java index 39a56f70..f68a7c90 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java @@ -12,9 +12,8 @@ import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.file.FileCollection; -import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.AbstractArchiveTask; @@ -25,39 +24,45 @@ public abstract class Obfuscation { private final Project project; - private final Provider officialToSrg; - private final Provider mappingsCsv; private final Configuration autoRenamingToolRuntime; private final Configuration installerToolsRuntime; private final FileCollection extraMixinMappings; @Inject public Obfuscation(Project project, - Provider officialToSrg, - Provider mappingsCsv, Configuration autoRenamingToolRuntime, Configuration installerToolsRuntime, FileCollection extraMixinMappings) { this.project = project; - this.officialToSrg = officialToSrg; - this.mappingsCsv = mappingsCsv; this.autoRenamingToolRuntime = autoRenamingToolRuntime; this.installerToolsRuntime = installerToolsRuntime; this.extraMixinMappings = extraMixinMappings; } + /** + * Format is TSRG + */ + @ApiStatus.Internal + public abstract RegularFileProperty getNamedToSrgMappings(); + + /** + * Format is "mappings.csv" + */ + @ApiStatus.Internal + public abstract RegularFileProperty getSrgToNamedMappings(); + @ApiStatus.Internal public void configureNamedToSrgOperation(RemapOperation operation) { operation.getToolType().set(RemapOperation.ToolType.ART); operation.getToolClasspath().from(autoRenamingToolRuntime); - operation.getMappings().from(officialToSrg); + operation.getMappings().from(getNamedToSrgMappings().get()); } @ApiStatus.Internal public void configureSrgToNamedOperation(RemapOperation operation) { operation.getToolType().set(RemapOperation.ToolType.INSTALLER_TOOLS); operation.getToolClasspath().from(installerToolsRuntime); - operation.getMappings().from(mappingsCsv); + operation.getMappings().from(getSrgToNamedMappings().get()); } /** @@ -68,7 +73,8 @@ public void configureSrgToNamedOperation(RemapOperation operation) { * @return a provider of the created task */ public TaskProvider reobfuscate(TaskProvider jar, SourceSet sourceSet) { - return reobfuscate(jar, sourceSet, ignored -> {}); + return reobfuscate(jar, sourceSet, ignored -> { + }); } /** diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 2b88b739..a9b06732 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -1,26 +1,35 @@ package net.neoforged.moddevgradle.legacyforge.internal; -import net.neoforged.moddevgradle.dsl.NeoForgeExtension; +import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin; +import net.neoforged.moddevgradle.internal.ArtifactNamingStrategy; +import net.neoforged.moddevgradle.internal.Branding; +import net.neoforged.moddevgradle.internal.DataFileCollectionFactory; +import net.neoforged.moddevgradle.internal.JarJarPlugin; import net.neoforged.moddevgradle.internal.LegacyForgeFacade; -import net.neoforged.moddevgradle.internal.ModDevPlugin; -import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; +import net.neoforged.moddevgradle.internal.ModDevRunWorkflow; +import net.neoforged.moddevgradle.internal.RepositoriesPlugin; +import net.neoforged.moddevgradle.internal.WorkflowArtifact; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; +import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeExtension; +import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeModdingSettings; import net.neoforged.moddevgradle.legacyforge.dsl.MixinExtension; import net.neoforged.moddevgradle.legacyforge.dsl.Obfuscation; -import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; +import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.type.ArtifactTypeDefinition; import org.gradle.api.attributes.Attribute; -import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.jvm.tasks.Jar; -import org.gradle.jvm.toolchain.JavaLanguageVersion; -import org.gradle.jvm.toolchain.JavaLauncher; -import org.gradle.jvm.toolchain.JavaToolchainService; import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.URI; import java.util.Map; @@ -28,6 +37,8 @@ @ApiStatus.Internal public class LegacyForgeModDevPlugin implements Plugin { + private static final Logger LOG = LoggerFactory.getLogger(LegacyForgeModDevPlugin.class); + public static final Attribute REMAPPED = Attribute.of("net.neoforged.moddevgradle.legacy.remapped", Boolean.class); public static final String CONFIGURATION_TOOL_ART = "autoRenamingToolRuntime"; @@ -35,7 +46,19 @@ public class LegacyForgeModDevPlugin implements Plugin { @Override public void apply(Project project) { - project.getPlugins().apply(ModDevPlugin.class); + project.getPlugins().apply(JavaLibraryPlugin.class); + project.getPlugins().apply(NeoFormRuntimePlugin.class); + project.getPlugins().apply(MinecraftDependenciesPlugin.class); + project.getPlugins().apply(JarJarPlugin.class); + + // TODO: Introduce a LegacyRepositoryPLugin to still allow repo management in settings.gradle + // Do not apply the repositories automatically if they have been applied at the settings-level. + // It's still possible to apply them manually, though. + if (!project.getGradle().getPlugins().hasPlugin(RepositoriesPlugin.class)) { + project.getPlugins().apply(RepositoriesPlugin.class); + } else { + LOG.info("Not enabling NeoForged repositories since they were applied at the settings level"); + } project.getRepositories().maven(repo -> { repo.setName("MinecraftForge"); @@ -66,35 +89,134 @@ public void apply(Project project) { spec.getDependencies().add(depFactory.create("net.neoforged.installertools:installertools:2.1.10:fatjar")); }); + var obf = project.getObjects().newInstance(Obfuscation.class, project, autoRenamingToolRuntime, installerToolsRuntime); + configureDependencyRemapping(project, obf); + + var dataFileCollections = DataFileCollectionFactory.createDefault(project); + project.getExtensions().create( + "legacyForge", + LegacyForgeExtension.class, + project, + dataFileCollections.accessTransformers().extension(), + dataFileCollections.interfaceInjectionData().extension(), + obf + ); + } + + public void enableModding(Project project, LegacyForgeModdingSettings settings, LegacyForgeExtension extension) { + var depFactory = project.getDependencyFactory(); + + var forgeVersion = settings.getForgeVersion(); + var neoForgeVersion = settings.getNeoForgeVersion(); + var mcpVersion = settings.getMcpVersion(); + + ModuleDependency platformModule = null; + ModuleDependency recompilableMinecraftWorkflowDependency = null; + String recompilableMinecraftDataDependencyNotation = null; + ModuleDependency modulePathDependency = null; + ModuleDependency runTypesDataDependency = null; + ModuleDependency librariesDependency; + String moddingPlatformDataDependencyNotation = null; + ArtifactNamingStrategy artifactNamingStrategy; + VersionCapabilities versionCapabilities; + if (forgeVersion != null || neoForgeVersion != null) { + // All settings are mutually exclusive + if (forgeVersion != null && neoForgeVersion != null || mcpVersion != null) { + throw new InvalidUserCodeException("Specifying a Forge version is mutually exclusive with NeoForge or MCP"); + } + + String groupId = forgeVersion != null ? "net.minecraftforge" : "net.neoforged"; + + platformModule = depFactory.create(groupId + ":forge:" + forgeVersion); + moddingPlatformDataDependencyNotation = groupId + ":forge:" + forgeVersion + ":userdev"; + runTypesDataDependency = platformModule.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); + modulePathDependency = platformModule.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) + // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. + .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); + librariesDependency = platformModule.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); + + var artifactPrefix = "forge-" + forgeVersion; + // We have to ensure that client resources are named "client-extra" and *do not* contain forge- + // otherwise FML might pick up the client resources as the main Minecraft jar. + artifactNamingStrategy = (artifact) -> { + if (artifact == WorkflowArtifact.CLIENT_RESOURCES) { + return "client-extra-" + forgeVersion + ".jar"; + } else { + return artifactPrefix + artifact.defaultSuffix + ".jar"; + } + }; + + versionCapabilities = VersionCapabilities.ofForgeVersion(forgeVersion); + } else if (mcpVersion != null) { + recompilableMinecraftDataDependencyNotation = "de.oceanlabs.mcp:mcp_config:" + mcpVersion + "@zip"; + recompilableMinecraftWorkflowDependency = depFactory.create("de.oceanlabs.mcp:mcp_config:" + mcpVersion); + librariesDependency = recompilableMinecraftWorkflowDependency.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); + artifactNamingStrategy = ArtifactNamingStrategy.createDefault("vanilla-" + mcpVersion); + + versionCapabilities = VersionCapabilities.ofMinecraftVersion(mcpVersion); + } else { + throw new InvalidUserCodeException("You must specify a Forge, NeoForge or MCP version"); + } + + var configurations = project.getConfigurations(); + + var artifacts = ModDevArtifactsWorkflow.create( + project, + Branding.MDG, + extension, + platformModule, + moddingPlatformDataDependencyNotation, + recompilableMinecraftWorkflowDependency, + recompilableMinecraftDataDependencyNotation, + librariesDependency, + artifactNamingStrategy, + configurations.getByName(DataFileCollectionFactory.CONFIGURATION_ACCESS_TRANSFORMERS), + configurations.getByName(DataFileCollectionFactory.CONFIGURATION_INTERFACE_INJECTION_DATA), + versionCapabilities + ); + + var runs = ModDevRunWorkflow.create( + project, + Branding.MDG, + artifacts, + modulePathDependency, + runTypesDataDependency, + null /* no support for test fixtures */, + librariesDependency, + extension.getRuns(), + versionCapabilities + ); + + for (var sourceSet : settings.getEnabledSourceSets().get()) { + artifacts.addToSourceSet(sourceSet.getName()); + } + + var obf = extension.getObfuscation(); + // We use this directory to store intermediate files used during moddev - var modDevBuildDir = project.getLayout().getBuildDirectory().dir("moddev"); - var namedToIntermediate = modDevBuildDir.map(d -> d.file("namedToIntermediate.tsrg")); - var intermediateToNamed = modDevBuildDir.map(d -> d.file("intermediateToNamed.srg")); - var mappingsCsv = modDevBuildDir.map(d -> d.file("intermediateToNamed.zip")); + var namedToIntermediate = artifacts.requestAdditionalMinecraftArtifact("namedToIntermediaryMapping", "namedToIntermediate.tsrg"); + obf.getNamedToSrgMappings().set(namedToIntermediate); + var intermediateToNamed = artifacts.requestAdditionalMinecraftArtifact("intermediaryToNamedMapping", "intermediateToNamed.srg"); + obf.getSrgToNamedMappings().set(intermediateToNamed); + var mappingsCsv = artifacts.requestAdditionalMinecraftArtifact("csvMapping", "intermediateToNamed.zip"); // This collection is used to share the files added by mixin with the obfuscation extension var extraMixinMappings = project.files(); - var obf = project.getExtensions().create("obfuscation", Obfuscation.class, project, namedToIntermediate, mappingsCsv, autoRenamingToolRuntime, installerToolsRuntime, extraMixinMappings); var mixin = project.getExtensions().create("mixin", MixinExtension.class, project, namedToIntermediate, extraMixinMappings); - project.getExtensions().configure(NeoForgeExtension.class, extension -> { - extension.getNeoForgeArtifact().set(extension.getVersion().map(version -> "net.minecraftforge:forge:" + version)); - extension.getNeoFormArtifact().set(extension.getNeoFormVersion().map(version -> "de.oceanlabs.mcp:mcp_config:" + version)); - - extension.getAdditionalMinecraftArtifacts().put("namedToIntermediaryMapping", namedToIntermediate.map(RegularFile::getAsFile)); - extension.getAdditionalMinecraftArtifacts().put("intermediaryToNamedMapping", intermediateToNamed.map(RegularFile::getAsFile)); - extension.getAdditionalMinecraftArtifacts().put("csvMapping", mappingsCsv.map(RegularFile::getAsFile)); - - extension.getRuns().configureEach(run -> { - LegacyForgeFacade.configureRun(project, run); + extension.getRuns().configureEach(run -> { + LegacyForgeFacade.configureRun(project, run); - // Mixin needs the intermediate (SRG) -> named (Mojang, MCP) mapping file in SRG (TSRG is not supported) to be able to ignore the refmaps of dependencies - run.getSystemProperties().put("mixin.env.remapRefMap", "true"); - run.getSystemProperties().put("mixin.env.refMapRemappingFile", intermediateToNamed.map(f -> f.getAsFile().getAbsolutePath())); + // Mixin needs the intermediate (SRG) -> named (Mojang, MCP) mapping file in SRG (TSRG is not supported) to be able to ignore the refmaps of dependencies + run.getSystemProperties().put("mixin.env.remapRefMap", "true"); + run.getSystemProperties().put("mixin.env.refMapRemappingFile", intermediateToNamed.map(f -> f.getAsFile().getAbsolutePath())); - run.getProgramArguments().addAll(mixin.getConfigs().map(cfgs -> cfgs.stream().flatMap(config -> Stream.of("--mixin.config", config)).toList())); - }); + run.getProgramArguments().addAll(mixin.getConfigs().map(cfgs -> cfgs.stream().flatMap(config -> Stream.of("--mixin.config", config)).toList())); }); var reobfJar = obf.reobfuscate( @@ -105,25 +227,22 @@ public void apply(Project project) { project.getTasks().named("assemble", assemble -> assemble.dependsOn(reobfJar)); // Forge expects the mapping csv files on the root classpath - project.getConfigurations().getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES) + artifacts.runtimeDependencies() .getDependencies().add(project.getDependencyFactory().create(project.files(mappingsCsv))); // Forge expects to find the Forge and client-extra jar on the legacy classpath // Newer FML versions also search for it on the java.class.path. // MDG already adds cilent-extra, but the forge jar is missing. - project.getConfigurations().getByName("additionalRuntimeClasspath") - .extendsFrom(project.getConfigurations().getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES)) + runs.getAdditionalClasspath() + .extendsFrom(artifacts.runtimeDependencies()) .exclude(Map.of("group", "net.neoforged", "module", "DevLaunch")); - project.getDependencies().attributesSchema(schema -> schema.attribute(REMAPPED)); - project.getDependencies().getArtifactTypes().named("jar", a -> a.getAttributes().attribute(REMAPPED, false)); - var remapDeps = project.getConfigurations().create("remappingDependencies", spec -> { spec.setDescription("An internal configuration that contains the Minecraft dependencies, used for remapping mods"); spec.setCanBeConsumed(false); spec.setCanBeDeclared(false); spec.setCanBeResolved(true); - spec.extendsFrom(project.getConfigurations().getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES)); + spec.extendsFrom(artifacts.runtimeDependencies()); }); project.getDependencies().registerTransform(RemappingTransform.class, params -> { @@ -138,24 +257,11 @@ public void apply(Project project) { .attribute(REMAPPED, true) .attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); }); + } - // Set the right Java version - project.getTasks().withType(CreateMinecraftArtifacts.class).configureEach(task -> { - var extension = ExtensionUtils.getExtension(project, NeoForgeExtension.NAME, NeoForgeExtension.class); - var toolchainService = ExtensionUtils.getExtension(project, "javaToolchains", JavaToolchainService.class); - task.getToolsJavaExecutable().set( - toolchainService.launcherFor(spec -> { - spec.getLanguageVersion().set( - extension.getVersion().map(VersionCapabilities::ofForgeVersion) - .orElse(extension.getNeoFormVersion().map(VersionCapabilities::ofNeoFormVersion)) - .map(VersionCapabilities::javaVersion) - .map(JavaLanguageVersion::of) - ); - }) - .map(JavaLauncher::getExecutablePath) - .map(f -> f.getAsFile().getAbsolutePath()) - ); - }); + private static void configureDependencyRemapping(Project project, Obfuscation obf) { + project.getDependencies().attributesSchema(schema -> schema.attribute(REMAPPED)); + project.getDependencies().getArtifactTypes().named("jar", a -> a.getAttributes().attribute(REMAPPED, false)); obf.createRemappingConfiguration(project.getConfigurations().getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME)); obf.createRemappingConfiguration(project.getConfigurations().getByName(JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME)); diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java index 312a071a..697464f4 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java @@ -25,7 +25,8 @@ public abstract class RemapOperation implements Serializable { @Inject - public RemapOperation() {} + public RemapOperation() { + } @Input public abstract Property getToolType(); diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java new file mode 100644 index 00000000..8989ac0a --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java @@ -0,0 +1,43 @@ +package net.neoforged.moddevgradle.dsl; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.Nullable; + +public abstract class ModdingVersionSettings { + @Nullable + private String neoForgeVersion; + + @Nullable + private String neoFormVersion; + + public @Nullable String getNeoForgeVersion() { + return neoForgeVersion; + } + + public @Nullable String getNeoFormVersion() { + return neoFormVersion; + } + + /** + * NeoForge version number. You have to set either this or {@link #setNeoFormVersion}. + */ + public void setNeoForgeVersion(String version) { + this.neoForgeVersion = version; + } + + /** + * You can set this property to a version of NeoForm + * to either override the version used in the version of NeoForge you set, or to compile against + * Vanilla artifacts that have no NeoForge code added. + */ + public void setNeoFormVersion(String version) { + this.neoFormVersion = version; + } + + /** + * Contains the list of source sets for which access to Minecraft classes should be configured. + * Defaults to the main source set, but can also be set to an empty list. + */ + public abstract ListProperty getEnabledSourceSets(); +} diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index 312d7a54..b7ab904e 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -1,177 +1,50 @@ package net.neoforged.moddevgradle.dsl; +import net.neoforged.moddevgradle.internal.ModDevExtension; import net.neoforged.moddevgradle.internal.ModDevPlugin; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.Action; -import org.gradle.api.GradleException; -import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.MapProperty; -import org.gradle.api.provider.Property; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskProvider; import javax.inject.Inject; -import java.io.File; +import java.util.List; /** * This is the top-level {@code neoForge} extension, used to configure the moddev plugin. */ -public abstract class NeoForgeExtension { +public abstract class NeoForgeExtension extends ModDevExtension { public static final String NAME = "neoForge"; private final Project project; - private final NamedDomainObjectContainer mods; - private final NamedDomainObjectContainer runs; - private final Parchment parchment; private final UnitTest unitTest; - private final DataFileCollection accessTransformers; - private final DataFileCollection interfaceInjectionData; - @Inject public NeoForgeExtension(Project project, DataFileCollection accessTransformers, DataFileCollection interfaceInjectionData) { + super(project, accessTransformers, interfaceInjectionData); this.project = project; - mods = project.container(ModModel.class); - runs = project.container(RunModel.class, name -> project.getObjects().newInstance(RunModel.class, name, project, mods)); - parchment = project.getObjects().newInstance(Parchment.class); unitTest = project.getObjects().newInstance(UnitTest.class); - getNeoForgeArtifact().convention(getVersion().map(version -> "net.neoforged:neoforge:" + version)); - getNeoFormArtifact().convention(getNeoFormVersion().map(version -> "net.neoforged:neoform:" + version)); - - this.accessTransformers = accessTransformers; - this.interfaceInjectionData = interfaceInjectionData; - getValidateAccessTransformers().convention(false); unitTest.getLoadedMods().convention(getMods()); } - /** - * Adds the necessary dependencies to develop a Minecraft mod to the given source set. - * The plugin automatically adds these dependencies to the main source set. - */ - public void addModdingDependenciesTo(SourceSet sourceSet) { - var configurations = project.getConfigurations(); - var sourceSets = ExtensionUtils.getSourceSets(project); - if (!sourceSets.contains(sourceSet)) { - throw new GradleException("Cannot add to the source set in another project."); - } - - configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()) - .extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES)); - configurations.getByName(sourceSet.getCompileClasspathConfigurationName()) - .extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_COMPILE_DEPENDENCIES)); - } - - /** - * NeoForge version number. You have to set either this, {@link #getNeoFormVersion()} - * or {@link #getNeoFormArtifact()}. - */ - public abstract Property getVersion(); - - /** - * You can set this property to a version of NeoForm - * to either override the version used in the version of NeoForge you set, or to compile against - * Vanilla artifacts that have no NeoForge code added. - *

- * This property is mutually exclusive with {@link #getNeoFormArtifact()}. - */ - public abstract Property getNeoFormVersion(); - - /** - * Is derived automatically from {@link #getVersion()}. - * - * @return Maven artifact coordinate (group:module:version) - */ - public abstract Property getNeoForgeArtifact(); - - /** - * Derived automatically from the {@link #getNeoFormVersion()}. - * You can override this property to use i.e. MCP for up to 1.20.1. - *

- * This property is mutually exclusive with {@link #getNeoForgeArtifact()}. - * - * @return Maven artifact coordinate (group:module:version) - */ - public abstract Property getNeoFormArtifact(); - - /** - * The list of additional access transformers that should be applied to the Minecraft source code. - *

- * If you do not set this property, the plugin will look for an access transformer file at - * {@code META-INF/accesstransformer.cfg} relative to your main source sets resource directories. - * - * @see Access Transformer File Format - */ - public void accessTransformers(Action action) { - action.execute(accessTransformers); - } - - public DataFileCollection getAccessTransformers() { - return accessTransformers; + @Deprecated(forRemoval = true) + public void setVersion(Object any) { + throw new InvalidUserCodeException("Please use enableModding { neoForgeVersion = ... } instead of the version property."); } - /** - * Replaces current access transformers. - */ - public void setAccessTransformers(Object... paths) { - getAccessTransformers().getFiles().setFrom(paths); - } - - /** - * The data-files describing additional interface implementation declarations to be added to - * Minecraft classes. - *

- * This is an advanced property: Injecting interfaces in your development environment using this property will not implement - * the interfaces in your published mod. You have to use Mixin or ASM to do that. - * - * @see Interface Injection Data Format - */ - public void interfaceInjectionData(Action action) { - action.execute(interfaceInjectionData); - } - - public DataFileCollection getInterfaceInjectionData() { - return interfaceInjectionData; - } - - /** - * Replaces current interface injection data files. - */ - public void setInterfaceInjectionData(Object... paths) { - getInterfaceInjectionData().getFiles().setFrom(paths); - } + public void enableModding(Action customizer) { + var modDevPlugin = project.getPlugins().getPlugin(ModDevPlugin.class); - /** - * Enable access transformer validation, raising fatal errors if an AT targets a member that doesn't exist. - *

- * Default {@code false}
- */ - public abstract Property getValidateAccessTransformers(); + var settings = project.getObjects().newInstance(ModdingVersionSettings.class); + // By default, enable modding deps only for the main source set + settings.getEnabledSourceSets().convention(project.provider(() -> { + var sourceSets = ExtensionUtils.getSourceSets(project); + return List.of(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)); + })); + customizer.execute(settings); - public NamedDomainObjectContainer getMods() { - return mods; - } - - public void mods(Action> action) { - action.execute(mods); - } - - public NamedDomainObjectContainer getRuns() { - return runs; - } - - public void runs(Action> action) { - action.execute(runs); - } - - public Parchment getParchment() { - return parchment; - } - - public void parchment(Action action) { - action.execute(parchment); + modDevPlugin.enableModding(project, settings, this); } public UnitTest getUnitTest() { @@ -181,33 +54,4 @@ public UnitTest getUnitTest() { public void unitTest(Action action) { action.execute(unitTest); } - - - /** - * The tasks to be run when the IDE reloads the Gradle project. - */ - public abstract ListProperty> getIdeSyncTasks(); - - /** - * Configures the given task to be run when the IDE reloads the Gradle project. - */ - public void ideSyncTask(TaskProvider task) { - this.getIdeSyncTasks().add(task); - } - - /** - * Configures the given task to be run when the IDE reloads the Gradle project. - */ - public void ideSyncTask(Task task) { - this.getIdeSyncTasks().add(task.getProject().getTasks().named(task.getName())); - } - - /** - * Used to request additional Minecraft artifacts from NFRT for advanced usage scenarios. - *

- * Maps a result name to the file it should be written to. - * The result names are specific to the NeoForm process that is being used in the background and may change between - * NeoForge versions. - */ - public abstract MapProperty getAdditionalMinecraftArtifacts(); } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/UnitTest.java b/src/main/java/net/neoforged/moddevgradle/dsl/UnitTest.java index e987f94c..5e9442c1 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/UnitTest.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/UnitTest.java @@ -1,6 +1,6 @@ package net.neoforged.moddevgradle.dsl; -import net.neoforged.moddevgradle.internal.ModDevPlugin; +import net.neoforged.moddevgradle.internal.ModDevRunWorkflow; import org.gradle.api.Project; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; @@ -22,7 +22,10 @@ public UnitTest(Project project) { * Enables the integration. */ public void enable() { - project.getPlugins().getPlugin(ModDevPlugin.class).setupTestTask(); + ModDevRunWorkflow.get(project).configureTesting( + getTestedMod(), + getLoadedMods() + ); } /** diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ArtifactNamingStrategy.java b/src/main/java/net/neoforged/moddevgradle/internal/ArtifactNamingStrategy.java new file mode 100644 index 00000000..b22a44d9 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ArtifactNamingStrategy.java @@ -0,0 +1,16 @@ +package net.neoforged.moddevgradle.internal; + +import org.jetbrains.annotations.ApiStatus; + +@FunctionalInterface +@ApiStatus.Internal +public interface ArtifactNamingStrategy { + static ArtifactNamingStrategy createDefault(String artifactFilenamePrefix) { + return (artifact) -> { + // It's helpful to be able to differentiate the Vanilla jar and the NeoForge jar in classic multiloader setups. + return artifactFilenamePrefix + artifact.defaultSuffix + ".jar"; + }; + } + + String getFilename(WorkflowArtifact artifact); +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/Branding.java b/src/main/java/net/neoforged/moddevgradle/internal/Branding.java index 9b07605f..8d843620 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/Branding.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/Branding.java @@ -1,12 +1,15 @@ package net.neoforged.moddevgradle.internal; +import org.jetbrains.annotations.ApiStatus; + /** * Used to customize the groups of tasks generated by MDG. * * @param publicTaskGroup Use this group for tasks that are considered to be part of the user-interface of MDG. * @param internalTaskGroup Use this group for tasks that are considered to be an implementation detail of MDG. */ -record Branding(String publicTaskGroup, String internalTaskGroup) { +@ApiStatus.Internal +public record Branding(String publicTaskGroup, String internalTaskGroup) { public static final Branding MDG = new Branding("mod development", "mod development/internal"); public static final Branding NEODEV = new Branding("neoforge development", "neoforge development/internal"); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java new file mode 100644 index 00000000..c5e7baa0 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java @@ -0,0 +1,124 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.dsl.DataFileCollection; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurablePublishArtifact; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.attributes.Category; +import org.gradle.api.component.AdhocComponentWithVariants; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.ApiStatus; + +import java.io.File; +import java.util.function.Consumer; + +@ApiStatus.Internal +public final class DataFileCollectionFactory { + public static final String CONFIGURATION_ACCESS_TRANSFORMERS = "accessTransformers"; + + public static final String CONFIGURATION_INTERFACE_INJECTION_DATA = "interfaceInjectionData"; + + private DataFileCollectionFactory() { + } + + public record DefaultDataFileCollections(DataFileCollectionWrapper accessTransformers, + DataFileCollectionWrapper interfaceInjectionData) { + } + + public record DataFileCollectionWrapper(DataFileCollection extension, Configuration configuration) { + } + + public static DefaultDataFileCollections createDefault(Project project) { + // Create an access transformer configuration + var accessTransformers = DataFileCollectionFactory.create( + project, + CONFIGURATION_ACCESS_TRANSFORMERS, + "AccessTransformers to widen visibility of Minecraft classes/fields/methods", + "accesstransformer" + ); + accessTransformers.extension().getFiles().convention(project.provider(() -> { + var collection = project.getObjects().fileCollection(); + + // Only return this when it actually exists + var mainSourceSet = ExtensionUtils.getSourceSets(project).getByName(SourceSet.MAIN_SOURCE_SET_NAME); + for (var resources : mainSourceSet.getResources().getSrcDirs()) { + var defaultPath = new File(resources, "META-INF/accesstransformer.cfg"); + if (project.file(defaultPath).exists()) { + return collection.from(defaultPath.getAbsolutePath()); + } + } + + return collection; + })); + + // Create a configuration for grabbing interface injection data + var interfaceInjectionData = DataFileCollectionFactory.create( + project, + CONFIGURATION_INTERFACE_INJECTION_DATA, + "Interface injection data adds extend/implements clauses for interfaces to Minecraft code at development time", + "interfaceinjection" + ); + + return new DefaultDataFileCollections(accessTransformers, interfaceInjectionData); + } + + public static DataFileCollectionWrapper create(Project project, String name, String description, String category) { + var configuration = project.getConfigurations().create(name, spec -> { + spec.setDescription(description); + spec.setCanBeConsumed(false); + spec.setCanBeResolved(true); + spec.attributes(attributes -> { + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.CATEGORY_ATTRIBUTE.getType(), category)); + }); + }); + + var elementsConfiguration = project.getConfigurations().create(name + "Elements", spec -> { + spec.setDescription("Published data files for " + name); + spec.setCanBeConsumed(true); + spec.setCanBeResolved(false); + spec.attributes(attributes -> { + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.CATEGORY_ATTRIBUTE.getType(), category)); + }); + }); + + // Set up the variant publishing conditionally + var java = (AdhocComponentWithVariants) project.getComponents().getByName("java"); + java.addVariantsFromConfiguration(elementsConfiguration, variant -> { + // This should be invoked lazily, so checking if the artifacts are empty is fine: + // "The details object used to determine what to do with a configuration variant **when publishing**." + if (variant.getConfigurationVariant().getArtifacts().isEmpty()) { + variant.skip(); + } + }); + + var depFactory = project.getDependencyFactory(); + Consumer publishCallback = new Consumer<>() { + ConfigurablePublishArtifact firstArtifact; + int artifactCount; + + @Override + public void accept(Object artifactNotation) { + elementsConfiguration.getDependencies().add(depFactory.create(project.files(artifactNotation))); + project.getArtifacts().add(elementsConfiguration.getName(), artifactNotation, artifact -> { + if (firstArtifact == null) { + firstArtifact = artifact; + artifact.setClassifier(category); + artifactCount = 1; + } else { + if (artifactCount == 1) { + firstArtifact.setClassifier(category + artifactCount); + } + artifact.setClassifier(category + (++artifactCount)); + } + }); + } + }; + + var extension = project.getObjects().newInstance(DataFileCollection.class, publishCallback); + configuration.getDependencies().add(depFactory.create(extension.getFiles())); + + return new DataFileCollectionWrapper(extension, configuration); + } + +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java index 3a7e7324..ba9b6410 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java @@ -11,9 +11,7 @@ import org.gradle.api.Project; import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; -import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.TaskProvider; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.Classpath; @@ -86,8 +84,8 @@ public void configureRuns(Map> prepareRunTask } @Override - public void configureTesting(SetProperty loadedMods, - Property testedMod, + public void configureTesting(Provider> loadedMods, + Provider testedMod, Provider runArgsDir, File gameDirectory, Provider programArgsFile, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java index 17d3ad51..50a73128 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java @@ -8,15 +8,14 @@ import org.gradle.api.Task; import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; -import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.TaskProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.Map; +import java.util.Set; /** * Implementing classes are responsible for registering {@code ideSyncTask} with their IDE. @@ -95,8 +94,8 @@ public final void runTaskOnProjectSync(Object task) { void configureRuns(Map> prepareRunTasks, Iterable runs) { } - void configureTesting(SetProperty loadedMods, - Property testedMod, + void configureTesting(Provider> loadedMods, + Provider testedMod, Provider runArgsDir, File gameDirectory, Provider programArgsFile, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java index 0f8ed346..35bf723e 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java @@ -11,9 +11,7 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.ExtensionAware; -import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.internal.DefaultTaskExecutionRequest; @@ -112,8 +110,8 @@ public void configureRuns(Map> prepareRunTask } @Override - public void configureTesting(SetProperty loadedMods, - Property testedMod, + public void configureTesting(Provider> loadedMods, + Provider testedMod, Provider runArgsDir, File gameDirectory, Provider programArgsFile, @@ -141,7 +139,7 @@ public void configureTesting(SetProperty loadedMods, if (intelliJRunConfigurations != null) { intelliJRunConfigurations.defaults(JUnit.class, jUnitDefaults -> { // $MODULE_WORKING_DIR$ is documented here: https://www.jetbrains.com/help/idea/absolute-path-variables.html - jUnitDefaults.setWorkingDirectory("$MODULE_WORKING_DIR$/" + ModDevPlugin.JUNIT_GAME_DIR); + jUnitDefaults.setWorkingDirectory("$MODULE_WORKING_DIR$/" + ModDevRunWorkflow.JUNIT_GAME_DIR); jUnitDefaults.setVmParameters( // The FML JUnit plugin uses this system property to read a file containing the program arguments needed to launch // NOTE: IntelliJ does not support $MODULE_WORKING_DIR$ in VM Arguments diff --git a/src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java new file mode 100644 index 00000000..fab93146 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java @@ -0,0 +1,27 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.tasks.JarJar; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class JarJarPlugin implements Plugin { + @Override + public void apply(Project project) { + SourceSetContainer sourceSets = ExtensionUtils.getExtension(project, "sourceSets", SourceSetContainer.class); + sourceSets.all(sourceSet -> { + var jarJarTask = JarJar.registerWithConfiguration(project, sourceSet.getTaskName(null, "jarJar")); + jarJarTask.configure(task -> task.setGroup(Branding.MDG.internalTaskGroup())); + + // The target jar task for this source set might not exist, and #named(String) requires the task to exist + var jarTaskName = sourceSet.getJarTaskName(); + project.getTasks().withType(AbstractArchiveTask.class).named(name -> name.equals(jarTaskName)).configureEach(task -> { + task.from(jarJarTask); + }); + }); + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java new file mode 100644 index 00000000..23170a81 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -0,0 +1,327 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.minecraftdependencies.MinecraftDistribution; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; +import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; +import net.neoforged.nfrtgradle.DownloadAssets; +import org.gradle.api.Named; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.DocsType; +import org.gradle.api.attributes.Usage; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * The workflow needed to produce artifacts and assets for compiling and running a mod. + */ +@ApiStatus.Internal +public record ModDevArtifactsWorkflow( + Project project, + TaskProvider createArtifacts, + TaskProvider downloadAssets, + Configuration runtimeDependencies, + Configuration compileDependencies, + Provider modDevBuildDir) { + private static final String EXTENSION_NAME = "__internal_modDevArtifactsWorkflow"; + + public static ModDevArtifactsWorkflow get(Project project) { + var result = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevArtifactsWorkflow.class); + if (result == null) { + throw new IllegalStateException("Mod development has not been enabled yet for project " + project); + } + return result; + } + + public static ModDevArtifactsWorkflow create(Project project, + Branding branding, + ModDevExtension extension, + ModuleDependency moddingPlatformDependency, + String moddingPlatformDataDependencyNotation, + ModuleDependency recompilableMinecraftWorkflowDependency, + String recompilableMinecraftWorkflowDataDependencyNotation, + ModuleDependency gameLibrariesDependency, + ArtifactNamingStrategy artifactNamingStrategy, + Configuration accessTransformers, + Configuration interfaceInjectionData, + VersionCapabilities versionCapabilities + ) { + var ideIntegration = IdeIntegration.of(project, branding); + + // We use this directory to store intermediate files used during moddev + var modDevBuildDir = project.getLayout().getBuildDirectory().dir("moddev"); + + var createManifestConfigurations = configureArtifactManifestConfigurations( + project, + moddingPlatformDependency, + recompilableMinecraftWorkflowDependency + ); + + var dependencyFactory = project.getDependencyFactory(); + var configurations = project.getConfigurations(); + var tasks = project.getTasks(); + var javaExtension = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); + + // Users can theoretically compile their mods at higher java versions than used by Minecraft, + // but it's more important to default the common user to the right Java version. + var javaToolchainService = ExtensionUtils.getExtension(project, "javaToolchains", JavaToolchainService.class); + + // Try to give people at least a fighting chance to run on the correct java version + var toolchainSpec = javaExtension.getToolchain(); + try { + toolchainSpec.getLanguageVersion().convention(JavaLanguageVersion.of(versionCapabilities.javaVersion())); + } catch (IllegalStateException e) { + // We tried our best + } + + // Add a filtered parchment repository automatically if enabled + var parchment = extension.getParchment(); + var parchmentData = configurations.create("parchmentData", spec -> { + spec.setDescription("Data used to add parameter names and javadoc to Minecraft sources"); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.setTransitive(false); // Expect a single result + spec.getDependencies().addLater(parchment.getParchmentArtifact().map(dependencyFactory::create)); + }); + + // it has to contain client-extra to be loaded by FML, and it must be added to the legacy CP + var createArtifacts = tasks.register("createMinecraftArtifacts", CreateMinecraftArtifacts.class, task -> { + task.setGroup(branding.internalTaskGroup()); + task.setDescription("Creates the NeoForge and Minecraft artifacts by invoking NFRT."); + for (var configuration : createManifestConfigurations) { + task.addArtifactsToManifest(configuration); + } + + // NFRT needs access to a JDK of the right version to be able to correctly decompile and recompile the code + task.getToolsJavaExecutable().set(javaToolchainService + .launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(versionCapabilities.javaVersion()))) + .map(javaLauncher -> javaLauncher.getExecutablePath().getAsFile().getAbsolutePath()) + ); + + task.getAccessTransformers().from(accessTransformers); + task.getInterfaceInjectionData().from(interfaceInjectionData); + task.getValidateAccessTransformers().set(extension.getValidateAccessTransformers()); + task.getParchmentData().from(parchmentData); + task.getParchmentEnabled().set(parchment.getEnabled()); + task.getParchmentConflictResolutionPrefix().set(parchment.getConflictResolutionPrefix()); + + Function> artifactPathStrategy = artifact -> + modDevBuildDir.map(dir -> dir.dir("artifacts").file(artifactNamingStrategy.getFilename(artifact))); + + task.getCompiledArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); + task.getCompiledWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); + task.getSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); + task.getResourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_RESOURCES)); + + task.getNeoForgeArtifact().set(moddingPlatformDataDependencyNotation); + task.getNeoFormArtifact().set(recompilableMinecraftWorkflowDataDependencyNotation); + task.getAdditionalResults().putAll(extension.getAdditionalMinecraftArtifacts()); + }); + ideIntegration.runTaskOnProjectSync(createArtifacts); + + var downloadAssets = tasks.register("downloadAssets", DownloadAssets.class, task -> { + // Not in the internal group in case someone wants to "preload" the asset before they go offline + task.setGroup(branding.publicTaskGroup()); + task.setDescription("Downloads the Minecraft assets and asset index needed to run a Minecraft client or generate client-side resources."); + // While downloadAssets does not require *all* of the dependencies, it does need NeoForge/NeoForm to benefit + // from any caching/overrides applied to these dependencies in Gradle + for (var configuration : createManifestConfigurations) { + task.addArtifactsToManifest(configuration); + } + task.getAssetPropertiesFile().set(modDevBuildDir.map(dir -> dir.file("minecraft_assets.properties"))); + task.getNeoForgeArtifact().set(moddingPlatformDataDependencyNotation); + task.getNeoFormArtifact().set(recompilableMinecraftWorkflowDataDependencyNotation); + }); + + // For IntelliJ, we attach a combined sources+classes artifact which enables an "Attach Sources..." link for IJ users + // Otherwise, attaching sources is a pain for IJ users. + Provider minecraftClassesArtifact; + if (ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + minecraftClassesArtifact = createArtifacts.map(task -> project.files(task.getCompiledWithSourcesArtifact())); + } else { + minecraftClassesArtifact = createArtifacts.map(task -> project.files(task.getCompiledArtifact())); + } + + // Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath. + // We cannot use "runtimeOnly", since the contents of that are published. + var runtimeDependencies = configurations.create("modDevRuntimeDependencies", config -> { + config.setDescription("The runtime dependencies to develop a mod for, including Minecraft classes and modding platform classes."); + config.setCanBeResolved(false); + config.setCanBeDeclared(false); + config.setCanBeConsumed(false); + + config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); + config.getDependencies().addLater(createArtifacts.map(task -> project.files(task.getResourcesArtifact())).map(dependencyFactory::create)); + // Technically, the Minecraft dependencies do not strictly need to be on the classpath because they are pulled from the legacy class path. + // However, we do it anyway because this matches production environments, and allows launch proxies such as DevLogin to use Minecraft's libraries. + config.getDependencies().add(gameLibrariesDependency); + }); + + // Configuration in which we place the required dependencies to develop mods for use in the compile-classpath. + // While compile only is not published, we also use a configuration here to be consistent. + var compileDependencies = configurations.create("modDevCompileDependencies", config -> { + config.setDescription("The compile-time dependencies to develop a mod, including Minecraft and modding platform classes."); + config.setCanBeResolved(false); + config.setCanBeDeclared(false); + config.setCanBeConsumed(false); + config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); + config.getDependencies().add(gameLibrariesDependency); + }); + + // For IDEs that support it, link the source/binary artifacts if we use separated ones + if (!ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + ideIntegration.attachSources( + Map.of( + createArtifacts.get().getCompiledArtifact(), + createArtifacts.get().getSourcesArtifact() + ) + ); + } + + var result = new ModDevArtifactsWorkflow( + project, + createArtifacts, + downloadAssets, + runtimeDependencies, + compileDependencies, + modDevBuildDir + ); + project.getExtensions().add(ModDevArtifactsWorkflow.class, EXTENSION_NAME, result); + return result; + } + + public void addToSourceSet(String name) { + var configurations = project.getConfigurations(); + var sourceSets = ExtensionUtils.getSourceSets(project); + var sourceSet = sourceSets.getByName(name); + configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(runtimeDependencies); + configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(compileDependencies); + } + + /** + * Collects all dependencies needed by the NeoFormRuntime + */ + private static List configureArtifactManifestConfigurations( + Project project, + @Nullable ModuleDependency moddingPlatformDependency, + @Nullable ModuleDependency recompilableMinecraftWorkflowDependency + ) { + var configurations = project.getConfigurations(); + + var configurationPrefix = "neoFormRuntimeDependencies"; + + var result = new ArrayList(); + + // Gradle prevents us from having dependencies with "incompatible attributes" in the same configuration. + // What constitutes incompatible cannot be overridden on a per-configuration basis. + var neoForgeClassesAndData = configurations.create(configurationPrefix + "NeoForgeClasses", spec -> { + spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (NeoForge classes)"); + spec.setCanBeConsumed(false); + spec.setCanBeResolved(true); + if (moddingPlatformDependency != null) { + spec.getDependencies().add(moddingPlatformDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-bundle"))); + } + + // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode + if (recompilableMinecraftWorkflowDependency != null) { + spec.getDependencies().add(recompilableMinecraftWorkflowDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoform"))); + } + }); + result.add(neoForgeClassesAndData); + + if (moddingPlatformDependency != null) { + var neoForgeSources = configurations.create(configurationPrefix + "NeoForgeSources", spec -> { + spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (NeoForge sources)"); + spec.setCanBeConsumed(false); + spec.setCanBeResolved(true); + spec.getDependencies().add(moddingPlatformDependency); + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, Category.CATEGORY_ATTRIBUTE, Category.DOCUMENTATION); + setNamedAttribute(project, attributes, DocsType.DOCS_TYPE_ATTRIBUTE, DocsType.SOURCES); + }); + }); + result.add(neoForgeSources); + } + + // Compile-time dependencies used by NeoForm, NeoForge and Minecraft. + // Also includes any classes referenced by compiled Minecraft code (used by decompilers, renamers, etc.) + var compileClasspath = configurations.create(configurationPrefix + "CompileClasspath", spec -> { + spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (Classpath)"); + spec.setCanBeConsumed(false); + spec.setCanBeResolved(true); + if (moddingPlatformDependency != null) { + spec.getDependencies().add(moddingPlatformDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-dependencies"))); + } + if (recompilableMinecraftWorkflowDependency != null) { + // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode + spec.getDependencies().add(recompilableMinecraftWorkflowDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoform-dependencies"))); + } + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_API); + setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); + }); + }); + result.add(compileClasspath); + + // Runtime-time dependencies used by NeoForm, NeoForge and Minecraft. + var runtimeClasspath = configurations.create(configurationPrefix + "RuntimeClasspath", spec -> { + spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (Classpath)"); + spec.setCanBeConsumed(false); + spec.setCanBeResolved(true); + if (moddingPlatformDependency != null) { + spec.getDependencies().add(moddingPlatformDependency); // Universal Jar + spec.getDependencies().add(moddingPlatformDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-dependencies"))); + } + // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode + if (recompilableMinecraftWorkflowDependency != null) { + spec.getDependencies().add(recompilableMinecraftWorkflowDependency.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoform-dependencies"))); + } + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); + }); + }); + result.add(runtimeClasspath); + + return result; + } + + public Provider requestAdditionalMinecraftArtifact(String id, String filename) { + return requestAdditionalMinecraftArtifact(id, modDevBuildDir.map(dir -> dir.file(filename))); + } + + public Provider requestAdditionalMinecraftArtifact(String id, Provider path) { + createArtifacts.configure(task -> task.getAdditionalResults().put(id, path.map(RegularFile::getAsFile))); + return project.getLayout().file( + createArtifacts.flatMap(task -> task.getAdditionalResults().getting(id)) + ); + } + + private static void setNamedAttribute(Project project, AttributeContainer attributes, Attribute attribute, String value) { + attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java new file mode 100644 index 00000000..acfda39c --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java @@ -0,0 +1,145 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.dsl.DataFileCollection; +import net.neoforged.moddevgradle.dsl.ModModel; +import net.neoforged.moddevgradle.dsl.Parchment; +import net.neoforged.moddevgradle.dsl.RunModel; +import org.gradle.api.Action; +import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.TaskProvider; + +import javax.inject.Inject; +import java.io.File; + +public abstract class ModDevExtension { + private final NamedDomainObjectContainer mods; + private final NamedDomainObjectContainer runs; + private final Parchment parchment; + + private final DataFileCollection accessTransformers; + private final DataFileCollection interfaceInjectionData; + + @Inject + public ModDevExtension(Project project, + DataFileCollection accessTransformers, + DataFileCollection interfaceInjectionData) { + mods = project.container(ModModel.class); + runs = project.container(RunModel.class, name -> project.getObjects().newInstance(RunModel.class, name, project, mods)); + parchment = project.getObjects().newInstance(Parchment.class); + this.accessTransformers = accessTransformers; + this.interfaceInjectionData = interfaceInjectionData; + getValidateAccessTransformers().convention(false); + } + + /** + * The list of additional access transformers that should be applied to the Minecraft source code. + *

+ * If you do not set this property, the plugin will look for an access transformer file at + * {@code META-INF/accesstransformer.cfg} relative to your main source sets resource directories. + * + * @see Access Transformer File Format + */ + public void accessTransformers(Action action) { + action.execute(accessTransformers); + } + + public DataFileCollection getAccessTransformers() { + return accessTransformers; + } + + /** + * Replaces current access transformers. + */ + public void setAccessTransformers(Object... paths) { + getAccessTransformers().getFiles().setFrom(paths); + } + + /** + * The data-files describing additional interface implementation declarations to be added to + * Minecraft classes. + *

+ * This is an advanced property: Injecting interfaces in your development environment using this property will not implement + * the interfaces in your published mod. You have to use Mixin or ASM to do that. + * + * @see Interface Injection Data Format + */ + public void interfaceInjectionData(Action action) { + action.execute(interfaceInjectionData); + } + + public DataFileCollection getInterfaceInjectionData() { + return interfaceInjectionData; + } + + /** + * Replaces current interface injection data files. + */ + public void setInterfaceInjectionData(Object... paths) { + getInterfaceInjectionData().getFiles().setFrom(paths); + } + + /** + * Enable access transformer validation, raising fatal errors if an AT targets a member that doesn't exist. + *

+ * Default {@code false}
+ */ + public abstract Property getValidateAccessTransformers(); + + public NamedDomainObjectContainer getMods() { + return mods; + } + + public void mods(Action> action) { + action.execute(mods); + } + + public NamedDomainObjectContainer getRuns() { + return runs; + } + + public void runs(Action> action) { + action.execute(runs); + } + + public Parchment getParchment() { + return parchment; + } + + public void parchment(Action action) { + action.execute(parchment); + } + + /** + * The tasks to be run when the IDE reloads the Gradle project. + */ + public abstract ListProperty> getIdeSyncTasks(); + + /** + * Configures the given task to be run when the IDE reloads the Gradle project. + */ + public void ideSyncTask(TaskProvider task) { + this.getIdeSyncTasks().add(task); + } + + /** + * Configures the given task to be run when the IDE reloads the Gradle project. + */ + public void ideSyncTask(Task task) { + this.getIdeSyncTasks().add(task.getProject().getTasks().named(task.getName())); + } + + /** + * Used to request additional Minecraft artifacts from NFRT for advanced usage scenarios. + *

+ * Maps a result name to the file it should be written to. + * The result names are specific to the NeoForm process that is being used in the background and may change between + * NeoForge versions. + */ + public abstract MapProperty getAdditionalMinecraftArtifacts(); +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 9fcad017..e70e6f6f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -1,61 +1,18 @@ package net.neoforged.moddevgradle.internal; import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin; -import net.neoforged.minecraftdependencies.MinecraftDistribution; -import net.neoforged.moddevgradle.dsl.DataFileCollection; -import net.neoforged.moddevgradle.dsl.InternalModelHelper; -import net.neoforged.moddevgradle.dsl.ModModel; +import net.neoforged.moddevgradle.dsl.ModdingVersionSettings; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; -import net.neoforged.moddevgradle.dsl.RunModel; -import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; -import net.neoforged.moddevgradle.tasks.JarJar; -import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; -import net.neoforged.nfrtgradle.DownloadAssets; import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; -import org.gradle.api.DomainObjectCollection; -import org.gradle.api.Named; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.artifacts.ConfigurablePublishArtifact; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.attributes.Attribute; -import org.gradle.api.attributes.AttributeContainer; -import org.gradle.api.attributes.Category; -import org.gradle.api.attributes.DocsType; -import org.gradle.api.attributes.Usage; -import org.gradle.api.component.AdhocComponentWithVariants; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.Directory; -import org.gradle.api.file.RegularFile; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.plugins.JavaLibraryPlugin; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.api.tasks.bundling.AbstractArchiveTask; -import org.gradle.api.tasks.testing.Test; -import org.gradle.jvm.toolchain.JavaLanguageVersion; -import org.gradle.jvm.toolchain.JavaToolchainService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; -import javax.inject.Inject; -import java.io.File; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; /** * The main plugin class. @@ -63,38 +20,12 @@ public class ModDevPlugin implements Plugin { private static final Logger LOG = LoggerFactory.getLogger(ModDevPlugin.class); - /** - * This must be relative to the project directory since we can only set this to the same project-relative - * directory across all subprojects due to IntelliJ limitations. - */ - static final String JUNIT_GAME_DIR = "build/minecraft-junit"; - - /** - * Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath. - * We cannot use "runtimeOnly", since the contents of that are published. - */ - public static final String CONFIGURATION_RUNTIME_DEPENDENCIES = "neoForgeRuntimeDependencies"; - - /** - * Name of the configuration in which we place the required dependencies to develop mods for use in the compile-classpath. - * While compile only is not published, we also use a configuration here to be consistent. - */ - public static final String CONFIGURATION_COMPILE_DEPENDENCIES = "neoForgeCompileDependencies"; - - private final ObjectFactory objectFactory; - - private Runnable configureTesting = null; - - @Inject - public ModDevPlugin(ObjectFactory objectFactory) { - this.objectFactory = objectFactory; - } - @Override public void apply(Project project) { project.getPlugins().apply(JavaLibraryPlugin.class); project.getPlugins().apply(NeoFormRuntimePlugin.class); project.getPlugins().apply(MinecraftDependenciesPlugin.class); + project.getPlugins().apply(JarJarPlugin.class); // Do not apply the repositories automatically if they have been applied at the settings-level. // It's still possible to apply them manually, though. @@ -103,753 +34,104 @@ public void apply(Project project) { } else { LOG.info("Not enabling NeoForged repositories since they were applied at the settings level"); } - var javaExtension = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); - - var configurations = project.getConfigurations(); - var layout = project.getLayout(); - var tasks = project.getTasks(); - - var ideIntegration = IdeIntegration.of(project, Branding.MDG); - - // We use this directory to store intermediate files used during moddev - var modDevBuildDir = layout.getBuildDirectory().dir("moddev"); - - // Create an access transformer configuration - var accessTransformers = dataFileConfiguration( - project, - "accessTransformers", - "AccessTransformers to widen visibility of Minecraft classes/fields/methods", - "accesstransformer" - ); - accessTransformers.extension.getFiles().convention(project.provider(() -> { - var collection = project.getObjects().fileCollection(); - - // Only return this when it actually exists - var mainSourceSet = ExtensionUtils.getSourceSets(project).getByName(SourceSet.MAIN_SOURCE_SET_NAME); - for (var resources : mainSourceSet.getResources().getSrcDirs()) { - var defaultPath = new File(resources, "META-INF/accesstransformer.cfg"); - if (project.file(defaultPath).exists()) { - return collection.from(defaultPath.getAbsolutePath()); - } - } - - return collection; - })); - - // Create a configuration for grabbing interface injection data - var interfaceInjectionData = dataFileConfiguration( - project, - "interfaceInjectionData", - "Interface injection data adds extend/implements clauses for interfaces to Minecraft code at development time", - "interfaceinjection" - ); + var dataFileCollections = DataFileCollectionFactory.createDefault(project); var extension = project.getExtensions().create( NeoForgeExtension.NAME, NeoForgeExtension.class, - accessTransformers.extension, - interfaceInjectionData.extension + dataFileCollections.accessTransformers().extension(), + dataFileCollections.interfaceInjectionData().extension() ); - ideIntegration.runTaskOnProjectSync(extension.getIdeSyncTasks()); - var dependencyFactory = project.getDependencyFactory(); - - Provider versionCapabilities = extension.getVersion().map(v -> { - // Temporary until we have imperative configuration - if (project.getPlugins().hasPlugin("net.neoforged.moddev.legacyforge")) { - return VersionCapabilities.ofForgeVersion(v); - } else { - return VersionCapabilities.ofNeoForgeVersion(v); - } - }) - .orElse(extension.getNeoFormVersion().map(VersionCapabilities::ofNeoFormVersion)) - .orElse(VersionCapabilities.latest()); - - // When a NeoForge version is specified, we use the dependencies published by that, and otherwise - // we fall back to a potentially specified NeoForm version, which allows us to run in "Vanilla" mode. - var neoForgeModDevLibrariesDependency = extension.getNeoForgeArtifact().map(artifactId -> { - return dependencyFactory.create(artifactId) - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-dependencies"); - }); - }).orElse(extension.getNeoFormArtifact().map(artifact -> { - return dependencyFactory.create(artifact) - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoform-dependencies"); - }); - })); - - var createManifestConfigurations = configureArtifactManifestConfigurations(project, extension); - - // Add a filtered parchment repository automatically if enabled - var parchment = extension.getParchment(); - var parchmentData = configurations.create("parchmentData", spec -> { - spec.setDescription("Data used to add parameter names and javadoc to Minecraft sources"); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.setTransitive(false); // Expect a single result - spec.getDependencies().addLater(parchment.getParchmentArtifact().map(project.getDependencyFactory()::create)); - }); + IdeIntegration.of(project, Branding.MDG).runTaskOnProjectSync(extension.getIdeSyncTasks()); + } - // it has to contain client-extra to be loaded by FML, and it must be added to the legacy CP - var createArtifacts = tasks.register("createMinecraftArtifacts", CreateMinecraftArtifacts.class, task -> { - task.setGroup(Branding.MDG.internalTaskGroup()); - task.setDescription("Creates the NeoForge and Minecraft artifacts by invoking NFRT."); - for (var configuration : createManifestConfigurations) { - task.addArtifactsToManifest(configuration); - } + public void enableModding( + Project project, + ModdingVersionSettings settings, + ModDevExtension extension + ) { + var neoForgeVersion = settings.getNeoForgeVersion(); + var neoFormVersion = settings.getNeoFormVersion(); + if (neoForgeVersion == null && neoFormVersion == null) { + throw new IllegalArgumentException("You must specify at least a NeoForge or a NeoForm version for vanilla-only mode"); + } - task.getAccessTransformers().from(accessTransformers.configuration); - task.getInterfaceInjectionData().from(interfaceInjectionData.configuration); - task.getValidateAccessTransformers().set(extension.getValidateAccessTransformers()); - task.getParchmentData().from(parchmentData); - task.getParchmentEnabled().set(parchment.getEnabled()); - task.getParchmentConflictResolutionPrefix().set(parchment.getConflictResolutionPrefix()); + var dependencyFactory = project.getDependencyFactory(); - var minecraftArtifactsDir = modDevBuildDir.map(dir -> dir.dir("artifacts")); - Function> jarPathFactory = suffix -> { - return minecraftArtifactsDir.zip( - // It's helpful to be able to differentiate the Vanilla jar and the NeoForge jar in classic multiloader setups. - extension.getNeoForgeArtifact().map(art -> { - var split = art.split(":", 3); - return split[1] + "-" + split[2]; - }) - .orElse(extension.getNeoFormArtifact().map(v -> "vanilla-" + v.split(":", 3)[2])), - (dir, prefix) -> dir.file(prefix + "-minecraft" + suffix + ".jar")); - }; - task.getCompiledArtifact().set(jarPathFactory.apply("")); - task.getCompiledWithSourcesArtifact().set(jarPathFactory.apply("-merged")); - task.getSourcesArtifact().set(jarPathFactory.apply("-sources")); - task.getResourcesArtifact().set(minecraftArtifactsDir.zip( - extension.getNeoForgeArtifact().map(art -> { - var split = art.split(":", 3); - return split[2] + "-" + split[1]; - }) - .orElse(extension.getNeoFormArtifact().map(v -> "vanilla-" + v.split(":", 3)[2])), - // To support older versions of FML, which pick up the Minecraft jar by looking on the LCP for "forge-", - // we have to ensure client-extra does *not* start with "forge-". - (dir, prefix) -> dir.file("client-extra-aka-minecraft-resources-" + prefix + ".jar") - )); + ModuleDependency neoForgeModule = null; + ModuleDependency modulePathDependency = null; + ModuleDependency runTypesDataDependency = null; + ModuleDependency testFixturesDependency = null; + String moddingPlatformDataDependencyNotation = null; + if (neoForgeVersion != null) { + neoForgeModule = dependencyFactory.create("net.neoforged:neoforge:" + neoForgeVersion); + moddingPlatformDataDependencyNotation = "net.neoforged:neoforge:" + neoForgeVersion + ":userdev"; + runTypesDataDependency = neoForgeModule.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); + modulePathDependency = neoForgeModule.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) + // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. + .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); + testFixturesDependency = neoForgeModule.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-test-fixtures")); + } - task.getNeoForgeArtifact().set(getNeoForgeUserDevDependencyNotation(extension)); - task.getNeoFormArtifact().set(getNeoFormDataDependencyNotation(extension)); - task.getAdditionalResults().putAll(extension.getAdditionalMinecraftArtifacts()); - }); - ideIntegration.runTaskOnProjectSync(createArtifacts); + ModuleDependency neoFormModule = null; + String recompilableMinecraftWorkflowDataDependencyNotation = null; + if (neoFormVersion != null) { + neoFormModule = dependencyFactory.create("net.neoforged:neoform:" + neoFormVersion); + recompilableMinecraftWorkflowDataDependencyNotation = "net.neoforged:neoform:" + neoFormVersion + "@zip"; + } - var downloadAssets = tasks.register("downloadAssets", DownloadAssets.class, task -> { - // Not in the internal group in case someone wants to "preload" the asset before they go offline - task.setGroup(Branding.MDG.publicTaskGroup()); - task.setDescription("Downloads the Minecraft assets and asset index needed to run a Minecraft client or generate client-side resources."); - // While downloadAssets does not require *all* of the dependencies, it does need NeoForge/NeoForm to benefit - // from any caching/overrides applied to these dependencies in Gradle - for (var configuration : createManifestConfigurations) { - task.addArtifactsToManifest(configuration); - } - task.getAssetPropertiesFile().set(modDevBuildDir.map(dir -> dir.file("minecraft_assets.properties"))); - task.getNeoForgeArtifact().set(getNeoForgeUserDevDependencyNotation(extension)); - task.getNeoFormArtifact().set(getNeoFormDataDependencyNotation(extension)); - }); + // When a NeoForge version is specified, we use the dependencies published by that, and otherwise + // we fall back to a potentially specified NeoForm version, which allows us to run in "Vanilla" mode. + ModuleDependency neoForgeModDevLibrariesDependency; + ArtifactNamingStrategy artifactNamingStrategy; + if (neoForgeModule != null) { + neoForgeModDevLibrariesDependency = neoForgeModule.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); - // For IntelliJ, we attach a combined sources+classes artifact which enables an "Attach Sources..." link for IJ users - // Otherwise, attaching sources is a pain for IJ users. - Provider minecraftClassesArtifact; - if (ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { - minecraftClassesArtifact = createArtifacts.map(task -> project.files(task.getCompiledWithSourcesArtifact())); + artifactNamingStrategy = ArtifactNamingStrategy.createDefault("neoforge-" + neoForgeVersion); } else { - minecraftClassesArtifact = createArtifacts.map(task -> project.files(task.getCompiledArtifact())); + neoForgeModDevLibrariesDependency = neoFormModule.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); + artifactNamingStrategy = ArtifactNamingStrategy.createDefault("vanilla-" + neoFormVersion); } - configurations.create(CONFIGURATION_RUNTIME_DEPENDENCIES, config -> { - config.setDescription("The runtime dependencies to develop a mod for NeoForge, including Minecraft classes."); - config.setCanBeResolved(false); - config.setCanBeConsumed(false); - - config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); - config.getDependencies().addLater(createArtifacts.map(task -> project.files(task.getResourcesArtifact())).map(dependencyFactory::create)); - // Technically the Minecraft dependencies do not strictly need to be on the classpath because they are pulled from the legacy class path. - // However, we do it anyway because this matches production environments, and allows launch proxies such as DevLogin to use Minecraft's libraries. - config.getDependencies().addLater(neoForgeModDevLibrariesDependency); - }); - - configurations.create(CONFIGURATION_COMPILE_DEPENDENCIES, config -> { - config.setDescription("The compile-time dependencies to develop a mod for NeoForge, including Minecraft classes."); - config.setCanBeResolved(false); - config.setCanBeConsumed(false); - config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); - config.getDependencies().addLater(neoForgeModDevLibrariesDependency); - }); - - var sourceSets = ExtensionUtils.getSourceSets(project); - extension.addModdingDependenciesTo(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)); - - // Try to give people at least a fighting chance to run on the correct java version - project.afterEvaluate(ignored -> { - var toolchainSpec = javaExtension.getToolchain(); - try { - toolchainSpec.getLanguageVersion().convention(versionCapabilities.map(VersionCapabilities::javaVersion).map(JavaLanguageVersion::of)); - } catch (IllegalStateException e) { - // We tried our best - } - }); - - // Let's try to get the userdev JSON out of the universal jar - // I don't like having to use a configuration for this... - var userDevConfigOnly = project.getConfigurations().create("neoForgeConfigOnly", spec -> { - spec.setDescription("Resolves exclusively the NeoForge userdev JSON for configuring runs"); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.setTransitive(false); - spec.getDependencies().addLater(extension.getNeoForgeArtifact().map(artifact -> { - return dependencyFactory.create(artifact) - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-moddev-config"); - }); - })); - }); - - var additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { - spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - - spec.getDependencies().addLater(neoForgeModDevLibrariesDependency); - addClientResources(project, spec, createArtifacts); - }); + var configurations = project.getConfigurations(); - // This defines the module path for runs - // NOTE: When running in vanilla mode, this provider is undefined and will not result in an actual dependency - var modulePathDependency = extension.getNeoForgeArtifact().map(artifactId -> { - return dependencyFactory.create(artifactId) - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-moddev-module-path"); - }) - // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. - .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); - }); + var versionCapabilities = neoForgeVersion != null ? VersionCapabilities.ofNeoForgeVersion(neoForgeVersion) + : VersionCapabilities.ofNeoFormVersion(neoFormVersion); - setupRuns( + var artifacts = ModDevArtifactsWorkflow.create( project, Branding.MDG, - modDevBuildDir, - extension.getRuns(), - userDevConfigOnly, - modulePath -> modulePath.getDependencies().addLater(modulePathDependency), - legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), - downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile), + extension, + neoForgeModule, + moddingPlatformDataDependencyNotation, + neoFormModule, + recompilableMinecraftWorkflowDataDependencyNotation, + neoForgeModDevLibrariesDependency, + artifactNamingStrategy, + configurations.getByName(DataFileCollectionFactory.CONFIGURATION_ACCESS_TRANSFORMERS), + configurations.getByName(DataFileCollectionFactory.CONFIGURATION_INTERFACE_INJECTION_DATA), versionCapabilities ); - setupJarJar(project); - - configureTesting = () -> { - // Weirdly enough, testCompileOnly extends from compileOnlyApi, and not compileOnly - configurations.named(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME).configure(configuration -> { - configuration.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); - configuration.getDependencies().addLater(neoForgeModDevLibrariesDependency); - }); - - var testFixtures = configurations.create("neoForgeTestFixtures", config -> { - config.setDescription("Additional JUnit helpers provided by NeoForge"); - config.setCanBeResolved(false); - config.setCanBeConsumed(false); - config.getDependencies().addLater(extension.getNeoForgeArtifact().map(artifact -> { - return dependencyFactory.create(artifact) - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-moddev-test-fixtures"); - }); - })); - }); - - configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, files -> { - files.extendsFrom(configurations.getByName(CONFIGURATION_RUNTIME_DEPENDENCIES)); - files.extendsFrom(testFixtures); - }); - - setupTestTask( - project, - Branding.MDG, - userDevConfigOnly, - tasks.named("test", Test.class), - extension.getUnitTest().getLoadedMods(), - extension.getUnitTest().getTestedMod(), - modDevBuildDir, - modulePath -> modulePath.getDependencies().addLater(modulePathDependency), - spec -> { - spec.getDependencies().addLater(neoForgeModDevLibrariesDependency); - addClientResources(project, spec, createArtifacts); - }, - downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile) - ); - }; - - // For IDEs that support it, link the source/binary artifacts if we use separated ones - if (!ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { - ideIntegration.attachSources( - Map.of( - createArtifacts.get().getCompiledArtifact(), - createArtifacts.get().getSourcesArtifact() - ) - ); + for (var sourceSet : settings.getEnabledSourceSets().get()) { + artifacts.addToSourceSet(sourceSet.getName()); } - } - // FML searches for client resources on the legacy classpath - private static void addClientResources(Project project, Configuration spec, TaskProvider createArtifacts) { - // FML searches for client resources on the legacy classpath - spec.getDependencies().add( - project.getDependencyFactory().create( - project.files(createArtifacts.flatMap(CreateMinecraftArtifacts::getResourcesArtifact)) - ) + ModDevRunWorkflow.create( + project, + Branding.MDG, + artifacts, + modulePathDependency, + runTypesDataDependency, + testFixturesDependency, + neoForgeModDevLibrariesDependency, + extension.getRuns(), + versionCapabilities ); } - - private static Provider getNeoFormDataDependencyNotation(NeoForgeExtension extension) { - return extension.getNeoFormArtifact().map(art -> art + "@zip"); - } - - private static Provider getNeoForgeUserDevDependencyNotation(NeoForgeExtension extension) { - return extension.getNeoForgeArtifact().map(art -> art + ":userdev"); - } - - /** - * Collects all dependencies needed by the NeoFormRuntime - */ - private List configureArtifactManifestConfigurations(Project project, NeoForgeExtension extension) { - var configurations = project.getConfigurations(); - var dependencyFactory = project.getDependencyFactory(); - - var configurationPrefix = "neoFormRuntimeDependencies"; - - Provider neoForgeDependency = extension.getNeoForgeArtifact().map(dependencyFactory::create); - Provider neoFormDependency = extension.getNeoFormArtifact().map(dependencyFactory::create); - - // Gradle prevents us from having dependencies with "incompatible attributes" in the same configuration. - // What constitutes incompatible cannot be overridden on a per-configuration basis. - var neoForgeClassesAndData = configurations.create(configurationPrefix + "NeoForgeClasses", spec -> { - spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (NeoForge classes)"); - spec.setCanBeConsumed(false); - spec.setCanBeResolved(true); - spec.getDependencies().addLater(neoForgeDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-moddev-bundle"); - }))); - - // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode - spec.getDependencies().addLater(neoFormDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoform"); - }))); - }); - - // This configuration is empty when running in Vanilla-mode. - var neoForgeSources = configurations.create(configurationPrefix + "NeoForgeSources", spec -> { - spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (NeoForge sources)"); - spec.setCanBeConsumed(false); - spec.setCanBeResolved(true); - spec.getDependencies().addLater(neoForgeDependency); - spec.attributes(attributes -> { - setNamedAttribute(project, attributes, Category.CATEGORY_ATTRIBUTE, Category.DOCUMENTATION); - setNamedAttribute(project, attributes, DocsType.DOCS_TYPE_ATTRIBUTE, DocsType.SOURCES); - }); - }); - - // Compile-time dependencies used by NeoForm, NeoForge and Minecraft. - // Also includes any classes referenced by compiled Minecraft code (used by decompilers, renamers, etc.) - var compileClasspath = configurations.create(configurationPrefix + "CompileClasspath", spec -> { - spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (Classpath)"); - spec.setCanBeConsumed(false); - spec.setCanBeResolved(true); - spec.getDependencies().addLater(neoForgeDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-dependencies"); - }))); - // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode - spec.getDependencies().addLater(neoFormDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoform-dependencies"); - }))); - spec.attributes(attributes -> { - setNamedAttribute(attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_API); - setNamedAttribute(attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); - }); - }); - - // Runtime-time dependencies used by NeoForm, NeoForge and Minecraft. - var runtimeClasspath = configurations.create(configurationPrefix + "RuntimeClasspath", spec -> { - spec.setDescription("Dependencies needed for running NeoFormRuntime for the selected NeoForge/NeoForm version (Classpath)"); - spec.setCanBeConsumed(false); - spec.setCanBeResolved(true); - spec.getDependencies().addLater(neoForgeDependency); // Universal Jar - spec.getDependencies().addLater(neoForgeDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoforge-dependencies"); - }))); - // This dependency is used when the NeoForm version is overridden or when we run in Vanilla-only mode - spec.getDependencies().addLater(neoFormDependency.map(dependency -> dependency.copy() - .capabilities(caps -> { - caps.requireCapability("net.neoforged:neoform-dependencies"); - }))); - spec.attributes(attributes -> { - setNamedAttribute(attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); - setNamedAttribute(attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); - }); - }); - - return List.of(neoForgeClassesAndData, neoForgeSources, compileClasspath, runtimeClasspath); - } - - static void setupRuns(Project project, - Branding branding, - Provider argFileDir, - DomainObjectCollection runs, - Object runTemplatesSourceFile, - Consumer configureModulePath, - Consumer configureLegacyClasspath, - Provider assetPropertiesFile, - Provider versionCapabilities - ) { - var ideIntegration = IdeIntegration.of(project, branding); - - // Create a configuration to resolve DevLaunch without leaking it to consumers - var devLaunchConfig = project.getConfigurations().create("devLaunchConfig", spec -> { - spec.setDescription("This configuration is used to inject DevLaunch into the runtime classpaths of runs."); - spec.getDependencies().add(project.getDependencyFactory().create(RunUtils.DEV_LAUNCH_GAV)); - }); - - // Create an empty task similar to "assemble" which can be used to generate all launch scripts at once - var createLaunchScriptsTask = project.getTasks().register("createLaunchScripts", Task.class, task -> { - task.setGroup(branding.publicTaskGroup()); - task.setDescription("Creates batch files/shell scripts to launch the game from outside of Gradle (i.e. Renderdoc, NVidia Nsight, etc.)"); - }); - - Map> prepareRunTasks = new IdentityHashMap<>(); - runs.all(run -> { - var prepareRunTask = setupRunInGradle( - project, - branding, - argFileDir, - run, - runTemplatesSourceFile, - configureModulePath, - configureLegacyClasspath, - assetPropertiesFile, - devLaunchConfig, - versionCapabilities, - createLaunchScriptsTask - ); - prepareRunTasks.put(run, prepareRunTask); - }); - ideIntegration.configureRuns(prepareRunTasks, runs); - } - - /** - * @param runTemplatesFile See {@link ConfigurableFileCollection#from(Object...)}. This must ultimately resolve - * to a single file that is - * @param configureLegacyClasspath Callback to add entries to the legacy classpath. - * @param assetPropertiesFile File that contains the asset properties file produced by NFRT. - * @param createLaunchScriptsTask - */ - private static TaskProvider setupRunInGradle( - Project project, - Branding branding, - Provider argFileDir, - RunModel run, - Object runTemplatesFile, - Consumer configureModulePath, - Consumer configureLegacyClasspath, // TODO: can be removed in favor of directly passing a configuration for the moddev libraries - Provider assetPropertiesFile, - Configuration devLaunchConfig, - Provider versionCapabilities, - TaskProvider createLaunchScriptsTask) { - var ideIntegration = IdeIntegration.of(project, branding); - var configurations = project.getConfigurations(); - var javaExtension = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); - var tasks = project.getTasks(); - - var runtimeClasspathConfig = run.getSourceSet().map(SourceSet::getRuntimeClasspathConfigurationName) - .map(configurations::getByName); - - // Sucks, but what can you do... Only at the end do we actually know which source set this run will use - project.afterEvaluate(ignored -> { - runtimeClasspathConfig.get().extendsFrom(devLaunchConfig); - }); - - var type = RunUtils.getRequiredType(project, run); - - var modulePathConfiguration = project.getConfigurations().create(InternalModelHelper.nameOfRun(run, "", "modulesOnly"), spec -> { - spec.setDescription("Libraries that should be placed on the JVMs boot module path for run " + run.getName() + "."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); - configureModulePath.accept(spec); - }); - - var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { - spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); - spec.attributes(attributes -> { - attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { - var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; - return project.getObjects().named(MinecraftDistribution.class, name); - })); - setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); - }); - configureLegacyClasspath.accept(spec); - spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); - }); - - var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); - - var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> { - task.setGroup(branding.internalTaskGroup()); - task.setDescription("Prepares all files needed to launch the " + run.getName() + " Minecraft run."); - - task.getGameDirectory().set(run.getGameDirectory()); - task.getVmArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.VMARGS)); - task.getProgramArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.PROGRAMARGS)); - task.getLog4jConfigFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.LOG4J_CONFIG)); - task.getRunType().set(run.getType()); - task.getRunTypeTemplatesSource().from(runTemplatesFile); - task.getModules().from(modulePathConfiguration); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); - task.getAssetProperties().set(assetPropertiesFile); - task.getSystemProperties().set(run.getSystemProperties().map(props -> { - props = new HashMap<>(props); - return props; - })); - task.getMainClass().set(run.getMainClass()); - task.getProgramArguments().set(run.getProgramArguments()); - task.getJvmArguments().set(run.getJvmArguments()); - task.getGameLogLevel().set(run.getLogLevel()); - task.getVersionCapabilities().set(versionCapabilities); - }); - ideIntegration.runTaskOnProjectSync(prepareRunTask); - - var launchScriptTask = tasks.register(InternalModelHelper.nameOfRun(run, "create", "launchScript"), CreateLaunchScriptTask.class, task -> { - task.setGroup(branding.internalTaskGroup()); - task.setDescription("Creates a bash/shell-script to launch the " + run.getName() + " Minecraft run from outside Gradle or your IDE."); - - task.getWorkingDirectory().set(run.getGameDirectory().map(d -> d.getAsFile().getAbsolutePath())); - task.getRuntimeClasspath().setFrom(runtimeClasspathConfig); - task.getLaunchScript().set(RunUtils.getLaunchScript(argFileDir, run)); - task.getClasspathArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.CLASSPATH)); - task.getVmArgsFile().set(prepareRunTask.get().getVmArgsFile().map(d -> d.getAsFile().getAbsolutePath())); - task.getProgramArgsFile().set(prepareRunTask.get().getProgramArgsFile().map(d -> d.getAsFile().getAbsolutePath())); - task.getEnvironment().set(run.getEnvironment()); - task.getModFolders().set(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); - }); - createLaunchScriptsTask.configure(task -> task.dependsOn(launchScriptTask)); - - tasks.register(InternalModelHelper.nameOfRun(run, "run", ""), RunGameTask.class, task -> { - task.setGroup(branding.publicTaskGroup()); - task.setDescription("Runs the " + run.getName() + " Minecraft run configuration."); - - // Launch with the Java version used in the project - var toolchainService = ExtensionUtils.findExtension(project, "javaToolchains", JavaToolchainService.class); - task.getJavaLauncher().set(toolchainService.launcherFor(spec -> spec.getLanguageVersion().set(javaExtension.getToolchain().getLanguageVersion()))); - // Note: this contains both the runtimeClasspath configuration and the sourceset's outputs. - // This records a dependency on compiling and processing the resources of the source set. - task.getClasspathProvider().from(run.getSourceSet().map(SourceSet::getRuntimeClasspath)); - task.getGameDirectory().set(run.getGameDirectory()); - - task.getEnvironmentProperty().set(run.getEnvironment()); - task.jvmArgs(RunUtils.getArgFileParameter(prepareRunTask.get().getVmArgsFile().get()).replace("\\", "\\\\")); - task.getMainClass().set(RunUtils.DEV_LAUNCH_MAIN_CLASS); - task.args(RunUtils.getArgFileParameter(prepareRunTask.get().getProgramArgsFile().get()).replace("\\", "\\\\")); - // Of course we need the arg files to be up-to-date ;) - task.dependsOn(prepareRunTask); - task.dependsOn(run.getTasksBefore()); - - task.getJvmArgumentProviders().add(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); - }); - - return prepareRunTask; - } - - public void setupTestTask() { - if (configureTesting == null) { - throw new IllegalStateException("Unit testing was already enabled once!"); - } - configureTesting.run(); - configureTesting = null; - } - - /** - * @see #setupRunInGradle for a description of the parameters - */ - static void setupTestTask(Project project, - Branding branding, - Object runTemplatesSourceFile, - TaskProvider testTask, - SetProperty loadedMods, - Property testedMod, - Provider argFileDir, - Consumer configureModulePath, - Consumer configureLegacyClasspath, - Provider assetPropertiesFile - ) { - var gameDirectory = new File(project.getProjectDir(), JUNIT_GAME_DIR); - - var ideIntegration = IdeIntegration.of(project, branding); - - var tasks = project.getTasks(); - var configurations = project.getConfigurations(); - - var testRuntimeClasspath = configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); - - var neoForgeModDevModules = project.getConfigurations().create("neoForgeTestModules", spec -> { - spec.setDescription("Libraries that should be placed on the JVMs boot module path for unit tests."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(testRuntimeClasspath); - configureModulePath.accept(spec); - }); - - var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { - spec.setDescription("Contains the legacy classpath of unit tests."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(testRuntimeClasspath); - spec.attributes(attributes -> { - setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); - setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); - }); - configureLegacyClasspath.accept(spec); - }); - - // Place files for junit runtime in a subdirectory to avoid conflicting with other runs - var runArgsDir = argFileDir.map(dir -> dir.dir("junit")); - - var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); - - var vmArgsFile = runArgsDir.map(dir -> dir.file("vmArgs.txt")); - var programArgsFile = runArgsDir.map(dir -> dir.file("programArgs.txt")); - var log4j2ConfigFile = runArgsDir.map(dir -> dir.file("log4j2.xml")); - var prepareTask = tasks.register("prepareNeoForgeTestFiles", PrepareTest.class, task -> { - task.setGroup(branding.internalTaskGroup()); - task.setDescription("Prepares all files needed to run the JUnit test task."); - task.getGameDirectory().set(gameDirectory); - task.getVmArgsFile().set(vmArgsFile); - task.getProgramArgsFile().set(programArgsFile); - task.getLog4jConfigFile().set(log4j2ConfigFile); - task.getRunTypeTemplatesSource().from(runTemplatesSourceFile); - task.getModules().from(neoForgeModDevModules); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); - task.getAssetProperties().set(assetPropertiesFile); - task.getGameLogLevel().set(Level.INFO); - }); - - // Ensure the test files are written on sync so that users who use IDE-only tests can run them - ideIntegration.runTaskOnProjectSync(prepareTask); - - testTask.configure(task -> { - task.dependsOn(prepareTask); - - // The FML JUnit plugin uses this system property to read a - // file containing the program arguments needed to launch - task.systemProperty("fml.junit.argsfile", programArgsFile.get().getAsFile().getAbsolutePath()); - task.jvmArgs(RunUtils.getArgFileParameter(vmArgsFile.get())); - - var modFoldersProvider = RunUtils.getGradleModFoldersProvider(project, loadedMods, testedMod); - task.getJvmArgumentProviders().add(modFoldersProvider); - }); - - project.afterEvaluate(p -> { - // Test tasks don't have a provider-based property for working directory, so we need to afterEvaluate it. - testTask.configure(task -> task.setWorkingDir(gameDirectory)); - }); - - ideIntegration.configureTesting(loadedMods, testedMod, runArgsDir, gameDirectory, programArgsFile, vmArgsFile); - } - - private static void setupJarJar(Project project) { - SourceSetContainer sourceSets = ExtensionUtils.getExtension(project, "sourceSets", SourceSetContainer.class); - sourceSets.all(sourceSet -> { - var jarJarTask = JarJar.registerWithConfiguration(project, sourceSet.getTaskName(null, "jarJar")); - jarJarTask.configure(task -> task.setGroup(Branding.MDG.internalTaskGroup())); - - // The target jar task for this source set might not exist, and #named(String) requires the task to exist - var jarTaskName = sourceSet.getJarTaskName(); - project.getTasks().withType(AbstractArchiveTask.class).named(name -> name.equals(jarTaskName)).configureEach(task -> { - task.from(jarJarTask); - }); - }); - } - - record DataFileCollectionWrapper(DataFileCollection extension, Configuration configuration) { - } - - private static DataFileCollectionWrapper dataFileConfiguration(Project project, String name, String description, String category) { - var configuration = project.getConfigurations().create(name, spec -> { - spec.setDescription(description); - spec.setCanBeConsumed(false); - spec.setCanBeResolved(true); - spec.attributes(attributes -> setNamedAttribute(project, attributes, Category.CATEGORY_ATTRIBUTE, category)); - }); - - var elementsConfiguration = project.getConfigurations().create(name + "Elements", spec -> { - spec.setDescription("Published data files for " + name); - spec.setCanBeConsumed(true); - spec.setCanBeResolved(false); - spec.attributes(attributes -> setNamedAttribute(project, attributes, Category.CATEGORY_ATTRIBUTE, category)); - }); - - // Set up the variant publishing conditionally - var java = (AdhocComponentWithVariants) project.getComponents().getByName("java"); - java.addVariantsFromConfiguration(elementsConfiguration, variant -> { - // This should be invoked lazily, so checking if the artifacts are empty is fine: - // "The details object used to determine what to do with a configuration variant **when publishing**." - if (variant.getConfigurationVariant().getArtifacts().isEmpty()) { - variant.skip(); - } - }); - - var depFactory = project.getDependencyFactory(); - Consumer publishCallback = new Consumer<>() { - ConfigurablePublishArtifact firstArtifact; - int artifactCount; - - @Override - public void accept(Object artifactNotation) { - elementsConfiguration.getDependencies().add(depFactory.create(project.files(artifactNotation))); - project.getArtifacts().add(elementsConfiguration.getName(), artifactNotation, artifact -> { - if (firstArtifact == null) { - firstArtifact = artifact; - artifact.setClassifier(category); - artifactCount = 1; - } else { - if (artifactCount == 1) { - firstArtifact.setClassifier(category + artifactCount); - } - artifact.setClassifier(category + (++artifactCount)); - } - }); - } - }; - - var extension = project.getObjects().newInstance(DataFileCollection.class, publishCallback); - configuration.getDependencies().add(depFactory.create(extension.getFiles())); - - return new DataFileCollectionWrapper(extension, configuration); - } - - private void setNamedAttribute(AttributeContainer attributes, Attribute attribute, String value) { - attributes.attribute(attribute, objectFactory.named(attribute.getType(), value)); - } - - private static void setNamedAttribute(Project project, AttributeContainer attributes, Attribute attribute, String value) { - attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); - } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java new file mode 100644 index 00000000..c27f88b1 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -0,0 +1,490 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.minecraftdependencies.MinecraftDistribution; +import net.neoforged.moddevgradle.dsl.InternalModelHelper; +import net.neoforged.moddevgradle.dsl.ModModel; +import net.neoforged.moddevgradle.dsl.RunModel; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; +import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; +import net.neoforged.nfrtgradle.DownloadAssets; +import org.gradle.api.DomainObjectCollection; +import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.Named; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Usage; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.plugins.jvm.JvmTestSuite; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.testing.base.TestingExtension; +import org.jetbrains.annotations.Nullable; +import org.slf4j.event.Level; + +import java.io.File; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * After modding has been enabled, this will be attached as an extension to the project. + */ +public class ModDevRunWorkflow { + private static final String EXTENSION_NAME = "__internal_modDevRunWorkflow"; + + /** + * This must be relative to the project directory since we can only set this to the same project-relative + * directory across all subprojects due to IntelliJ limitations. + */ + static final String JUNIT_GAME_DIR = "build/minecraft-junit"; + + private final Project project; + private final Branding branding; + @Nullable + private final ModuleDependency modulePathDependency; + @Nullable + private final ModuleDependency testFixturesDependency; + private final ModuleDependency gameLibrariesDependency; + private final Configuration additionalClasspath; + private final @Nullable Configuration userDevConfigOnly; + + /** + * @param gameLibrariesDependency A module dependency that represents the library dependencies of the game. + * This module can be depended on with different usage attributes, which allow it + * to expose different sets of libraries for use in compiling code or at runtime + * (apiElements vs. runtimeElements). + */ + private ModDevRunWorkflow(Project project, + Branding branding, + ModDevArtifactsWorkflow artifactsWorkflow, + @Nullable ModuleDependency modulePathDependency, + @Nullable ModuleDependency runTypesConfigDependency, + @Nullable ModuleDependency testFixturesDependency, + ModuleDependency gameLibrariesDependency, + DomainObjectCollection runs, + VersionCapabilities versionCapabilities) { + this.project = project; + this.branding = branding; + this.modulePathDependency = modulePathDependency; + this.testFixturesDependency = testFixturesDependency; + this.gameLibrariesDependency = gameLibrariesDependency; + + var configurations = project.getConfigurations(); + + // Let's try to get the userdev JSON out of the universal jar + // I don't like having to use a configuration for this... + if (runTypesConfigDependency != null) { + userDevConfigOnly = configurations.create("neoForgeConfigOnly", spec -> { + spec.setDescription("Resolves exclusively the NeoForge userdev JSON for configuring runs"); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.setTransitive(false); + spec.getDependencies().add(runTypesConfigDependency); + }); + } else { + // Create an implicit configuration + + userDevConfigOnly = null; + } + + additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { + spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + + spec.getDependencies().add(gameLibrariesDependency); + addClientResources(project, spec, artifactsWorkflow.createArtifacts()); + }); + + setupRuns( + project, + branding, + artifactsWorkflow.modDevBuildDir(), + runs, + userDevConfigOnly, + modulePath -> { + if (modulePathDependency != null) { + modulePath.getDependencies().add(modulePathDependency); + } + }, + legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), + artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile), + project.provider(() -> versionCapabilities) + ); + } + + public static ModDevRunWorkflow get(Project project) { + var workflow = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevRunWorkflow.class); + if (workflow == null) { + throw new InvalidUserCodeException("Please enable the modding plugin first by setting a version"); + } + return workflow; + } + + public static ModDevRunWorkflow create(Project project, + Branding branding, + ModDevArtifactsWorkflow artifactsWorkflow, + @Nullable ModuleDependency modulePathDependency, + @Nullable ModuleDependency runTypesConfigDependency, + @Nullable ModuleDependency testFixturesDependency, + ModuleDependency gameLibrariesDependency, + DomainObjectCollection runs, + VersionCapabilities versionCapabilites) { + var workflow = new ModDevRunWorkflow( + project, + branding, + artifactsWorkflow, + modulePathDependency, + runTypesConfigDependency, + testFixturesDependency, + gameLibrariesDependency, + runs, + versionCapabilites + ); + + project.getExtensions().add(EXTENSION_NAME, workflow); + + return workflow; + } + + public void configureTesting(Provider testedMod, Provider> loadedMods) { + var testing = project.getExtensions().getByType(TestingExtension.class); + var testSuite = (JvmTestSuite) testing.getSuites().getByName("test"); + var testSourceSet = testSuite.getSources(); + + var artifactsWorkflow = ModDevArtifactsWorkflow.get(project); + artifactsWorkflow.addToSourceSet(testSourceSet.getName()); + + var configurations = project.getConfigurations(); + + // If test fixtures are available for the current workflow, add them to runtime only + if (testFixturesDependency != null) { + configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName(), configuration -> { + configuration.getDependencies().add(testFixturesDependency); + }); + } + + for (var target : testSuite.getTargets()) { + setupTestTask( + project, + branding, + userDevConfigOnly, + target.getTestTask(), + loadedMods, + testedMod, + artifactsWorkflow.modDevBuildDir(), + modulePath -> { + if (modulePathDependency != null) { + modulePath.getDependencies().add(modulePathDependency); + } + }, + legacyClassPath -> { + legacyClassPath.getDependencies().add(gameLibrariesDependency); + addClientResources(project, legacyClassPath, artifactsWorkflow.createArtifacts()); + }, + artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile) + ); + } + } + + // FML searches for client resources on the legacy classpath + private void addClientResources(Project project, Configuration spec, TaskProvider createArtifacts) { + // FML searches for client resources on the legacy classpath + spec.getDependencies().add( + project.getDependencyFactory().create( + project.files(createArtifacts.flatMap(CreateMinecraftArtifacts::getResourcesArtifact)) + ) + ); + } + + public static void setupRuns( + Project project, + Branding branding, + Provider argFileDir, + DomainObjectCollection runs, + Object runTemplatesSourceFile, + Consumer configureModulePath, + Consumer configureLegacyClasspath, + Provider assetPropertiesFile, + Provider versionCapabilities + ) { + var dependencyFactory = project.getDependencyFactory(); + var ideIntegration = IdeIntegration.of(project, branding); + + // Create a configuration to resolve DevLaunch without leaking it to consumers + var devLaunchConfig = project.getConfigurations().create("devLaunchConfig", spec -> { + spec.setDescription("This configuration is used to inject DevLaunch into the runtime classpaths of runs."); + spec.getDependencies().add(dependencyFactory.create(RunUtils.DEV_LAUNCH_GAV)); + }); + + // Create an empty task similar to "assemble" which can be used to generate all launch scripts at once + var createLaunchScriptsTask = project.getTasks().register("createLaunchScripts", Task.class, task -> { + task.setGroup(branding.publicTaskGroup()); + task.setDescription("Creates batch files/shell scripts to launch the game from outside of Gradle (i.e. Renderdoc, NVidia Nsight, etc.)"); + }); + + Map> prepareRunTasks = new IdentityHashMap<>(); + runs.all(run -> { + var prepareRunTask = setupRunInGradle( + project, + branding, + argFileDir, + run, + runTemplatesSourceFile, + configureModulePath, + configureLegacyClasspath, + assetPropertiesFile, + devLaunchConfig, + versionCapabilities, + createLaunchScriptsTask + ); + prepareRunTasks.put(run, prepareRunTask); + }); + ideIntegration.configureRuns(prepareRunTasks, runs); + } + + /** + * @param runTemplatesFile See {@link ConfigurableFileCollection#from(Object...)}. This must ultimately resolve + * to a single file that is + * @param configureLegacyClasspath Callback to add entries to the legacy classpath. + * @param assetPropertiesFile File that contains the asset properties file produced by NFRT. + */ + private static TaskProvider setupRunInGradle( + Project project, + Branding branding, + Provider argFileDir, + RunModel run, + Object runTemplatesFile, + Consumer configureModulePath, + Consumer configureLegacyClasspath, // TODO: can be removed in favor of directly passing a configuration for the moddev libraries + Provider assetPropertiesFile, + Configuration devLaunchConfig, + Provider versionCapabilities, + TaskProvider createLaunchScriptsTask) { + var ideIntegration = IdeIntegration.of(project, branding); + var configurations = project.getConfigurations(); + var javaExtension = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); + var tasks = project.getTasks(); + + var runtimeClasspathConfig = run.getSourceSet().map(SourceSet::getRuntimeClasspathConfigurationName) + .map(configurations::getByName); + + // Sucks, but what can you do... Only at the end do we actually know which source set this run will use + project.afterEvaluate(ignored -> { + runtimeClasspathConfig.get().extendsFrom(devLaunchConfig); + }); + + var type = RunUtils.getRequiredType(project, run); + + var modulePathConfiguration = project.getConfigurations().create(InternalModelHelper.nameOfRun(run, "", "modulesOnly"), spec -> { + spec.setDescription("Libraries that should be placed on the JVMs boot module path for run " + run.getName() + "."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); + configureModulePath.accept(spec); + }); + + var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { + spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); + spec.attributes(attributes -> { + attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { + var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; + return project.getObjects().named(MinecraftDistribution.class, name); + })); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + configureLegacyClasspath.accept(spec); + spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); + }); + + var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + + var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> { + task.setGroup(branding.internalTaskGroup()); + task.setDescription("Prepares all files needed to launch the " + run.getName() + " Minecraft run."); + + task.getGameDirectory().set(run.getGameDirectory()); + task.getVmArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.VMARGS)); + task.getProgramArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.PROGRAMARGS)); + task.getLog4jConfigFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.LOG4J_CONFIG)); + task.getRunType().set(run.getType()); + task.getRunTypeTemplatesSource().from(runTemplatesFile); + task.getModules().from(modulePathConfiguration); + task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + task.getAssetProperties().set(assetPropertiesFile); + task.getSystemProperties().set(run.getSystemProperties().map(props -> { + props = new HashMap<>(props); + return props; + })); + task.getMainClass().set(run.getMainClass()); + task.getProgramArguments().set(run.getProgramArguments()); + task.getJvmArguments().set(run.getJvmArguments()); + task.getGameLogLevel().set(run.getLogLevel()); + task.getVersionCapabilities().set(versionCapabilities); + }); + ideIntegration.runTaskOnProjectSync(prepareRunTask); + + var launchScriptTask = tasks.register(InternalModelHelper.nameOfRun(run, "create", "launchScript"), CreateLaunchScriptTask.class, task -> { + task.setGroup(branding.internalTaskGroup()); + task.setDescription("Creates a bash/shell-script to launch the " + run.getName() + " Minecraft run from outside Gradle or your IDE."); + + task.getWorkingDirectory().set(run.getGameDirectory().map(d -> d.getAsFile().getAbsolutePath())); + task.getRuntimeClasspath().setFrom(runtimeClasspathConfig); + task.getLaunchScript().set(RunUtils.getLaunchScript(argFileDir, run)); + task.getClasspathArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.CLASSPATH)); + task.getVmArgsFile().set(prepareRunTask.get().getVmArgsFile().map(d -> d.getAsFile().getAbsolutePath())); + task.getProgramArgsFile().set(prepareRunTask.get().getProgramArgsFile().map(d -> d.getAsFile().getAbsolutePath())); + task.getEnvironment().set(run.getEnvironment()); + task.getModFolders().set(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); + }); + createLaunchScriptsTask.configure(task -> task.dependsOn(launchScriptTask)); + + tasks.register(InternalModelHelper.nameOfRun(run, "run", ""), RunGameTask.class, task -> { + task.setGroup(branding.publicTaskGroup()); + task.setDescription("Runs the " + run.getName() + " Minecraft run configuration."); + + // Launch with the Java version used in the project + var toolchainService = ExtensionUtils.findExtension(project, "javaToolchains", JavaToolchainService.class); + task.getJavaLauncher().set(toolchainService.launcherFor(spec -> spec.getLanguageVersion().set(javaExtension.getToolchain().getLanguageVersion()))); + // Note: this contains both the runtimeClasspath configuration and the sourceset's outputs. + // This records a dependency on compiling and processing the resources of the source set. + task.getClasspathProvider().from(run.getSourceSet().map(SourceSet::getRuntimeClasspath)); + task.getGameDirectory().set(run.getGameDirectory()); + + task.getEnvironmentProperty().set(run.getEnvironment()); + task.jvmArgs(RunUtils.getArgFileParameter(prepareRunTask.get().getVmArgsFile().get()).replace("\\", "\\\\")); + task.getMainClass().set(RunUtils.DEV_LAUNCH_MAIN_CLASS); + task.args(RunUtils.getArgFileParameter(prepareRunTask.get().getProgramArgsFile().get()).replace("\\", "\\\\")); + // Of course we need the arg files to be up-to-date ;) + task.dependsOn(prepareRunTask); + task.dependsOn(run.getTasksBefore()); + + task.getJvmArgumentProviders().add(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); + }); + + return prepareRunTask; + } + + /** + * @see #setupRunInGradle for a description of the parameters + */ + static void setupTestTask(Project project, + Branding branding, + Object runTemplatesSourceFile, + TaskProvider testTask, + Provider> loadedMods, + Provider testedMod, + Provider argFileDir, + Consumer configureModulePath, + Consumer configureLegacyClasspath, + Provider assetPropertiesFile + ) { + var gameDirectory = new File(project.getProjectDir(), JUNIT_GAME_DIR); + + var ideIntegration = IdeIntegration.of(project, branding); + + var tasks = project.getTasks(); + var configurations = project.getConfigurations(); + + var testRuntimeClasspath = configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); + + var neoForgeModDevModules = project.getConfigurations().create("neoForgeTestModules", spec -> { + spec.setDescription("Libraries that should be placed on the JVMs boot module path for unit tests."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(testRuntimeClasspath); + configureModulePath.accept(spec); + }); + + var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { + spec.setDescription("Contains the legacy classpath of unit tests."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(testRuntimeClasspath); + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + configureLegacyClasspath.accept(spec); + }); + + // Place files for junit runtime in a subdirectory to avoid conflicting with other runs + var runArgsDir = argFileDir.map(dir -> dir.dir("junit")); + + var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + + var vmArgsFile = runArgsDir.map(dir -> dir.file("vmArgs.txt")); + var programArgsFile = runArgsDir.map(dir -> dir.file("programArgs.txt")); + var log4j2ConfigFile = runArgsDir.map(dir -> dir.file("log4j2.xml")); + var prepareTask = tasks.register("prepareNeoForgeTestFiles", PrepareTest.class, task -> { + task.setGroup(branding.internalTaskGroup()); + task.setDescription("Prepares all files needed to run the JUnit test task."); + task.getGameDirectory().set(gameDirectory); + task.getVmArgsFile().set(vmArgsFile); + task.getProgramArgsFile().set(programArgsFile); + task.getLog4jConfigFile().set(log4j2ConfigFile); + task.getRunTypeTemplatesSource().from(runTemplatesSourceFile); + task.getModules().from(neoForgeModDevModules); + task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + task.getAssetProperties().set(assetPropertiesFile); + task.getGameLogLevel().set(Level.INFO); + }); + + // Ensure the test files are written on sync so that users who use IDE-only tests can run them + ideIntegration.runTaskOnProjectSync(prepareTask); + + testTask.configure(task -> { + task.dependsOn(prepareTask); + + // The FML JUnit plugin uses this system property to read a + // file containing the program arguments needed to launch + task.systemProperty("fml.junit.argsfile", programArgsFile.get().getAsFile().getAbsolutePath()); + task.jvmArgs(RunUtils.getArgFileParameter(vmArgsFile.get())); + + var modFoldersProvider = RunUtils.getGradleModFoldersProvider(project, loadedMods, testedMod); + task.getJvmArgumentProviders().add(modFoldersProvider); + }); + + project.afterEvaluate(p -> { + // Test tasks don't have a provider-based property for working directory, so we need to afterEvaluate it. + testTask.configure(task -> task.setWorkingDir(gameDirectory)); + }); + + ideIntegration.configureTesting(loadedMods, testedMod, runArgsDir, gameDirectory, programArgsFile, vmArgsFile); + } + + public Configuration getAdditionalClasspath() { + return additionalClasspath; + } + + private static void setNamedAttribute(Project project, AttributeContainer attributes, Attribute attribute, String value) { + attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index 68c61f70..f3402caf 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -8,12 +8,11 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; -import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; +import java.util.Set; import java.util.function.Consumer; /** @@ -35,7 +34,7 @@ public static void setupRuns(Project project, Consumer configureAdditionalClasspath, Provider assetPropertiesFile ) { - ModDevPlugin.setupRuns( + ModDevRunWorkflow.setupRuns( project, Branding.NEODEV, argFileDir, @@ -44,8 +43,7 @@ public static void setupRuns(Project project, configureModulePath, configureAdditionalClasspath, assetPropertiesFile, - // This overload of the method was only used by NeoForge 1.21.3 - project.provider(() -> VersionCapabilities.ofMinecraftVersion("1.21.3")) + project.getObjects().property(VersionCapabilities.class) // empty provider ); } @@ -58,7 +56,7 @@ public static void setupRuns(Project project, Provider assetPropertiesFile, Provider neoFormVersion ) { - ModDevPlugin.setupRuns( + ModDevRunWorkflow.setupRuns( project, Branding.NEODEV, argFileDir, @@ -75,13 +73,13 @@ public static void setupTestTask(Project project, Provider argFileDir, TaskProvider testTask, Object runTemplatesSourceFile, - SetProperty loadedMods, - Property testedMod, + Provider> loadedMods, + Provider testedMod, Consumer configureModulePath, Consumer configureAdditionalClasspath, Provider assetPropertiesFile ) { - ModDevPlugin.setupTestTask( + ModDevRunWorkflow.setupTestTask( project, Branding.NEODEV, runTemplatesSourceFile, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index e50297ad..189cbe78 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java @@ -97,7 +97,8 @@ abstract class PrepareRunOrTest extends DefaultTask { /** * Only used when {@link #getRunTypeTemplatesSource()} is empty, - * to know whether the associated Minecraft version requires one or two data runs. + * to know whether the associated Minecraft version has separate entrypoints for generating resource- and + * data packs. * Defaults to latest. */ @Input diff --git a/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java new file mode 100644 index 00000000..ac7b7add --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java @@ -0,0 +1,16 @@ +package net.neoforged.moddevgradle.internal; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public enum WorkflowArtifact { + COMPILED(""), + COMPILED_WITH_SOURCES("-merged"), + SOURCES("-sources"), + CLIENT_RESOURCES("-client-extra"); + public final String defaultSuffix; + + WorkflowArtifact(String defaultSuffix) { + this.defaultSuffix = defaultSuffix; + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/jarjar/ResolvedJarJarArtifact.java b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/ResolvedJarJarArtifact.java index 7a774e92..3539a4ea 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/jarjar/ResolvedJarJarArtifact.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/ResolvedJarJarArtifact.java @@ -50,7 +50,7 @@ public ContainedVersion createContainedVersion() { } public ContainedJarMetadata createContainerMetadata() { - return new ContainedJarMetadata(createContainedJarIdentifier(), createContainedVersion(), "META-INF/jarjar/"+embeddedFilename, isObfuscated(file)); + return new ContainedJarMetadata(createContainedJarIdentifier(), createContainedVersion(), "META-INF/jarjar/" + embeddedFilename, isObfuscated(file)); } @InputFile @@ -85,7 +85,7 @@ public String getArtifact() { } private static boolean isObfuscated(final File dependency) { - try(final JarFile jarFile = new JarFile(dependency)) { + try (final JarFile jarFile = new JarFile(dependency)) { final Manifest manifest = jarFile.getManifest(); return manifest.getMainAttributes().containsKey("Obfuscated-By"); } catch (IOException e) { diff --git a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java index 4218ae6f..f7a855ae 100644 --- a/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java +++ b/src/main/java/net/neoforged/moddevgradle/tasks/JarJar.java @@ -94,7 +94,7 @@ public static TaskProvider registerWithConfiguration(Project project, St // jvm version. We could copy DefaultJvmFeature, and search for the target version of the compile task, // but this is difficult - we only have a feature name, not the linked source set. For this reason, we use // the toolchain version, which is the most likely to be correct. - attributes.attributeProvider(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, javaPlugin.getToolchain().getLanguageVersion().map(JavaLanguageVersion::asInt)); + attributes.attributeProvider(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, javaPlugin.getToolchain().getLanguageVersion().orElse(JavaLanguageVersion.current()).map(JavaLanguageVersion::asInt)); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); diff --git a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java index ed465533..4c4b981c 100644 --- a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java +++ b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java @@ -279,5 +279,6 @@ public void createArtifacts() { run(args); } - record RequestedResult(String id, File destination) {} + record RequestedResult(String id, File destination) { + } } diff --git a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java index 6dbff66b..bfa43800 100644 --- a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java +++ b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java @@ -65,7 +65,7 @@ public abstract class NeoFormRuntimeTask extends DefaultTask { */ @Input @ApiStatus.Internal - protected abstract Property getJavaExecutable(); + public abstract Property getJavaExecutable(); @Inject @ApiStatus.Internal diff --git a/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java b/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java index 95e6bf72..76b511dc 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java @@ -147,7 +147,9 @@ private void publishDataFiles(String groupId, group = "{0}" version = "{1}" neoForge { - version = "{DEFAULT_NEOFORGE_VERSION}" + enableModding { + neoForgeVersion = "{DEFAULT_NEOFORGE_VERSION}" + } } publishing { publications { diff --git a/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java b/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java index a0702b91..387838d2 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java @@ -28,7 +28,7 @@ public void testApplyInEmptyProject() throws IOException { .withArguments("tasks", "--all") .build(); - assertThat(result.getOutput()).contains("createMinecraftArtifacts"); + assertThat(result.getOutput()).doesNotContain("createMinecraftArtifacts"); assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); } diff --git a/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java b/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java index 1b2cafa1..e04fa8cc 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java @@ -29,7 +29,7 @@ public void testApplyInEmptyProject() throws IOException { .withArguments("tasks", "--all") .build(); - assertThat(result.getOutput()).contains("createMinecraftArtifacts"); + assertThat(result.getOutput()).doesNotContain("createMinecraftArtifacts"); assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); } } diff --git a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java index 3a0c3e7c..adc9f5a0 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java @@ -24,6 +24,8 @@ void setup() { project.getPlugins().apply(ModDevPlugin.class); extension = ExtensionUtils.getExtension(project, "neoForge", NeoForgeExtension.class); + + extension.enableModding(settings -> settings.setNeoForgeVersion("1.2.3")); } @Test diff --git a/testproject/build.gradle b/testproject/build.gradle index 44cdcbe5..6d7a1b1b 100644 --- a/testproject/build.gradle +++ b/testproject/build.gradle @@ -26,8 +26,10 @@ test { } neoForge { - version = project.neoforge_version - addModdingDependenciesTo(sourceSets.api) + enableModding { + neoForgeVersion = project.neoforge_version + enabledSourceSets = [sourceSets.main, sourceSets.api] + } interfaceInjectionData = files("interfaces.json") diff --git a/testproject/common/build.gradle b/testproject/common/build.gradle index 2fc6f383..d88fc19c 100644 --- a/testproject/common/build.gradle +++ b/testproject/common/build.gradle @@ -7,7 +7,9 @@ plugins { } neoForge { - neoFormVersion = "1.21-20240613.152323" + enableModding { + neoFormVersion = "1.21-20240613.152323" + } accessTransformers { from(project.file('accesstransformer.cfg')) diff --git a/testproject/jijtest/build.gradle b/testproject/jijtest/build.gradle index 028146e3..fae3fbcf 100644 --- a/testproject/jijtest/build.gradle +++ b/testproject/jijtest/build.gradle @@ -40,7 +40,9 @@ test { } neoForge { - version = project.neoforge_version + enableModding { + neoForgeVersion = project.neoforge_version + } runs { data { diff --git a/testproject/subproject/build.gradle b/testproject/subproject/build.gradle index 5a178677..827b4a64 100644 --- a/testproject/subproject/build.gradle +++ b/testproject/subproject/build.gradle @@ -5,7 +5,9 @@ plugins { } neoForge { - version = project.neoforge_version + enableModding { + neoForgeVersion = project.neoforge_version + } interfaceInjectionData { from(project.file('interfaces.json')) From 001f2af3ef6aec1c5b22b9bb5de7063e38eb516c Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 22 Dec 2024 20:09:02 +0100 Subject: [PATCH 02/25] Configure imperatively --- .../legacyforge/dsl/LegacyForgeExtension.java | 10 +------ .../legacyforge/dsl/MixinExtension.java | 1 + ...scation.java => ObfuscationExtension.java} | 23 ++++++++++----- .../internal/LegacyForgeModDevPlugin.java | 29 +++++++++++-------- .../internal/ModDevArtifactsWorkflow.java | 2 -- .../legacyforge/dsl/MixinMappingTest.java | 10 +++++-- 6 files changed, 41 insertions(+), 34 deletions(-) rename src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/{Obfuscation.java => ObfuscationExtension.java} (90%) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index 97287d96..fdffbc23 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -17,16 +17,12 @@ public abstract class LegacyForgeExtension extends ModDevExtension { private final Project project; - private final Obfuscation obfuscation; - @Inject public LegacyForgeExtension(Project project, DataFileCollection accessTransformers, - DataFileCollection interfaceInjectionData, - Obfuscation obfuscation) { + DataFileCollection interfaceInjectionData) { super(project, accessTransformers, interfaceInjectionData); this.project = project; - this.obfuscation = obfuscation; } public void setVersion(String version) { @@ -48,8 +44,4 @@ public void enableModding(Action customizer) { plugin.enableModding(project, settings, this); } - - public Obfuscation getObfuscation() { - return obfuscation; - } } diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java index cf703f02..d74ebe6c 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java @@ -8,6 +8,7 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.jvm.tasks.Jar; +import org.jetbrains.annotations.ApiStatus; import javax.inject.Inject; diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java similarity index 90% rename from src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java rename to src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java index f68a7c90..6b168149 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/Obfuscation.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java @@ -5,6 +5,7 @@ import net.neoforged.moddevgradle.legacyforge.tasks.RemapOperation; import org.apache.commons.lang3.StringUtils; import org.gradle.api.Action; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ExternalModuleDependency; @@ -14,6 +15,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.AbstractArchiveTask; @@ -22,23 +24,29 @@ import javax.inject.Inject; import java.util.List; -public abstract class Obfuscation { +public abstract class ObfuscationExtension { private final Project project; private final Configuration autoRenamingToolRuntime; private final Configuration installerToolsRuntime; private final FileCollection extraMixinMappings; @Inject - public Obfuscation(Project project, - Configuration autoRenamingToolRuntime, - Configuration installerToolsRuntime, - FileCollection extraMixinMappings) { + public ObfuscationExtension(Project project, + Configuration autoRenamingToolRuntime, + Configuration installerToolsRuntime, + FileCollection extraMixinMappings) { this.project = project; this.autoRenamingToolRuntime = autoRenamingToolRuntime; this.installerToolsRuntime = installerToolsRuntime; this.extraMixinMappings = extraMixinMappings; } + private Provider assertConfigured(Provider provider) { + return provider.orElse(project.provider(() -> { + throw new InvalidUserCodeException("Please enable modding by setting legacyForge.version or calling legacyForge.enableModding()"); + })); + } + /** * Format is TSRG */ @@ -55,14 +63,14 @@ public Obfuscation(Project project, public void configureNamedToSrgOperation(RemapOperation operation) { operation.getToolType().set(RemapOperation.ToolType.ART); operation.getToolClasspath().from(autoRenamingToolRuntime); - operation.getMappings().from(getNamedToSrgMappings().get()); + operation.getMappings().from(assertConfigured(getNamedToSrgMappings())); } @ApiStatus.Internal public void configureSrgToNamedOperation(RemapOperation operation) { operation.getToolType().set(RemapOperation.ToolType.INSTALLER_TOOLS); operation.getToolClasspath().from(installerToolsRuntime); - operation.getMappings().from(getSrgToNamedMappings().get()); + operation.getMappings().from(assertConfigured(getSrgToNamedMappings())); } /** @@ -88,7 +96,6 @@ public TaskProvider reobfuscate(TaskProvider reobfuscate(TaskProvider jar, SourceSet sourceSet, Action configuration) { - var reobf = project.getTasks().register("reobf" + StringUtils.capitalize(jar.getName()), RemapJar.class, task -> { task.getInput().set(jar.flatMap(AbstractArchiveTask::getArchiveFile)); task.getDestinationDirectory().convention(task.getProject().getLayout().getBuildDirectory().dir("libs")); diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index a9b06732..e37da722 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -10,11 +10,12 @@ import net.neoforged.moddevgradle.internal.ModDevRunWorkflow; import net.neoforged.moddevgradle.internal.RepositoriesPlugin; import net.neoforged.moddevgradle.internal.WorkflowArtifact; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeExtension; import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeModdingSettings; import net.neoforged.moddevgradle.legacyforge.dsl.MixinExtension; -import net.neoforged.moddevgradle.legacyforge.dsl.Obfuscation; +import net.neoforged.moddevgradle.legacyforge.dsl.ObfuscationExtension; import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Plugin; @@ -39,6 +40,10 @@ public class LegacyForgeModDevPlugin implements Plugin { private static final Logger LOG = LoggerFactory.getLogger(LegacyForgeModDevPlugin.class); + public static final String MIXIN_EXTENSION = "mixin"; + public static final String OBFUSCATION_EXTENSION = "obfuscation"; + public static final String LEGACYFORGE_EXTENSION = "legacyForge"; + public static final Attribute REMAPPED = Attribute.of("net.neoforged.moddevgradle.legacy.remapped", Boolean.class); public static final String CONFIGURATION_TOOL_ART = "autoRenamingToolRuntime"; @@ -89,17 +94,20 @@ public void apply(Project project) { spec.getDependencies().add(depFactory.create("net.neoforged.installertools:installertools:2.1.10:fatjar")); }); - var obf = project.getObjects().newInstance(Obfuscation.class, project, autoRenamingToolRuntime, installerToolsRuntime); + // This collection is used to share the files added by mixin with the obfuscation extension + var extraMixinMappings = project.files(); + var obf = project.getExtensions().create(OBFUSCATION_EXTENSION, ObfuscationExtension.class, project, autoRenamingToolRuntime, installerToolsRuntime, extraMixinMappings); + project.getExtensions().create(MIXIN_EXTENSION, MixinExtension.class, project, obf.getNamedToSrgMappings(), extraMixinMappings); + configureDependencyRemapping(project, obf); var dataFileCollections = DataFileCollectionFactory.createDefault(project); project.getExtensions().create( - "legacyForge", + LEGACYFORGE_EXTENSION, LegacyForgeExtension.class, project, dataFileCollections.accessTransformers().extension(), - dataFileCollections.interfaceInjectionData().extension(), - obf + dataFileCollections.interfaceInjectionData().extension() ); } @@ -195,7 +203,9 @@ public void enableModding(Project project, LegacyForgeModdingSettings settings, artifacts.addToSourceSet(sourceSet.getName()); } - var obf = extension.getObfuscation(); + // Configure the mixin and obfuscation extensions + var mixin = ExtensionUtils.getExtension(project, MIXIN_EXTENSION, MixinExtension.class); + var obf = ExtensionUtils.getExtension(project, OBFUSCATION_EXTENSION, ObfuscationExtension.class); // We use this directory to store intermediate files used during moddev var namedToIntermediate = artifacts.requestAdditionalMinecraftArtifact("namedToIntermediaryMapping", "namedToIntermediate.tsrg"); @@ -204,11 +214,6 @@ public void enableModding(Project project, LegacyForgeModdingSettings settings, obf.getSrgToNamedMappings().set(intermediateToNamed); var mappingsCsv = artifacts.requestAdditionalMinecraftArtifact("csvMapping", "intermediateToNamed.zip"); - // This collection is used to share the files added by mixin with the obfuscation extension - var extraMixinMappings = project.files(); - - var mixin = project.getExtensions().create("mixin", MixinExtension.class, project, namedToIntermediate, extraMixinMappings); - extension.getRuns().configureEach(run -> { LegacyForgeFacade.configureRun(project, run); @@ -259,7 +264,7 @@ public void enableModding(Project project, LegacyForgeModdingSettings settings, }); } - private static void configureDependencyRemapping(Project project, Obfuscation obf) { + private static void configureDependencyRemapping(Project project, ObfuscationExtension obf) { project.getDependencies().attributesSchema(schema -> schema.attribute(REMAPPED)); project.getDependencies().getArtifactTypes().named("jar", a -> a.getAttributes().attribute(REMAPPED, false)); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 23170a81..91839bae 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -165,7 +165,6 @@ public static ModDevArtifactsWorkflow create(Project project, var runtimeDependencies = configurations.create("modDevRuntimeDependencies", config -> { config.setDescription("The runtime dependencies to develop a mod for, including Minecraft classes and modding platform classes."); config.setCanBeResolved(false); - config.setCanBeDeclared(false); config.setCanBeConsumed(false); config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); @@ -180,7 +179,6 @@ public static ModDevArtifactsWorkflow create(Project project, var compileDependencies = configurations.create("modDevCompileDependencies", config -> { config.setDescription("The compile-time dependencies to develop a mod, including Minecraft and modding platform classes."); config.setCanBeResolved(false); - config.setCanBeDeclared(false); config.setCanBeConsumed(false); config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); config.getDependencies().add(gameLibrariesDependency); diff --git a/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java b/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java index b16b163c..f1403e49 100644 --- a/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java +++ b/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java @@ -19,20 +19,24 @@ public void testMixinMappingsArePropagatedToObfuscationTasks() { var project = ProjectBuilder.builder().build(); project.getPlugins().apply(LegacyForgeModDevPlugin.class); - var obfuscation = ExtensionUtils.getExtension(project, "obfuscation", Obfuscation.class); + var obfuscation = ExtensionUtils.getExtension(project, LegacyForgeModDevPlugin.OBFUSCATION_EXTENSION, ObfuscationExtension.class); var sourceSets = ExtensionUtils.getSourceSets(project); - var mixinExtension = ExtensionUtils.getExtension(project, "mixin", MixinExtension.class); + var mixinExtension = ExtensionUtils.getExtension(project, LegacyForgeModDevPlugin.MIXIN_EXTENSION, MixinExtension.class); var mainSourceSet = sourceSets.getByName("main"); mixinExtension.add(mainSourceSet, "testmod.refmap.json"); var someJarTask = project.getTasks().register("someJar", Jar.class); var customRemapJarTask = obfuscation.reobfuscate(someJarTask, mainSourceSet).get(); - var remapJarTask = (RemapJar) project.getTasks().getByName("reobfJar"); + + var remapJarTask = (RemapJar) project.getTasks().getByName("reobfSomeJar"); // The main named->intermediary mappings for the game var namedToIntermediary = project.getLayout().getBuildDirectory().file("moddev/namedToIntermediate.tsrg").get().getAsFile(); var mixinApMappings = project.getLayout().getBuildDirectory().file("mixin/testmod.refmap.json.mappings.tsrg").get().getAsFile(); + // Enable modding to actually wire up the tasks + ExtensionUtils.getExtension(project, LegacyForgeModDevPlugin.LEGACYFORGE_EXTENSION, LegacyForgeExtension.class).setVersion("1.20.1-47.11"); + // The mapping file produced by the Mixin AP should be added as an input to both Jar tasks. var otherMappings = customRemapJarTask.getRemapOperation().getMappings().getFiles(); assertThat(otherMappings).containsOnly(namedToIntermediary, mixinApMappings); From 638863350b4cca9f0d326bcfe7d3c8e6b070706c Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 22 Dec 2024 20:16:11 +0100 Subject: [PATCH 03/25] Fix unit test enabling. --- .../net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index c27f88b1..4a94a92c 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -173,7 +173,7 @@ public void configureTesting(Provider testedMod, Provider { + configurations.getByName(testSourceSet.getRuntimeOnlyConfigurationName(), configuration -> { configuration.getDependencies().add(testFixturesDependency); }); } From 14860a9f4bcdbc99e37965cdb3551d37a1880ff6 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 22 Dec 2024 21:09:36 +0100 Subject: [PATCH 04/25] Fix wrong mappings used for artifact remapping. --- .../moddevgradle/legacyforge/dsl/ObfuscationExtension.java | 2 +- .../legacyforge/internal/LegacyForgeModDevPlugin.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java index 6b168149..28b9ce33 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java @@ -54,7 +54,7 @@ private Provider assertConfigured(Provider provider) { public abstract RegularFileProperty getNamedToSrgMappings(); /** - * Format is "mappings.csv" + * Format is a ZIP file containing CSV files with mapping data. */ @ApiStatus.Internal public abstract RegularFileProperty getSrgToNamedMappings(); diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index e37da722..3828e4d3 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -211,8 +211,8 @@ public void enableModding(Project project, LegacyForgeModdingSettings settings, var namedToIntermediate = artifacts.requestAdditionalMinecraftArtifact("namedToIntermediaryMapping", "namedToIntermediate.tsrg"); obf.getNamedToSrgMappings().set(namedToIntermediate); var intermediateToNamed = artifacts.requestAdditionalMinecraftArtifact("intermediaryToNamedMapping", "intermediateToNamed.srg"); - obf.getSrgToNamedMappings().set(intermediateToNamed); var mappingsCsv = artifacts.requestAdditionalMinecraftArtifact("csvMapping", "intermediateToNamed.zip"); + obf.getSrgToNamedMappings().set(mappingsCsv); extension.getRuns().configureEach(run -> { LegacyForgeFacade.configureRun(project, run); From c9c86e7f75533909365f0314ea60b37dee7fea88 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Tue, 24 Dec 2024 14:39:44 +0100 Subject: [PATCH 05/25] Shorten names, Cleanup. --- .../internal/DataFileCollectionFactory.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java index c5e7baa0..d5c4c643 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java @@ -13,6 +13,13 @@ import java.io.File; import java.util.function.Consumer; +/** + * Access Transformers and Interface Injection Data are treated in a common way as "collections of data files", + * which can be declared via a {@link DataFileCollection DSL}, and have an associated configuration for internal + * use by the plugin and the publication of these files. + * + * This factory constructs these pairs. + */ @ApiStatus.Internal public final class DataFileCollectionFactory { public static final String CONFIGURATION_ACCESS_TRANSFORMERS = "accessTransformers"; @@ -22,14 +29,18 @@ public final class DataFileCollectionFactory { private DataFileCollectionFactory() { } - public record DefaultDataFileCollections(DataFileCollectionWrapper accessTransformers, - DataFileCollectionWrapper interfaceInjectionData) { + public record DefaultCollections(CollectionWrapper accessTransformers, + CollectionWrapper interfaceInjectionData) { } - public record DataFileCollectionWrapper(DataFileCollection extension, Configuration configuration) { + public record CollectionWrapper(DataFileCollection extension, Configuration configuration) { } - public static DefaultDataFileCollections createDefault(Project project) { + /** + * Constructs the default data file collections for access transformers and intrface injection data + * with sensible defaults. + */ + public static DefaultCollections createDefault(Project project) { // Create an access transformer configuration var accessTransformers = DataFileCollectionFactory.create( project, @@ -60,10 +71,10 @@ public static DefaultDataFileCollections createDefault(Project project) { "interfaceinjection" ); - return new DefaultDataFileCollections(accessTransformers, interfaceInjectionData); + return new DefaultCollections(accessTransformers, interfaceInjectionData); } - public static DataFileCollectionWrapper create(Project project, String name, String description, String category) { + public static CollectionWrapper create(Project project, String name, String description, String category) { var configuration = project.getConfigurations().create(name, spec -> { spec.setDescription(description); spec.setCanBeConsumed(false); @@ -118,7 +129,7 @@ public void accept(Object artifactNotation) { var extension = project.getObjects().newInstance(DataFileCollection.class, publishCallback); configuration.getDependencies().add(depFactory.create(extension.getFiles())); - return new DataFileCollectionWrapper(extension, configuration); + return new CollectionWrapper(extension, configuration); } } From a1a82080733a7a5450c184bf5bd1a1c02453c8c2 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Tue, 24 Dec 2024 14:52:44 +0100 Subject: [PATCH 06/25] Re-Enable the neoForge.setVersion method --- .../legacyforge/dsl/LegacyForgeExtension.java | 10 ++++++++-- .../moddevgradle/dsl/NeoForgeExtension.java | 13 ++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index fdffbc23..624350c6 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -25,9 +25,15 @@ public LegacyForgeExtension(Project project, this.project = project; } - public void setVersion(String version) { + /** + * Shorthand for: + * + * enableModding { forgeVersion = '...' } + * + */ + public void setVersion(Object version) { enableModding(settings -> { - settings.setForgeVersion(version); + settings.setForgeVersion(version.toString()); }); } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index b7ab904e..be76878b 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -28,9 +28,16 @@ public NeoForgeExtension(Project project, DataFileCollection accessTransformers, unitTest.getLoadedMods().convention(getMods()); } - @Deprecated(forRemoval = true) - public void setVersion(Object any) { - throw new InvalidUserCodeException("Please use enableModding { neoForgeVersion = ... } instead of the version property."); + /** + * Shorthand for: + * + * enableModding { neoForgeVersion = '...' } + * + */ + public void setVersion(Object version) { + enableModding(settings -> { + settings.setNeoForgeVersion(version.toString()); + }); } public void enableModding(Action customizer) { From 9f3ca3c3f5aab99cc5151e2fd06dd300b1144d3f Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Tue, 24 Dec 2024 14:59:22 +0100 Subject: [PATCH 07/25] Review Comments --- .../dsl/LegacyForgeModdingSettings.java | 1 + .../internal/ModDevExtension.java | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java index 22ba415a..8af43ba3 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java @@ -8,6 +8,7 @@ public abstract class LegacyForgeModdingSettings { @Nullable private String neoForgeVersion; + @Nullable private String forgeVersion; @Nullable diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java index acfda39c..4ee62576 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java @@ -4,14 +4,16 @@ import net.neoforged.moddevgradle.dsl.ModModel; import net.neoforged.moddevgradle.dsl.Parchment; import net.neoforged.moddevgradle.dsl.RunModel; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.Action; -import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.GradleException; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; +import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import javax.inject.Inject; @@ -22,6 +24,7 @@ public abstract class ModDevExtension { private final NamedDomainObjectContainer runs; private final Parchment parchment; + private final Project project; private final DataFileCollection accessTransformers; private final DataFileCollection interfaceInjectionData; @@ -32,6 +35,7 @@ public ModDevExtension(Project project, mods = project.container(ModModel.class); runs = project.container(RunModel.class, name -> project.getObjects().newInstance(RunModel.class, name, project, mods)); parchment = project.getObjects().newInstance(Parchment.class); + this.project = project; this.accessTransformers = accessTransformers; this.interfaceInjectionData = interfaceInjectionData; getValidateAccessTransformers().convention(false); @@ -142,4 +146,17 @@ public void ideSyncTask(Task task) { * NeoForge versions. */ public abstract MapProperty getAdditionalMinecraftArtifacts(); + + /** + * Adds the necessary dependencies to develop a Minecraft mod to the given source set. + * The plugin automatically adds these dependencies to the main source set. + */ + public void addModdingDependenciesTo(SourceSet sourceSet) { + var sourceSets = ExtensionUtils.getSourceSets(project); + if (!sourceSets.contains(sourceSet)) { + throw new GradleException("Cannot add to the source set in another project."); + } + + ModDevArtifactsWorkflow.get(project).addToSourceSet(sourceSet.getName()); + } } From 0b8ccbf8a0cb11b74539ef1bcbb6491bef620790 Mon Sep 17 00:00:00 2001 From: shartte Date: Tue, 24 Dec 2024 14:59:40 +0100 Subject: [PATCH 08/25] Update src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java Co-authored-by: Matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com> --- .../moddevgradle/legacyforge/dsl/ObfuscationExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java index 28b9ce33..189279ae 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java @@ -48,7 +48,7 @@ private Provider assertConfigured(Provider provider) { } /** - * Format is TSRG + * Format is TSRG. */ @ApiStatus.Internal public abstract RegularFileProperty getNamedToSrgMappings(); From 810fa3f26488e07c283c625cbfe7865f9fe0ca22 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:17:54 +0100 Subject: [PATCH 09/25] Undo seemingly unnecessary nfrtgradle changes --- .../net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java | 3 +-- src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java index 4c4b981c..ed465533 100644 --- a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java +++ b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java @@ -279,6 +279,5 @@ public void createArtifacts() { run(args); } - record RequestedResult(String id, File destination) { - } + record RequestedResult(String id, File destination) {} } diff --git a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java index bfa43800..6dbff66b 100644 --- a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java +++ b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeTask.java @@ -65,7 +65,7 @@ public abstract class NeoFormRuntimeTask extends DefaultTask { */ @Input @ApiStatus.Internal - public abstract Property getJavaExecutable(); + protected abstract Property getJavaExecutable(); @Inject @ApiStatus.Internal From 2c44516430ba30984829c5d72c5764881a453094 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:21:34 +0100 Subject: [PATCH 10/25] Move ModDevExtension to dsl package --- .../moddevgradle/legacyforge/dsl/LegacyForgeExtension.java | 2 +- .../moddevgradle/legacyforge/dsl/MixinExtension.java | 1 - .../moddevgradle/{internal => dsl}/ModDevExtension.java | 7 ++----- .../net/neoforged/moddevgradle/dsl/NeoForgeExtension.java | 2 -- .../moddevgradle/internal/ModDevArtifactsWorkflow.java | 1 + .../net/neoforged/moddevgradle/internal/ModDevPlugin.java | 1 + 6 files changed, 5 insertions(+), 9 deletions(-) rename src/main/java/net/neoforged/moddevgradle/{internal => dsl}/ModDevExtension.java (95%) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index 624350c6..0e71a86e 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -1,7 +1,7 @@ package net.neoforged.moddevgradle.legacyforge.dsl; import net.neoforged.moddevgradle.dsl.DataFileCollection; -import net.neoforged.moddevgradle.internal.ModDevExtension; +import net.neoforged.moddevgradle.dsl.ModDevExtension; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin; import org.gradle.api.Action; diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java index d74ebe6c..cf703f02 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinExtension.java @@ -8,7 +8,6 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.jvm.tasks.Jar; -import org.jetbrains.annotations.ApiStatus; import javax.inject.Inject; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java similarity index 95% rename from src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java rename to src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index 4ee62576..c1765cd8 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -1,9 +1,6 @@ -package net.neoforged.moddevgradle.internal; +package net.neoforged.moddevgradle.dsl; -import net.neoforged.moddevgradle.dsl.DataFileCollection; -import net.neoforged.moddevgradle.dsl.ModModel; -import net.neoforged.moddevgradle.dsl.Parchment; -import net.neoforged.moddevgradle.dsl.RunModel; +import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.Action; import org.gradle.api.GradleException; diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index be76878b..df34bb75 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -1,10 +1,8 @@ package net.neoforged.moddevgradle.dsl; -import net.neoforged.moddevgradle.internal.ModDevExtension; import net.neoforged.moddevgradle.internal.ModDevPlugin; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.Action; -import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 91839bae..35f8117f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -1,6 +1,7 @@ package net.neoforged.moddevgradle.internal; import net.neoforged.minecraftdependencies.MinecraftDistribution; +import net.neoforged.moddevgradle.dsl.ModDevExtension; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index e70e6f6f..b8c4e849 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -1,6 +1,7 @@ package net.neoforged.moddevgradle.internal; import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin; +import net.neoforged.moddevgradle.dsl.ModDevExtension; import net.neoforged.moddevgradle.dsl.ModdingVersionSettings; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; From dd4a7f11fc762fb2ea52458d98fc48141cbe6271 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:26:07 +0100 Subject: [PATCH 11/25] tweak run workflow --- .../internal/ModDevRunWorkflow.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 4a94a92c..e4a59c76 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -60,7 +60,7 @@ public class ModDevRunWorkflow { private final ModuleDependency testFixturesDependency; private final ModuleDependency gameLibrariesDependency; private final Configuration additionalClasspath; - private final @Nullable Configuration userDevConfigOnly; + private final Configuration userDevConfigOnly; /** * @param gameLibrariesDependency A module dependency that represents the library dependencies of the game. @@ -87,19 +87,15 @@ private ModDevRunWorkflow(Project project, // Let's try to get the userdev JSON out of the universal jar // I don't like having to use a configuration for this... - if (runTypesConfigDependency != null) { - userDevConfigOnly = configurations.create("neoForgeConfigOnly", spec -> { - spec.setDescription("Resolves exclusively the NeoForge userdev JSON for configuring runs"); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.setTransitive(false); + userDevConfigOnly = configurations.create("neoForgeConfigOnly", spec -> { + spec.setDescription("Resolves exclusively the NeoForge userdev JSON for configuring runs"); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.setTransitive(false); + if (runTypesConfigDependency != null) { spec.getDependencies().add(runTypesConfigDependency); - }); - } else { - // Create an implicit configuration - - userDevConfigOnly = null; - } + } + }); additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); @@ -202,7 +198,7 @@ public void configureTesting(Provider testedMod, Provider createArtifacts) { + private static void addClientResources(Project project, Configuration spec, TaskProvider createArtifacts) { // FML searches for client resources on the legacy classpath spec.getDependencies().add( project.getDependencyFactory().create( From 234797f60bb4e1584b9e76f54226fd8ee5b09360 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:31:33 +0100 Subject: [PATCH 12/25] Undo formatting change --- .../moddevgradle/legacyforge/tasks/RemapOperation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java index 697464f4..312a071a 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/tasks/RemapOperation.java @@ -25,8 +25,7 @@ public abstract class RemapOperation implements Serializable { @Inject - public RemapOperation() { - } + public RemapOperation() {} @Input public abstract Property getToolType(); From 25dc55fe9969ff98b848fd358d7f6341ad482779 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:52:11 +0100 Subject: [PATCH 13/25] Move JarJar plugin to jarjar subpackage --- .../legacyforge/internal/LegacyForgeModDevPlugin.java | 2 +- .../java/net/neoforged/moddevgradle/internal/ModDevPlugin.java | 1 + .../moddevgradle/internal/{ => jarjar}/JarJarPlugin.java | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) rename src/main/java/net/neoforged/moddevgradle/internal/{ => jarjar}/JarJarPlugin.java (91%) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 3828e4d3..564b9f02 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -4,7 +4,7 @@ import net.neoforged.moddevgradle.internal.ArtifactNamingStrategy; import net.neoforged.moddevgradle.internal.Branding; import net.neoforged.moddevgradle.internal.DataFileCollectionFactory; -import net.neoforged.moddevgradle.internal.JarJarPlugin; +import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.LegacyForgeFacade; import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; import net.neoforged.moddevgradle.internal.ModDevRunWorkflow; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index b8c4e849..811974cf 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -4,6 +4,7 @@ import net.neoforged.moddevgradle.dsl.ModDevExtension; import net.neoforged.moddevgradle.dsl.ModdingVersionSettings; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; +import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; import org.gradle.api.Plugin; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java similarity index 91% rename from src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java rename to src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java index fab93146..a8efda14 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/JarJarPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java @@ -1,5 +1,6 @@ -package net.neoforged.moddevgradle.internal; +package net.neoforged.moddevgradle.internal.jarjar; +import net.neoforged.moddevgradle.internal.Branding; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.tasks.JarJar; import org.gradle.api.Plugin; From f7b05119800d1e0a7027a3fdce6875c8aa458b30 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 17:52:11 +0100 Subject: [PATCH 14/25] Wire IDE Sync tasks up in the artifact workflow --- .../moddevgradle/internal/ModDevArtifactsWorkflow.java | 3 +++ .../java/net/neoforged/moddevgradle/internal/ModDevPlugin.java | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 35f8117f..2bacc6ea 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -67,6 +67,9 @@ public static ModDevArtifactsWorkflow create(Project project, ) { var ideIntegration = IdeIntegration.of(project, branding); + // Make sync tasks run + ideIntegration.runTaskOnProjectSync(extension.getIdeSyncTasks()); + // We use this directory to store intermediate files used during moddev var modDevBuildDir = project.getLayout().getBuildDirectory().dir("moddev"); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 811974cf..7838689f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -38,13 +38,12 @@ public void apply(Project project) { } var dataFileCollections = DataFileCollectionFactory.createDefault(project); - var extension = project.getExtensions().create( + project.getExtensions().create( NeoForgeExtension.NAME, NeoForgeExtension.class, dataFileCollections.accessTransformers().extension(), dataFileCollections.interfaceInjectionData().extension() ); - IdeIntegration.of(project, Branding.MDG).runTaskOnProjectSync(extension.getIdeSyncTasks()); } public void enableModding( From 72d1bb4d75b52cad1bce7ecf69011908b858ef27 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 18:10:14 +0100 Subject: [PATCH 15/25] Refactor --- legacytest/build.gradle | 4 +-- .../legacyforge/dsl/LegacyForgeExtension.java | 24 ++++++++++++++--- .../legacyforge/dsl/ObfuscationExtension.java | 2 +- .../internal/LegacyForgeModDevPlugin.java | 2 +- .../moddevgradle/dsl/ModDevExtension.java | 6 +++++ .../dsl/ModdingVersionSettings.java | 10 +++---- .../moddevgradle/dsl/NeoForgeExtension.java | 26 +++++++++++++++---- .../moddevgradle/internal/IdeIntegration.java | 4 ++- .../internal/ModDevArtifactsWorkflow.java | 3 --- .../moddevgradle/internal/ModDevPlugin.java | 4 +-- .../DataFileCollectionFunctionalTest.java | 4 +-- .../AccessTransformerConventionTest.java | 2 +- testproject/build.gradle | 4 +-- testproject/common/build.gradle | 4 +-- testproject/jijtest/build.gradle | 4 +-- testproject/subproject/build.gradle | 4 +-- 16 files changed, 67 insertions(+), 40 deletions(-) diff --git a/legacytest/build.gradle b/legacytest/build.gradle index b8ebdc4e..b537b4b9 100644 --- a/legacytest/build.gradle +++ b/legacytest/build.gradle @@ -13,9 +13,7 @@ java { } legacyForge { - enableModding { - mcpVersion = '1.19.2' - } + mcpVersion = '1.19.2' } publishing { diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index 0e71a86e..af386963 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -26,18 +26,34 @@ public LegacyForgeExtension(Project project, } /** + * Enables modding for the main source set using the given Forge version. + * * Shorthand for: * - * enableModding { forgeVersion = '...' } + * enable { forgeVersion = '...' } * */ public void setVersion(Object version) { - enableModding(settings -> { + enable(settings -> { settings.setForgeVersion(version.toString()); }); } - public void enableModding(Action customizer) { + /** + * Enables modding for the main source-set in Vanilla-mode. + * + * Shorthand for: + * + * enable { forgeVersion = '...' } + * + */ + public void setMcpVersion(Object version) { + enable(settings -> { + settings.setMcpVersion(version.toString()); + }); + } + + public void enable(Action customizer) { var plugin = project.getPlugins().getPlugin(LegacyForgeModDevPlugin.class); var settings = project.getObjects().newInstance(LegacyForgeModdingSettings.class); @@ -48,6 +64,6 @@ public void enableModding(Action customizer) { })); customizer.execute(settings); - plugin.enableModding(project, settings, this); + plugin.enable(project, settings, this); } } diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java index 189279ae..a27bb342 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java @@ -43,7 +43,7 @@ public ObfuscationExtension(Project project, private Provider assertConfigured(Provider provider) { return provider.orElse(project.provider(() -> { - throw new InvalidUserCodeException("Please enable modding by setting legacyForge.version or calling legacyForge.enableModding()"); + throw new InvalidUserCodeException("Please enable modding by setting legacyForge.version or calling legacyForge.enable()"); })); } diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 564b9f02..72065056 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -111,7 +111,7 @@ public void apply(Project project) { ); } - public void enableModding(Project project, LegacyForgeModdingSettings settings, LegacyForgeExtension extension) { + public void enable(Project project, LegacyForgeModdingSettings settings, LegacyForgeExtension extension) { var depFactory = project.getDependencyFactory(); var forgeVersion = settings.getForgeVersion(); diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index c1765cd8..e68bdc3f 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -1,5 +1,7 @@ package net.neoforged.moddevgradle.dsl; +import net.neoforged.moddevgradle.internal.Branding; +import net.neoforged.moddevgradle.internal.IdeIntegration; import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.Action; @@ -36,6 +38,10 @@ public ModDevExtension(Project project, this.accessTransformers = accessTransformers; this.interfaceInjectionData = interfaceInjectionData; getValidateAccessTransformers().convention(false); + + // Make sync tasks run + var ideIntegration = IdeIntegration.of(project, Branding.MDG); + ideIntegration.runTaskOnProjectSync(getIdeSyncTasks()); } /** diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java index 8989ac0a..6085a6ce 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java @@ -6,13 +6,13 @@ public abstract class ModdingVersionSettings { @Nullable - private String neoForgeVersion; + private String version; @Nullable private String neoFormVersion; - public @Nullable String getNeoForgeVersion() { - return neoForgeVersion; + public @Nullable String getVersion() { + return version; } public @Nullable String getNeoFormVersion() { @@ -22,8 +22,8 @@ public abstract class ModdingVersionSettings { /** * NeoForge version number. You have to set either this or {@link #setNeoFormVersion}. */ - public void setNeoForgeVersion(String version) { - this.neoForgeVersion = version; + public void setVersion(String version) { + this.version = version; } /** diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index df34bb75..fbe4ad03 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -27,18 +27,34 @@ public NeoForgeExtension(Project project, DataFileCollection accessTransformers, } /** + * Enables modding on the main source set with the given NeoForge version. + * * Shorthand for: * - * enableModding { neoForgeVersion = '...' } + * enable { version = '...' } * */ public void setVersion(Object version) { - enableModding(settings -> { - settings.setNeoForgeVersion(version.toString()); + enable(settings -> { + settings.setVersion(version.toString()); }); } - public void enableModding(Action customizer) { + /** + * Enables the Vanilla-only mode of ModDevGradle. + * + * Shorthand for: + * + * enable { neoFormVersion = '...' } + * + */ + public void setNeoFormVersion(Object version) { + enable(settings -> { + settings.setNeoFormVersion(version.toString()); + }); + } + + public void enable(Action customizer) { var modDevPlugin = project.getPlugins().getPlugin(ModDevPlugin.class); var settings = project.getObjects().newInstance(ModdingVersionSettings.class); @@ -49,7 +65,7 @@ public void enableModding(Action customizer) { })); customizer.execute(settings); - modDevPlugin.enableModding(project, settings, this); + modDevPlugin.enable(project, settings, this); } public UnitTest getUnitTest() { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java index 50a73128..d630ff3c 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java @@ -10,6 +10,7 @@ import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; +import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +21,8 @@ /** * Implementing classes are responsible for registering {@code ideSyncTask} with their IDE. */ -sealed abstract class IdeIntegration permits IntelliJIntegration, EclipseIntegration, NoIdeIntegration { +@ApiStatus.Internal +public sealed abstract class IdeIntegration permits IntelliJIntegration, EclipseIntegration, NoIdeIntegration { private static final Logger LOG = LoggerFactory.getLogger(IdeIntegration.class); /** diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 2bacc6ea..35f8117f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -67,9 +67,6 @@ public static ModDevArtifactsWorkflow create(Project project, ) { var ideIntegration = IdeIntegration.of(project, branding); - // Make sync tasks run - ideIntegration.runTaskOnProjectSync(extension.getIdeSyncTasks()); - // We use this directory to store intermediate files used during moddev var modDevBuildDir = project.getLayout().getBuildDirectory().dir("moddev"); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 7838689f..0bce97cc 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -46,12 +46,12 @@ public void apply(Project project) { ); } - public void enableModding( + public void enable( Project project, ModdingVersionSettings settings, ModDevExtension extension ) { - var neoForgeVersion = settings.getNeoForgeVersion(); + var neoForgeVersion = settings.getVersion(); var neoFormVersion = settings.getNeoFormVersion(); if (neoForgeVersion == null && neoFormVersion == null) { throw new IllegalArgumentException("You must specify at least a NeoForge or a NeoForm version for vanilla-only mode"); diff --git a/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java b/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java index 76b511dc..95e6bf72 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/DataFileCollectionFunctionalTest.java @@ -147,9 +147,7 @@ private void publishDataFiles(String groupId, group = "{0}" version = "{1}" neoForge { - enableModding { - neoForgeVersion = "{DEFAULT_NEOFORGE_VERSION}" - } + version = "{DEFAULT_NEOFORGE_VERSION}" } publishing { publications { diff --git a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java index adc9f5a0..76b7c853 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java @@ -25,7 +25,7 @@ void setup() { extension = ExtensionUtils.getExtension(project, "neoForge", NeoForgeExtension.class); - extension.enableModding(settings -> settings.setNeoForgeVersion("1.2.3")); + extension.enable(settings -> settings.setVersion("1.2.3")); } @Test diff --git a/testproject/build.gradle b/testproject/build.gradle index 6d7a1b1b..7e99fb43 100644 --- a/testproject/build.gradle +++ b/testproject/build.gradle @@ -26,8 +26,8 @@ test { } neoForge { - enableModding { - neoForgeVersion = project.neoforge_version + enable { + version = project.neoforge_version enabledSourceSets = [sourceSets.main, sourceSets.api] } diff --git a/testproject/common/build.gradle b/testproject/common/build.gradle index d88fc19c..2fc6f383 100644 --- a/testproject/common/build.gradle +++ b/testproject/common/build.gradle @@ -7,9 +7,7 @@ plugins { } neoForge { - enableModding { - neoFormVersion = "1.21-20240613.152323" - } + neoFormVersion = "1.21-20240613.152323" accessTransformers { from(project.file('accesstransformer.cfg')) diff --git a/testproject/jijtest/build.gradle b/testproject/jijtest/build.gradle index fae3fbcf..028146e3 100644 --- a/testproject/jijtest/build.gradle +++ b/testproject/jijtest/build.gradle @@ -40,9 +40,7 @@ test { } neoForge { - enableModding { - neoForgeVersion = project.neoforge_version - } + version = project.neoforge_version runs { data { diff --git a/testproject/subproject/build.gradle b/testproject/subproject/build.gradle index 827b4a64..5a178677 100644 --- a/testproject/subproject/build.gradle +++ b/testproject/subproject/build.gradle @@ -5,9 +5,7 @@ plugins { } neoForge { - enableModding { - neoForgeVersion = project.neoforge_version - } + version = project.neoforge_version interfaceInjectionData { from(project.file('interfaces.json')) From 7bdda3f6f4f3f2bc3a1c9357a715b642d2f663bb Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 18:12:26 +0100 Subject: [PATCH 16/25] Refactor --- testproject/build.gradle | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/testproject/build.gradle b/testproject/build.gradle index 7e99fb43..44cdcbe5 100644 --- a/testproject/build.gradle +++ b/testproject/build.gradle @@ -26,10 +26,8 @@ test { } neoForge { - enable { - version = project.neoforge_version - enabledSourceSets = [sourceSets.main, sourceSets.api] - } + version = project.neoforge_version + addModdingDependenciesTo(sourceSets.api) interfaceInjectionData = files("interfaces.json") From 2fbc714a83926d0d9d881f912d8f693300653e02 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 18:17:01 +0100 Subject: [PATCH 17/25] Refactor --- .../moddevgradle/legacyforge/dsl/LegacyForgeExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index af386963..a1eaf555 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -26,7 +26,7 @@ public LegacyForgeExtension(Project project, } /** - * Enables modding for the main source set using the given Forge version. + * Enables modding for the main source-set using the given Forge version. * * Shorthand for: * @@ -44,7 +44,7 @@ public void setVersion(Object version) { * * Shorthand for: * - * enable { forgeVersion = '...' } + * enable { mcpVersion = '...' } * */ public void setMcpVersion(Object version) { From ed897c040ce5069e9ab9a9fd95b1133143185c7c Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 19:02:35 +0100 Subject: [PATCH 18/25] Review comments --- .../moddevgradle/internal/ModDevArtifactsWorkflow.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 35f8117f..1a190584 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -90,7 +90,8 @@ public static ModDevArtifactsWorkflow create(Project project, try { toolchainSpec.getLanguageVersion().convention(JavaLanguageVersion.of(versionCapabilities.javaVersion())); } catch (IllegalStateException e) { - // We tried our best + // We tried our best, but the java version was already resolved and is thus finalized + // this can occur if any dependency resolution happens since it reads this version for the attributes } // Add a filtered parchment repository automatically if enabled From 7664017b57944d2fdd1b822ed68c8416426a3a59 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 19:03:37 +0100 Subject: [PATCH 19/25] Review comments --- .../neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 1a190584..ed9035da 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -94,7 +94,6 @@ public static ModDevArtifactsWorkflow create(Project project, // this can occur if any dependency resolution happens since it reads this version for the attributes } - // Add a filtered parchment repository automatically if enabled var parchment = extension.getParchment(); var parchmentData = configurations.create("parchmentData", spec -> { spec.setDescription("Data used to add parameter names and javadoc to Minecraft sources"); From acf7dc199eb48a2987daaac06ec4f477fdfafefc Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 19:06:17 +0100 Subject: [PATCH 20/25] Review comments --- .../moddevgradle/internal/ModDevArtifactsWorkflow.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index ed9035da..0b927131 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -124,8 +124,9 @@ public static ModDevArtifactsWorkflow create(Project project, task.getParchmentEnabled().set(parchment.getEnabled()); task.getParchmentConflictResolutionPrefix().set(parchment.getConflictResolutionPrefix()); + var artifactsDir = modDevBuildDir.map(dir -> dir.dir("artifacts")); Function> artifactPathStrategy = artifact -> - modDevBuildDir.map(dir -> dir.dir("artifacts").file(artifactNamingStrategy.getFilename(artifact))); + artifactsDir.map(dir -> dir.file(artifactNamingStrategy.getFilename(artifact))); task.getCompiledArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); task.getCompiledWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); From f3eb8c29a492995279d449d3f64621ed1ae66b13 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 19:06:50 +0100 Subject: [PATCH 21/25] Review comments --- .../internal/ModDevArtifactsWorkflow.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 0b927131..78e263f4 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -208,14 +208,6 @@ public static ModDevArtifactsWorkflow create(Project project, return result; } - public void addToSourceSet(String name) { - var configurations = project.getConfigurations(); - var sourceSets = ExtensionUtils.getSourceSets(project); - var sourceSet = sourceSets.getByName(name); - configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(runtimeDependencies); - configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(compileDependencies); - } - /** * Collects all dependencies needed by the NeoFormRuntime */ @@ -310,6 +302,14 @@ private static List configureArtifactManifestConfigurations( return result; } + public void addToSourceSet(String name) { + var configurations = project.getConfigurations(); + var sourceSets = ExtensionUtils.getSourceSets(project); + var sourceSet = sourceSets.getByName(name); + configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(runtimeDependencies); + configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(compileDependencies); + } + public Provider requestAdditionalMinecraftArtifact(String id, String filename) { return requestAdditionalMinecraftArtifact(id, modDevBuildDir.map(dir -> dir.file(filename))); } From 09a29d9584206b6a4f6f49e5e94b2dd88e5ca2e4 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 22:48:14 +0100 Subject: [PATCH 22/25] Review comments --- .../legacyforge/dsl/LegacyForgeExtension.java | 24 +-- .../dsl/LegacyForgeModdingSettings.java | 30 +++- .../internal/LegacyForgeModDevPlugin.java | 13 +- .../moddevgradle/dsl/ModDevExtension.java | 12 +- .../dsl/ModdingVersionSettings.java | 25 ++- .../moddevgradle/dsl/NeoForgeExtension.java | 13 +- .../moddevgradle/internal/Branding.java | 3 - ...nFactory.java => DataFileCollections.java} | 29 ++-- .../moddevgradle/internal/IdeIntegration.java | 2 - .../internal/ModDevArtifactsWorkflow.java | 39 ++++- .../moddevgradle/internal/ModDevPlugin.java | 14 +- .../internal/ModDevRunWorkflow.java | 14 +- .../moddevgradle/internal/NeoDevFacade.java | 5 +- .../internal/WorkflowArtifact.java | 2 +- .../internal/jarjar/JarJarPlugin.java | 1 + .../functional/AbstractFunctionalTest.java | 6 +- .../functional/GroovyScriptTest.java | 22 +++ .../functional/KotlinScriptTest.java | 24 +++ .../AccessTransformerConventionTest.java | 3 +- .../internal/ModDevPluginTest.java | 151 +++++++++++++++++ .../legacyforge/LegacyModDevPluginTest.java | 155 ++++++++++++++++++ .../legacyforge/dsl/MixinMappingTest.java | 2 +- 22 files changed, 494 insertions(+), 95 deletions(-) rename src/main/java/net/neoforged/moddevgradle/internal/{DataFileCollectionFactory.java => DataFileCollections.java} (89%) create mode 100644 src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java create mode 100644 src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java index a1eaf555..aa0d0140 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java @@ -2,14 +2,11 @@ import net.neoforged.moddevgradle.dsl.DataFileCollection; import net.neoforged.moddevgradle.dsl.ModDevExtension; -import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin; import org.gradle.api.Action; import org.gradle.api.Project; -import org.gradle.api.tasks.SourceSet; import javax.inject.Inject; -import java.util.List; /** * This is the top-level {@code legacyForge} extension, used to configure the moddev plugin. @@ -27,29 +24,29 @@ public LegacyForgeExtension(Project project, /** * Enables modding for the main source-set using the given Forge version. - * + *

* Shorthand for: * - * enable { forgeVersion = '...' } + * enable { forgeVersion = '...' } * */ - public void setVersion(Object version) { + public void setVersion(String version) { enable(settings -> { - settings.setForgeVersion(version.toString()); + settings.setForgeVersion(version); }); } /** * Enables modding for the main source-set in Vanilla-mode. - * + *

* Shorthand for: * - * enable { mcpVersion = '...' } + * enable { mcpVersion = '...' } * */ - public void setMcpVersion(Object version) { + public void setMcpVersion(String version) { enable(settings -> { - settings.setMcpVersion(version.toString()); + settings.setMcpVersion(version); }); } @@ -57,11 +54,6 @@ public void enable(Action customizer) { var plugin = project.getPlugins().getPlugin(LegacyForgeModDevPlugin.class); var settings = project.getObjects().newInstance(LegacyForgeModdingSettings.class); - // By default, enable modding deps only for the main source set - settings.getEnabledSourceSets().convention(project.provider(() -> { - var sourceSets = ExtensionUtils.getSourceSets(project); - return List.of(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)); - })); customizer.execute(settings); plugin.enable(project, settings, this); diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java index 8af43ba3..a004c936 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java @@ -1,9 +1,14 @@ package net.neoforged.moddevgradle.legacyforge.dsl; -import org.gradle.api.provider.ListProperty; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; + public abstract class LegacyForgeModdingSettings { @Nullable private String neoForgeVersion; @@ -14,6 +19,16 @@ public abstract class LegacyForgeModdingSettings { @Nullable private String mcpVersion; + private Set enabledSourceSets = new HashSet<>(); + + @Inject + public LegacyForgeModdingSettings(Project project) { + // By default, enable modding deps only for the main source set + var sourceSets = ExtensionUtils.getSourceSets(project); + var mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + enabledSourceSets.add(mainSourceSet); + } + public @Nullable String getNeoForgeVersion() { return neoForgeVersion; } @@ -54,5 +69,16 @@ public void setMcpVersion(String version) { * Contains the list of source sets for which access to Minecraft classes should be configured. * Defaults to the main source set, but can also be set to an empty list. */ - public abstract ListProperty getEnabledSourceSets(); + + /** + * Contains the list of source sets for which access to Minecraft classes should be configured. + * Defaults to the main source set, but can also be set to an empty list. + */ + public Set getEnabledSourceSets() { + return enabledSourceSets; + } + + public void setEnabledSourceSets(Set enabledSourceSets) { + this.enabledSourceSets = enabledSourceSets; + } } diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 72065056..976daf9a 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -3,7 +3,7 @@ import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin; import net.neoforged.moddevgradle.internal.ArtifactNamingStrategy; import net.neoforged.moddevgradle.internal.Branding; -import net.neoforged.moddevgradle.internal.DataFileCollectionFactory; +import net.neoforged.moddevgradle.internal.DataFileCollections; import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.LegacyForgeFacade; import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; @@ -101,7 +101,7 @@ public void apply(Project project) { configureDependencyRemapping(project, obf); - var dataFileCollections = DataFileCollectionFactory.createDefault(project); + var dataFileCollections = DataFileCollections.create(project); project.getExtensions().create( LEGACYFORGE_EXTENSION, LegacyForgeExtension.class, @@ -174,6 +174,7 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF var artifacts = ModDevArtifactsWorkflow.create( project, + settings.getEnabledSourceSets(), Branding.MDG, extension, platformModule, @@ -182,8 +183,8 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF recompilableMinecraftDataDependencyNotation, librariesDependency, artifactNamingStrategy, - configurations.getByName(DataFileCollectionFactory.CONFIGURATION_ACCESS_TRANSFORMERS), - configurations.getByName(DataFileCollectionFactory.CONFIGURATION_INTERFACE_INJECTION_DATA), + configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), + configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), versionCapabilities ); @@ -199,10 +200,6 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF versionCapabilities ); - for (var sourceSet : settings.getEnabledSourceSets().get()) { - artifacts.addToSourceSet(sourceSet.getName()); - } - // Configure the mixin and obfuscation extensions var mixin = ExtensionUtils.getExtension(project, MIXIN_EXTENSION, MixinExtension.class); var obf = ExtensionUtils.getExtension(project, OBFUSCATION_EXTENSION, ObfuscationExtension.class); diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index e68bdc3f..5ccfee1b 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -151,15 +151,11 @@ public void ideSyncTask(Task task) { public abstract MapProperty getAdditionalMinecraftArtifacts(); /** - * Adds the necessary dependencies to develop a Minecraft mod to the given source set. - * The plugin automatically adds these dependencies to the main source set. + * Adds the necessary dependencies to develop a Minecraft mod to additional source sets. + * If you do not specify a source set when you enable modding, the dependencies are automatically added + * to the main source set. */ public void addModdingDependenciesTo(SourceSet sourceSet) { - var sourceSets = ExtensionUtils.getSourceSets(project); - if (!sourceSets.contains(sourceSet)) { - throw new GradleException("Cannot add to the source set in another project."); - } - - ModDevArtifactsWorkflow.get(project).addToSourceSet(sourceSet.getName()); + ModDevArtifactsWorkflow.get(project).addToSourceSet(sourceSet); } } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java index 6085a6ce..08c61f3c 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java @@ -1,9 +1,14 @@ package net.neoforged.moddevgradle.dsl; -import org.gradle.api.provider.ListProperty; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; + public abstract class ModdingVersionSettings { @Nullable private String version; @@ -11,6 +16,16 @@ public abstract class ModdingVersionSettings { @Nullable private String neoFormVersion; + private Set enabledSourceSets = new HashSet<>(); + + @Inject + public ModdingVersionSettings(Project project) { + // By default, enable modding deps only for the main source set + var sourceSets = ExtensionUtils.getSourceSets(project); + var mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + enabledSourceSets.add(mainSourceSet); + } + public @Nullable String getVersion() { return version; } @@ -39,5 +54,11 @@ public void setNeoFormVersion(String version) { * Contains the list of source sets for which access to Minecraft classes should be configured. * Defaults to the main source set, but can also be set to an empty list. */ - public abstract ListProperty getEnabledSourceSets(); + public Set getEnabledSourceSets() { + return enabledSourceSets; + } + + public void setEnabledSourceSets(Set enabledSourceSets) { + this.enabledSourceSets = enabledSourceSets; + } } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index fbe4ad03..04984dfc 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -34,9 +34,9 @@ public NeoForgeExtension(Project project, DataFileCollection accessTransformers, * enable { version = '...' } * */ - public void setVersion(Object version) { + public void setVersion(String version) { enable(settings -> { - settings.setVersion(version.toString()); + settings.setVersion(version); }); } @@ -48,9 +48,9 @@ public void setVersion(Object version) { * enable { neoFormVersion = '...' } * */ - public void setNeoFormVersion(Object version) { + public void setNeoFormVersion(String version) { enable(settings -> { - settings.setNeoFormVersion(version.toString()); + settings.setNeoFormVersion(version); }); } @@ -58,11 +58,6 @@ public void enable(Action customizer) { var modDevPlugin = project.getPlugins().getPlugin(ModDevPlugin.class); var settings = project.getObjects().newInstance(ModdingVersionSettings.class); - // By default, enable modding deps only for the main source set - settings.getEnabledSourceSets().convention(project.provider(() -> { - var sourceSets = ExtensionUtils.getSourceSets(project); - return List.of(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)); - })); customizer.execute(settings); modDevPlugin.enable(project, settings, this); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/Branding.java b/src/main/java/net/neoforged/moddevgradle/internal/Branding.java index 8d843620..5efcac01 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/Branding.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/Branding.java @@ -1,14 +1,11 @@ package net.neoforged.moddevgradle.internal; -import org.jetbrains.annotations.ApiStatus; - /** * Used to customize the groups of tasks generated by MDG. * * @param publicTaskGroup Use this group for tasks that are considered to be part of the user-interface of MDG. * @param internalTaskGroup Use this group for tasks that are considered to be an implementation detail of MDG. */ -@ApiStatus.Internal public record Branding(String publicTaskGroup, String internalTaskGroup) { public static final Branding MDG = new Branding("mod development", "mod development/internal"); public static final Branding NEODEV = new Branding("neoforge development", "neoforge development/internal"); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollections.java similarity index 89% rename from src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java rename to src/main/java/net/neoforged/moddevgradle/internal/DataFileCollections.java index d5c4c643..896ec937 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollectionFactory.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollections.java @@ -17,32 +17,23 @@ * Access Transformers and Interface Injection Data are treated in a common way as "collections of data files", * which can be declared via a {@link DataFileCollection DSL}, and have an associated configuration for internal * use by the plugin and the publication of these files. - * + *

* This factory constructs these pairs. */ @ApiStatus.Internal -public final class DataFileCollectionFactory { +public record DataFileCollections(CollectionWrapper accessTransformers, + CollectionWrapper interfaceInjectionData) { public static final String CONFIGURATION_ACCESS_TRANSFORMERS = "accessTransformers"; public static final String CONFIGURATION_INTERFACE_INJECTION_DATA = "interfaceInjectionData"; - private DataFileCollectionFactory() { - } - - public record DefaultCollections(CollectionWrapper accessTransformers, - CollectionWrapper interfaceInjectionData) { - } - - public record CollectionWrapper(DataFileCollection extension, Configuration configuration) { - } - /** * Constructs the default data file collections for access transformers and intrface injection data * with sensible defaults. */ - public static DefaultCollections createDefault(Project project) { + public static DataFileCollections create(Project project) { // Create an access transformer configuration - var accessTransformers = DataFileCollectionFactory.create( + var accessTransformers = createCollection( project, CONFIGURATION_ACCESS_TRANSFORMERS, "AccessTransformers to widen visibility of Minecraft classes/fields/methods", @@ -64,17 +55,20 @@ public static DefaultCollections createDefault(Project project) { })); // Create a configuration for grabbing interface injection data - var interfaceInjectionData = DataFileCollectionFactory.create( + var interfaceInjectionData = createCollection( project, CONFIGURATION_INTERFACE_INJECTION_DATA, "Interface injection data adds extend/implements clauses for interfaces to Minecraft code at development time", "interfaceinjection" ); - return new DefaultCollections(accessTransformers, interfaceInjectionData); + return new DataFileCollections(accessTransformers, interfaceInjectionData); } - public static CollectionWrapper create(Project project, String name, String description, String category) { + public record CollectionWrapper(DataFileCollection extension, Configuration configuration) { + } + + private static CollectionWrapper createCollection(Project project, String name, String description, String category) { var configuration = project.getConfigurations().create(name, spec -> { spec.setDescription(description); spec.setCanBeConsumed(false); @@ -131,5 +125,4 @@ public void accept(Object artifactNotation) { return new CollectionWrapper(extension, configuration); } - } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java index d630ff3c..7ae3ebf1 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java @@ -10,7 +10,6 @@ import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; -import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +20,6 @@ /** * Implementing classes are responsible for registering {@code ideSyncTask} with their IDE. */ -@ApiStatus.Internal public sealed abstract class IdeIntegration permits IntelliJIntegration, EclipseIntegration, NoIdeIntegration { private static final Logger LOG = LoggerFactory.getLogger(IdeIntegration.class); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 78e263f4..9a938cbf 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -6,6 +6,8 @@ import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; import net.neoforged.nfrtgradle.DownloadAssets; +import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Named; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -20,6 +22,7 @@ import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaToolchainService; @@ -27,6 +30,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -41,7 +45,10 @@ public record ModDevArtifactsWorkflow( TaskProvider downloadAssets, Configuration runtimeDependencies, Configuration compileDependencies, - Provider modDevBuildDir) { + Provider modDevBuildDir, + Provider artifactsBuildDir +) { + private static final String EXTENSION_NAME = "__internal_modDevArtifactsWorkflow"; public static ModDevArtifactsWorkflow get(Project project) { @@ -53,6 +60,7 @@ public static ModDevArtifactsWorkflow get(Project project) { } public static ModDevArtifactsWorkflow create(Project project, + Collection enabledSourceSets, Branding branding, ModDevExtension extension, ModuleDependency moddingPlatformDependency, @@ -65,10 +73,15 @@ public static ModDevArtifactsWorkflow create(Project project, Configuration interfaceInjectionData, VersionCapabilities versionCapabilities ) { + if (project.getExtensions().findByName(EXTENSION_NAME) != null) { + throw new InvalidUserCodeException("You cannot enable modding in the same project twice."); + } + var ideIntegration = IdeIntegration.of(project, branding); // We use this directory to store intermediate files used during moddev var modDevBuildDir = project.getLayout().getBuildDirectory().dir("moddev"); + var artifactsBuildDir = project.getLayout().getBuildDirectory().dir("moddev/artifacts"); var createManifestConfigurations = configureArtifactManifestConfigurations( project, @@ -124,9 +137,8 @@ public static ModDevArtifactsWorkflow create(Project project, task.getParchmentEnabled().set(parchment.getEnabled()); task.getParchmentConflictResolutionPrefix().set(parchment.getConflictResolutionPrefix()); - var artifactsDir = modDevBuildDir.map(dir -> dir.dir("artifacts")); Function> artifactPathStrategy = artifact -> - artifactsDir.map(dir -> dir.file(artifactNamingStrategy.getFilename(artifact))); + artifactsBuildDir.map(dir -> dir.file(artifactNamingStrategy.getFilename(artifact))); task.getCompiledArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); task.getCompiledWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); @@ -202,9 +214,16 @@ public static ModDevArtifactsWorkflow create(Project project, downloadAssets, runtimeDependencies, compileDependencies, - modDevBuildDir + modDevBuildDir, + artifactsBuildDir ); + project.getExtensions().add(ModDevArtifactsWorkflow.class, EXTENSION_NAME, result); + + for (var sourceSets : enabledSourceSets) { + result.addToSourceSet(sourceSets); + } + return result; } @@ -302,16 +321,22 @@ private static List configureArtifactManifestConfigurations( return result; } - public void addToSourceSet(String name) { + /** + * Adds the compile-time and runtime-dependencies needed to compile mod code to the source-set of the given name. + */ + public void addToSourceSet(SourceSet sourceSet) { var configurations = project.getConfigurations(); var sourceSets = ExtensionUtils.getSourceSets(project); - var sourceSet = sourceSets.getByName(name); + if (!sourceSets.contains(sourceSet)) { + throw new GradleException("Cannot add to the source set in another project: " + sourceSet); + } + configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(runtimeDependencies); configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(compileDependencies); } public Provider requestAdditionalMinecraftArtifact(String id, String filename) { - return requestAdditionalMinecraftArtifact(id, modDevBuildDir.map(dir -> dir.file(filename))); + return requestAdditionalMinecraftArtifact(id, artifactsBuildDir.map(dir -> dir.file(filename))); } public Provider requestAdditionalMinecraftArtifact(String id, Provider path) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 0bce97cc..fd4d3a06 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -7,6 +7,7 @@ import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.ModuleDependency; @@ -37,7 +38,7 @@ public void apply(Project project) { LOG.info("Not enabling NeoForged repositories since they were applied at the settings level"); } - var dataFileCollections = DataFileCollectionFactory.createDefault(project); + var dataFileCollections = DataFileCollections.create(project); project.getExtensions().create( NeoForgeExtension.NAME, NeoForgeExtension.class, @@ -54,7 +55,7 @@ public void enable( var neoForgeVersion = settings.getVersion(); var neoFormVersion = settings.getNeoFormVersion(); if (neoForgeVersion == null && neoFormVersion == null) { - throw new IllegalArgumentException("You must specify at least a NeoForge or a NeoForm version for vanilla-only mode"); + throw new InvalidUserCodeException("You must specify at least a NeoForge or a NeoForm version for vanilla-only mode"); } var dependencyFactory = project.getDependencyFactory(); @@ -106,6 +107,7 @@ public void enable( var artifacts = ModDevArtifactsWorkflow.create( project, + settings.getEnabledSourceSets(), Branding.MDG, extension, neoForgeModule, @@ -114,15 +116,11 @@ public void enable( recompilableMinecraftWorkflowDataDependencyNotation, neoForgeModDevLibrariesDependency, artifactNamingStrategy, - configurations.getByName(DataFileCollectionFactory.CONFIGURATION_ACCESS_TRANSFORMERS), - configurations.getByName(DataFileCollectionFactory.CONFIGURATION_INTERFACE_INJECTION_DATA), + configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), + configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), versionCapabilities ); - for (var sourceSet : settings.getEnabledSourceSets().get()) { - artifacts.addToSourceSet(sourceSet.getName()); - } - ModDevRunWorkflow.create( project, Branding.MDG, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 3249d180..837179c5 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -120,7 +120,7 @@ private ModDevRunWorkflow(Project project, }, legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile), - project.provider(() -> versionCapabilities) + versionCapabilities ); } @@ -164,7 +164,7 @@ public void configureTesting(Provider testedMod, Provider testedMod, Provider 1) { + // NOTE: We can implement support for multiple test tasks later if someone is adamant about it + throw new InvalidUserCodeException("MDG currently only supports test suites with a single test task."); + } + for (var target : testSuite.getTargets()) { setupTestTask( project, @@ -200,7 +205,6 @@ public void configureTesting(Provider testedMod, Provider createArtifacts) { - // FML searches for client resources on the legacy classpath spec.getDependencies().add( project.getDependencyFactory().create( project.files(createArtifacts.flatMap(CreateMinecraftArtifacts::getResourcesArtifact)) @@ -217,7 +221,7 @@ public static void setupRuns( Consumer configureModulePath, Consumer configureLegacyClasspath, Provider assetPropertiesFile, - Provider versionCapabilities + VersionCapabilities versionCapabilities ) { var dependencyFactory = project.getDependencyFactory(); var ideIntegration = IdeIntegration.of(project, branding); @@ -273,7 +277,7 @@ private static TaskProvider setupRunInGradle( Consumer configureLegacyClasspath, // TODO: can be removed in favor of directly passing a configuration for the moddev libraries Provider assetPropertiesFile, Configuration devLaunchConfig, - Provider versionCapabilities, + VersionCapabilities versionCapabilities, TaskProvider createLaunchScriptsTask) { var ideIntegration = IdeIntegration.of(project, branding); var configurations = project.getConfigurations(); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index f3402caf..6758c606 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -43,7 +43,8 @@ public static void setupRuns(Project project, configureModulePath, configureAdditionalClasspath, assetPropertiesFile, - project.getObjects().property(VersionCapabilities.class) // empty provider + // This overload of the method was only used by NeoForge 1.21.3 + VersionCapabilities.ofMinecraftVersion("1.21.3") ); } @@ -65,7 +66,7 @@ public static void setupRuns(Project project, configureModulePath, configureAdditionalClasspath, assetPropertiesFile, - neoFormVersion.map(VersionCapabilities::ofNeoFormVersion) + neoFormVersion.map(VersionCapabilities::ofNeoFormVersion).getOrElse(VersionCapabilities.latest()) ); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java index ac7b7add..c1a7f795 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java @@ -7,7 +7,7 @@ public enum WorkflowArtifact { COMPILED(""), COMPILED_WITH_SOURCES("-merged"), SOURCES("-sources"), - CLIENT_RESOURCES("-client-extra"); + CLIENT_RESOURCES("-client-extra-aka-minecraft-resources"); public final String defaultSuffix; WorkflowArtifact(String defaultSuffix) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java index a8efda14..885cefd7 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/jarjar/JarJarPlugin.java @@ -7,6 +7,7 @@ import org.gradle.api.Project; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.gradle.api.tasks.compile.JavaCompile; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal diff --git a/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java b/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java index 1427611e..792459d8 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java @@ -48,7 +48,11 @@ void writeGroovyBuildScript(@Language("gradle") String text, Object... args) thr writeFile(buildFile, interpolateTemplate(text, args)); } - private static String interpolateTemplate(@Language("gradle") String text, Object[] args) { + void writeKotlinBuildScript(@Language("kotlin") String text, Object... args) throws IOException { + writeFile(buildFile, interpolateTemplate(text, args)); + } + + private static String interpolateTemplate(String text, Object[] args) { var m = Pattern.compile("\\{(\\d+|[A-Z_]+)}"); var body = m.matcher(text).replaceAll(matchResult -> { Object arg; diff --git a/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java b/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java index 387838d2..150d803d 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/GroovyScriptTest.java @@ -32,4 +32,26 @@ public void testApplyInEmptyProject() throws IOException { assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); } + @Test + public void testApplyInEmptyProjectAndEnable() throws IOException { + writeFile(settingsFile, "rootProject.name = 'hello-world'"); + writeGroovyBuildScript(""" + plugins { + id "net.neoforged.moddev" + } + neoForge { + version = "{DEFAULT_NEOFORGE_VERSION}" + } + """); + + BuildResult result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir) + .withArguments("tasks", "--all") + .build(); + + assertThat(result.getOutput()).contains("createMinecraftArtifacts"); + assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); + } + } diff --git a/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java b/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java index e04fa8cc..0e311686 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/KotlinScriptTest.java @@ -32,4 +32,28 @@ public void testApplyInEmptyProject() throws IOException { assertThat(result.getOutput()).doesNotContain("createMinecraftArtifacts"); assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); } + + @Test + public void testApplyInEmptyProjectAndEnable() throws IOException { + writeFile(settingsFile, """ + rootProject.name = "hello-world"; + """); + writeKotlinBuildScript(""" + plugins { + id("net.neoforged.moddev") + } + neoForge { + version = "{DEFAULT_NEOFORGE_VERSION}" + } + """); + + BuildResult result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir) + .withArguments("tasks", "--all") + .build(); + + assertThat(result.getOutput()).contains("createMinecraftArtifacts"); + assertEquals(TaskOutcome.SUCCESS, result.task(":tasks").getOutcome()); + } } diff --git a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java index 76b7c853..41117846 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/AccessTransformerConventionTest.java @@ -24,8 +24,7 @@ void setup() { project.getPlugins().apply(ModDevPlugin.class); extension = ExtensionUtils.getExtension(project, "neoForge", NeoForgeExtension.class); - - extension.enable(settings -> settings.setVersion("1.2.3")); + extension.setVersion("1.2.3"); } @Test diff --git a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java new file mode 100644 index 00000000..2efbfb22 --- /dev/null +++ b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java @@ -0,0 +1,151 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.dsl.NeoForgeExtension; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ModDevPluginTest { + private final Project project; + private final NeoForgeExtension extension; + private final SourceSet mainSourceSet; + private final SourceSet testSourceSet; + + public ModDevPluginTest() { + project = ProjectBuilder.builder().build(); + project.getPlugins().apply(ModDevPlugin.class); + + extension = ExtensionUtils.getExtension(project, "neoForge", NeoForgeExtension.class); + + var sourceSets = ExtensionUtils.getSourceSets(project); + mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + + // Set the Java version to the currently running Java to make it use that + var java = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); + java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.current()); + } + + @Test + void testModdingCannotBeEnabledTwice() { + extension.setVersion("1.2.3"); + var e = assertThrows(InvalidUserCodeException.class, () -> extension.setVersion("1.2.3")); + assertThat(e).hasMessage("You cannot enable modding in the same project twice."); + } + + @Test + void testEnableForTestSourceSetOnly() { + extension.enable(settings -> { + settings.setVersion("1.2.3"); + settings.setEnabledSourceSets(Set.of(testSourceSet)); + }); + + // Both the compile and runtime classpath of the main source set had no dependencies added + assertNoDependencies(mainSourceSet.getCompileClasspathConfigurationName()); + assertNoDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()); + + // While the test classpath should have modding dependencies + assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); + } + + @Test + void testAddModdingDependenciesTo() { + extension.setVersion("1.2.3"); + + // Initially, only the main source set should have the dependencies + assertContainsModdingCompileDependencies("1.2.3", mainSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", mainSourceSet.getRuntimeClasspathConfigurationName()); + assertNoDependencies(testSourceSet.getCompileClasspathConfigurationName()); + assertNoDependencies(testSourceSet.getRuntimeClasspathConfigurationName()); + + // Now add it to the test source set too + extension.addModdingDependenciesTo(testSourceSet); + + assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); + } + + private void assertNoDependencies(String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .isEmpty(); + } + + private void assertContainsModdingCompileDependencies(String version, String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .containsOnly( + "build/moddev/artifacts/neoforge-" + version + ".jar", + "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]" + ); + } + + private void assertContainsModdingRuntimeDependencies(String version, String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + + var dependentTasks = configuration.getBuildDependencies().getDependencies(null); + assertThat(dependentTasks) + .extracting(Task::getName) + .containsOnly("createMinecraftArtifacts"); + + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .containsOnly( + "build/moddev/artifacts/neoforge-" + version + ".jar", + "build/moddev/artifacts/neoforge-" + version + "-client-extra.jar", + "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]" + ); + } + + private String describeDependency(Dependency dependency) { + if (dependency instanceof FileCollectionDependency fileCollectionDependency) { + return fileCollectionDependency.getFiles().getFiles() + .stream() + .map(f -> project.getProjectDir().toPath().relativize(f.toPath()).toString().replace('\\', '/')) + .collect(Collectors.joining(";")); + } else if (dependency instanceof ExternalModuleDependency moduleDependency) { + return moduleDependency.getGroup() + + ":" + moduleDependency.getName() + + ":" + moduleDependency.getVersion() + + formatCapabilities(moduleDependency); + } else { + return dependency.toString(); + } + } + + private String formatCapabilities(ExternalModuleDependency moduleDependency) { + var capabilities = moduleDependency.getRequestedCapabilities(); + if (capabilities.isEmpty()) { + return ""; + } + + var mainVersion = moduleDependency.getVersion(); + return "[" + + capabilities.stream().map(cap -> { + if (Objects.equals(mainVersion, cap.getVersion()) || cap.getVersion() == null) { + return cap.getGroup() + ":" + cap.getName(); + } else { + return cap.getGroup() + ":" + cap.getName() + ":" + cap.getVersion(); + } + }).collect(Collectors.joining(",")) + "]"; + } +} diff --git a/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java new file mode 100644 index 00000000..0ec01aee --- /dev/null +++ b/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java @@ -0,0 +1,155 @@ +package net.neoforged.moddevgradle.legacyforge; + +import net.neoforged.moddevgradle.dsl.NeoForgeExtension; +import net.neoforged.moddevgradle.internal.ModDevPlugin; +import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; +import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeExtension; +import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin; +import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LegacyModDevPluginTest { + private final Project project; + private final LegacyForgeExtension extension; + private final SourceSet mainSourceSet; + private final SourceSet testSourceSet; + + public LegacyModDevPluginTest() { + project = ProjectBuilder.builder().build(); + project.getPlugins().apply(LegacyForgeModDevPlugin.class); + + extension = ExtensionUtils.getExtension(project, "legacyForge", LegacyForgeExtension.class); + + var sourceSets = ExtensionUtils.getSourceSets(project); + mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + + // Set the Java version to the currently running Java to make it use that + var java = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); + java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.current()); + } + + @Test + void testModdingCannotBeEnabledTwice() { + extension.setVersion("1.2.3"); + var e = assertThrows(InvalidUserCodeException.class, () -> extension.setVersion("1.2.3")); + assertThat(e).hasMessage("You cannot enable modding in the same project twice."); + } + + @Test + void testEnableForTestSourceSetOnly() { + extension.enable(settings -> { + settings.setForgeVersion("1.2.3"); + settings.setEnabledSourceSets(Set.of(testSourceSet)); + }); + + // Both the compile and runtime classpath of the main source set had no dependencies added + assertNoDependencies(mainSourceSet.getCompileClasspathConfigurationName()); + assertNoDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()); + + // While the test classpath should have modding dependencies + assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); + } + + @Test + void testAddModdingDependenciesTo() { + extension.setVersion("1.2.3"); + + // Initially, only the main source set should have the dependencies + assertContainsModdingCompileDependencies("1.2.3", mainSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", mainSourceSet.getRuntimeClasspathConfigurationName()); + assertNoDependencies(testSourceSet.getCompileClasspathConfigurationName()); + assertNoDependencies(testSourceSet.getRuntimeClasspathConfigurationName()); + + // Now add it to the test source set too + extension.addModdingDependenciesTo(testSourceSet); + + assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); + assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); + } + + private void assertNoDependencies(String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .isEmpty(); + } + + private void assertContainsModdingCompileDependencies(String version, String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .containsOnly( + "build/moddev/artifacts/forge-" + version + ".jar", + "net.minecraftforge:forge:" + version + "[net.neoforged:neoforge-dependencies]" + ); + } + + private void assertContainsModdingRuntimeDependencies(String version, String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + + var dependentTasks = configuration.getBuildDependencies().getDependencies(null); + assertThat(dependentTasks) + .extracting(Task::getName) + .containsOnly("createMinecraftArtifacts"); + + assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency) + .containsOnly( + "build/moddev/artifacts/forge-" + version + ".jar", + "build/moddev/artifacts/client-extra-1.2.3.jar", + "build/moddev/artifacts/intermediateToNamed.zip", + "net.minecraftforge:forge:" + version + "[net.neoforged:neoforge-dependencies]" + ); + } + + private String describeDependency(Dependency dependency) { + if (dependency instanceof FileCollectionDependency fileCollectionDependency) { + return fileCollectionDependency.getFiles().getFiles() + .stream() + .map(f -> project.getProjectDir().toPath().relativize(f.toPath()).toString().replace('\\', '/')) + .collect(Collectors.joining(";")); + } else if (dependency instanceof ExternalModuleDependency moduleDependency) { + return moduleDependency.getGroup() + + ":" + moduleDependency.getName() + + ":" + moduleDependency.getVersion() + + formatCapabilities(moduleDependency); + } else { + return dependency.toString(); + } + } + + private String formatCapabilities(ExternalModuleDependency moduleDependency) { + var capabilities = moduleDependency.getRequestedCapabilities(); + if (capabilities.isEmpty()) { + return ""; + } + + var mainVersion = moduleDependency.getVersion(); + return "[" + + capabilities.stream().map(cap -> { + if (Objects.equals(mainVersion, cap.getVersion()) || cap.getVersion() == null) { + return cap.getGroup() + ":" + cap.getName(); + } else { + return cap.getGroup() + ":" + cap.getName() + ":" + cap.getVersion(); + } + }).collect(Collectors.joining(",")) + "]"; + } +} diff --git a/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java b/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java index f1403e49..f7716036 100644 --- a/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java +++ b/src/test/java/net/neoforged/moddevgradle/legacyforge/dsl/MixinMappingTest.java @@ -31,7 +31,7 @@ public void testMixinMappingsArePropagatedToObfuscationTasks() { var remapJarTask = (RemapJar) project.getTasks().getByName("reobfSomeJar"); // The main named->intermediary mappings for the game - var namedToIntermediary = project.getLayout().getBuildDirectory().file("moddev/namedToIntermediate.tsrg").get().getAsFile(); + var namedToIntermediary = project.getLayout().getBuildDirectory().file("moddev/artifacts/namedToIntermediate.tsrg").get().getAsFile(); var mixinApMappings = project.getLayout().getBuildDirectory().file("mixin/testmod.refmap.json.mappings.tsrg").get().getAsFile(); // Enable modding to actually wire up the tasks From 5a8d2080592988e3f857cf8794f378b42ee80732 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 23:37:12 +0100 Subject: [PATCH 23/25] Changed approach to passing modules * capabilities --- .../internal/LegacyForgeModDevPlugin.java | 45 ++++---------- .../internal/ModDevArtifactsWorkflow.java | 26 ++++---- .../moddevgradle/internal/ModDevPlugin.java | 50 ++++------------ .../internal/ModDevRunWorkflow.java | 19 +++--- .../internal/ModdingDependencies.java | 60 +++++++++++++++++++ .../internal/utils/VersionCapabilities.java | 13 +++- .../internal/ModDevPluginTest.java | 2 +- 7 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 976daf9a..47e9031a 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -4,6 +4,7 @@ import net.neoforged.moddevgradle.internal.ArtifactNamingStrategy; import net.neoforged.moddevgradle.internal.Branding; import net.neoforged.moddevgradle.internal.DataFileCollections; +import net.neoforged.moddevgradle.internal.ModdingDependencies; import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.LegacyForgeFacade; import net.neoforged.moddevgradle.internal.ModDevArtifactsWorkflow; @@ -118,13 +119,10 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF var neoForgeVersion = settings.getNeoForgeVersion(); var mcpVersion = settings.getMcpVersion(); - ModuleDependency platformModule = null; - ModuleDependency recompilableMinecraftWorkflowDependency = null; - String recompilableMinecraftDataDependencyNotation = null; - ModuleDependency modulePathDependency = null; - ModuleDependency runTypesDataDependency = null; - ModuleDependency librariesDependency; - String moddingPlatformDataDependencyNotation = null; + ModuleDependency neoForge = null; + ModuleDependency neoForm = null; + String neoFormNotation = null; + String neoForgeNotation = null; ArtifactNamingStrategy artifactNamingStrategy; VersionCapabilities versionCapabilities; if (forgeVersion != null || neoForgeVersion != null) { @@ -135,16 +133,8 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF String groupId = forgeVersion != null ? "net.minecraftforge" : "net.neoforged"; - platformModule = depFactory.create(groupId + ":forge:" + forgeVersion); - moddingPlatformDataDependencyNotation = groupId + ":forge:" + forgeVersion + ":userdev"; - runTypesDataDependency = platformModule.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); - modulePathDependency = platformModule.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) - // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. - .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); - librariesDependency = platformModule.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); + neoForge = depFactory.create(groupId + ":forge:" + forgeVersion); + neoForgeNotation = groupId + ":forge:" + forgeVersion + ":userdev"; var artifactPrefix = "forge-" + forgeVersion; // We have to ensure that client resources are named "client-extra" and *do not* contain forge- @@ -159,10 +149,8 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF versionCapabilities = VersionCapabilities.ofForgeVersion(forgeVersion); } else if (mcpVersion != null) { - recompilableMinecraftDataDependencyNotation = "de.oceanlabs.mcp:mcp_config:" + mcpVersion + "@zip"; - recompilableMinecraftWorkflowDependency = depFactory.create("de.oceanlabs.mcp:mcp_config:" + mcpVersion); - librariesDependency = recompilableMinecraftWorkflowDependency.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); + neoForm = depFactory.create("de.oceanlabs.mcp:mcp_config:" + mcpVersion); + neoFormNotation = "de.oceanlabs.mcp:mcp_config:" + mcpVersion + "@zip"; artifactNamingStrategy = ArtifactNamingStrategy.createDefault("vanilla-" + mcpVersion); versionCapabilities = VersionCapabilities.ofMinecraftVersion(mcpVersion); @@ -172,16 +160,14 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF var configurations = project.getConfigurations(); + var dependencies = ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities); + var artifacts = ModDevArtifactsWorkflow.create( project, settings.getEnabledSourceSets(), Branding.MDG, extension, - platformModule, - moddingPlatformDataDependencyNotation, - recompilableMinecraftWorkflowDependency, - recompilableMinecraftDataDependencyNotation, - librariesDependency, + dependencies, artifactNamingStrategy, configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), @@ -192,12 +178,7 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF project, Branding.MDG, artifacts, - modulePathDependency, - runTypesDataDependency, - null /* no support for test fixtures */, - librariesDependency, - extension.getRuns(), - versionCapabilities + extension.getRuns() ); // Configure the mixin and obfuscation extensions diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 9a938cbf..924427a7 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -41,6 +41,8 @@ @ApiStatus.Internal public record ModDevArtifactsWorkflow( Project project, + ModdingDependencies dependencies, + VersionCapabilities versionCapabilities, TaskProvider createArtifacts, TaskProvider downloadAssets, Configuration runtimeDependencies, @@ -63,11 +65,7 @@ public static ModDevArtifactsWorkflow create(Project project, Collection enabledSourceSets, Branding branding, ModDevExtension extension, - ModuleDependency moddingPlatformDependency, - String moddingPlatformDataDependencyNotation, - ModuleDependency recompilableMinecraftWorkflowDependency, - String recompilableMinecraftWorkflowDataDependencyNotation, - ModuleDependency gameLibrariesDependency, + ModdingDependencies moddingDependencies, ArtifactNamingStrategy artifactNamingStrategy, Configuration accessTransformers, Configuration interfaceInjectionData, @@ -85,8 +83,8 @@ public static ModDevArtifactsWorkflow create(Project project, var createManifestConfigurations = configureArtifactManifestConfigurations( project, - moddingPlatformDependency, - recompilableMinecraftWorkflowDependency + moddingDependencies.neoForgeDependency(), + moddingDependencies.neoFormDependency() ); var dependencyFactory = project.getDependencyFactory(); @@ -145,8 +143,8 @@ public static ModDevArtifactsWorkflow create(Project project, task.getSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); task.getResourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_RESOURCES)); - task.getNeoForgeArtifact().set(moddingPlatformDataDependencyNotation); - task.getNeoFormArtifact().set(recompilableMinecraftWorkflowDataDependencyNotation); + task.getNeoForgeArtifact().set(moddingDependencies.neoForgeDependencyNotation()); + task.getNeoFormArtifact().set(moddingDependencies.neoFormDependencyNotation()); task.getAdditionalResults().putAll(extension.getAdditionalMinecraftArtifacts()); }); ideIntegration.runTaskOnProjectSync(createArtifacts); @@ -161,8 +159,8 @@ public static ModDevArtifactsWorkflow create(Project project, task.addArtifactsToManifest(configuration); } task.getAssetPropertiesFile().set(modDevBuildDir.map(dir -> dir.file("minecraft_assets.properties"))); - task.getNeoForgeArtifact().set(moddingPlatformDataDependencyNotation); - task.getNeoFormArtifact().set(recompilableMinecraftWorkflowDataDependencyNotation); + task.getNeoForgeArtifact().set(moddingDependencies.neoForgeDependencyNotation()); + task.getNeoFormArtifact().set(moddingDependencies.neoFormDependencyNotation()); }); // For IntelliJ, we attach a combined sources+classes artifact which enables an "Attach Sources..." link for IJ users @@ -185,7 +183,7 @@ public static ModDevArtifactsWorkflow create(Project project, config.getDependencies().addLater(createArtifacts.map(task -> project.files(task.getResourcesArtifact())).map(dependencyFactory::create)); // Technically, the Minecraft dependencies do not strictly need to be on the classpath because they are pulled from the legacy class path. // However, we do it anyway because this matches production environments, and allows launch proxies such as DevLogin to use Minecraft's libraries. - config.getDependencies().add(gameLibrariesDependency); + config.getDependencies().add(moddingDependencies.gameLibrariesDependency()); }); // Configuration in which we place the required dependencies to develop mods for use in the compile-classpath. @@ -195,7 +193,7 @@ public static ModDevArtifactsWorkflow create(Project project, config.setCanBeResolved(false); config.setCanBeConsumed(false); config.getDependencies().addLater(minecraftClassesArtifact.map(dependencyFactory::create)); - config.getDependencies().add(gameLibrariesDependency); + config.getDependencies().add(moddingDependencies.gameLibrariesDependency()); }); // For IDEs that support it, link the source/binary artifacts if we use separated ones @@ -210,6 +208,8 @@ public static ModDevArtifactsWorkflow create(Project project, var result = new ModDevArtifactsWorkflow( project, + moddingDependencies, + versionCapabilities, createArtifacts, downloadAssets, runtimeDependencies, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index fd4d3a06..b2762073 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -60,43 +60,26 @@ public void enable( var dependencyFactory = project.getDependencyFactory(); - ModuleDependency neoForgeModule = null; - ModuleDependency modulePathDependency = null; - ModuleDependency runTypesDataDependency = null; - ModuleDependency testFixturesDependency = null; - String moddingPlatformDataDependencyNotation = null; + ModuleDependency neoForge = null; + String neoForgeNotation = null; if (neoForgeVersion != null) { - neoForgeModule = dependencyFactory.create("net.neoforged:neoforge:" + neoForgeVersion); - moddingPlatformDataDependencyNotation = "net.neoforged:neoforge:" + neoForgeVersion + ":userdev"; - runTypesDataDependency = neoForgeModule.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); - modulePathDependency = neoForgeModule.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) - // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. - .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); - testFixturesDependency = neoForgeModule.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-test-fixtures")); + neoForge = dependencyFactory.create("net.neoforged:neoforge:" + neoForgeVersion); + neoForgeNotation = "net.neoforged:neoforge:" + neoForgeVersion + ":userdev"; } - ModuleDependency neoFormModule = null; - String recompilableMinecraftWorkflowDataDependencyNotation = null; + ModuleDependency neoForm = null; + String neoFormNotation = null; if (neoFormVersion != null) { - neoFormModule = dependencyFactory.create("net.neoforged:neoform:" + neoFormVersion); - recompilableMinecraftWorkflowDataDependencyNotation = "net.neoforged:neoform:" + neoFormVersion + "@zip"; + neoForm = dependencyFactory.create("net.neoforged:neoform:" + neoFormVersion); + neoFormNotation = "net.neoforged:neoform:" + neoFormVersion + "@zip"; } // When a NeoForge version is specified, we use the dependencies published by that, and otherwise // we fall back to a potentially specified NeoForm version, which allows us to run in "Vanilla" mode. - ModuleDependency neoForgeModDevLibrariesDependency; ArtifactNamingStrategy artifactNamingStrategy; - if (neoForgeModule != null) { - neoForgeModDevLibrariesDependency = neoForgeModule.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); - + if (neoForge != null) { artifactNamingStrategy = ArtifactNamingStrategy.createDefault("neoforge-" + neoForgeVersion); } else { - neoForgeModDevLibrariesDependency = neoFormModule.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); artifactNamingStrategy = ArtifactNamingStrategy.createDefault("vanilla-" + neoFormVersion); } @@ -105,16 +88,14 @@ public void enable( var versionCapabilities = neoForgeVersion != null ? VersionCapabilities.ofNeoForgeVersion(neoForgeVersion) : VersionCapabilities.ofNeoFormVersion(neoFormVersion); + var dependencies = ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities); + var artifacts = ModDevArtifactsWorkflow.create( project, settings.getEnabledSourceSets(), Branding.MDG, extension, - neoForgeModule, - moddingPlatformDataDependencyNotation, - neoFormModule, - recompilableMinecraftWorkflowDataDependencyNotation, - neoForgeModDevLibrariesDependency, + dependencies, artifactNamingStrategy, configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), @@ -125,12 +106,7 @@ public void enable( project, Branding.MDG, artifacts, - modulePathDependency, - runTypesDataDependency, - testFixturesDependency, - neoForgeModDevLibrariesDependency, - extension.getRuns(), - versionCapabilities + extension.getRuns() ); } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 837179c5..ad63efa2 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -135,20 +135,19 @@ public static ModDevRunWorkflow get(Project project) { public static ModDevRunWorkflow create(Project project, Branding branding, ModDevArtifactsWorkflow artifactsWorkflow, - @Nullable ModuleDependency modulePathDependency, - @Nullable ModuleDependency runTypesConfigDependency, - @Nullable ModuleDependency testFixturesDependency, - ModuleDependency gameLibrariesDependency, - DomainObjectCollection runs, - VersionCapabilities versionCapabilites) { + DomainObjectCollection runs) { + + var dependencies = artifactsWorkflow.dependencies(); + var versionCapabilites = artifactsWorkflow.versionCapabilities(); + var workflow = new ModDevRunWorkflow( project, branding, artifactsWorkflow, - modulePathDependency, - runTypesConfigDependency, - testFixturesDependency, - gameLibrariesDependency, + dependencies.modulePathDependency(), + dependencies.runTypesConfigDependency(), + dependencies.testFixturesDependency(), + dependencies.gameLibrariesDependency(), runs, versionCapabilites ); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java b/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java new file mode 100644 index 00000000..6b6c223d --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java @@ -0,0 +1,60 @@ +package net.neoforged.moddevgradle.internal; + +import net.neoforged.moddevgradle.internal.utils.VersionCapabilities; +import org.gradle.api.artifacts.ModuleDependency; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public record ModdingDependencies( + @Nullable ModuleDependency neoForgeDependency, + @Nullable String neoForgeDependencyNotation, + @Nullable ModuleDependency neoFormDependency, + @Nullable String neoFormDependencyNotation, + ModuleDependency gameLibrariesDependency, + @Nullable ModuleDependency modulePathDependency, + @Nullable ModuleDependency runTypesConfigDependency, + @Nullable ModuleDependency testFixturesDependency +) { + + public static ModdingDependencies create(ModuleDependency neoForge, + String neoForgeNotation, + ModuleDependency neoForm, + String neoFormNotation, + VersionCapabilities versionCapabilities) { + + ModuleDependency modulePathDependency = null; + ModuleDependency runTypesDataDependency = null; + ModuleDependency librariesDependency; + if (neoForge != null) { + runTypesDataDependency = neoForge.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); + modulePathDependency = neoForge.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) + // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. + .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); + librariesDependency = neoForge.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); + } else { + librariesDependency = neoForm.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); + } + + ModuleDependency testFixturesDependency = null; + if (neoForge != null && versionCapabilities.testFixtures()) { + testFixturesDependency = neoForge.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-test-fixtures")); + } + + return new ModdingDependencies( + neoForge, + neoForgeNotation, + neoForm, + neoFormNotation, + librariesDependency, + modulePathDependency, + runTypesDataDependency, + testFixturesDependency + ); + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilities.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilities.java index f249e136..197900de 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilities.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilities.java @@ -11,16 +11,18 @@ * Models the changing capabilities of the modding platform and Vanilla, which we tie to the Minecraft version. * @param javaVersion Which Java version Vanilla uses to compile and run. * @param splitDataRuns Whether Vanilla has separate main classes for generating client and server data. + * @param testFixtures If the NeoForge version for this Minecraft version supports test fixtures. */ -public record VersionCapabilities(int javaVersion, boolean splitDataRuns) implements Serializable { +public record VersionCapabilities(int javaVersion, boolean splitDataRuns, boolean testFixtures) implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(VersionCapabilities.class); - private static final VersionCapabilities LATEST = new VersionCapabilities(21, true); + private static final VersionCapabilities LATEST = new VersionCapabilities(21, true, true); private static final Pattern NEOFORGE_PATTERN = Pattern.compile("^(\\d+\\.\\d+)\\.\\d+(|-.*)$"); private static final int MC_24W45A_INDEX = getReferenceVersionIndex("24w45a"); private static final int MC_24W14A_INDEX = getReferenceVersionIndex("24w14a"); + private static final int MC_1_20_4_INDEX = getReferenceVersionIndex("1.20.4"); private static final int MC_1_18_PRE2_INDEX = getReferenceVersionIndex("1.18-pre2"); private static final int MC_21W19A_INDEX = getReferenceVersionIndex("21w19a"); @@ -41,8 +43,9 @@ public static VersionCapabilities ofMinecraftVersion(String minecraftVersion) { public static VersionCapabilities ofVersionIndex(int versionIndex) { var javaVersion = getJavaVersion(versionIndex); var splitData = hasSplitDataEntrypoints(versionIndex); + var testFixtures = hasTestFixtures(versionIndex); - return new VersionCapabilities(javaVersion, splitData); + return new VersionCapabilities(javaVersion, splitData, testFixtures); } static int getJavaVersion(int versionIndex) { @@ -61,6 +64,10 @@ static boolean hasSplitDataEntrypoints(int versionIndex) { return versionIndex <= MC_24W45A_INDEX; } + static boolean hasTestFixtures(int versionIndex) { + return versionIndex <= MC_1_20_4_INDEX; + } + static int indexOfNeoForgeVersion(String version) { // NeoForge omits the "1." at the start of the Minecraft version and just adds an incrementing last digit var matcher = NEOFORGE_PATTERN.matcher(version); diff --git a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java index 2efbfb22..d9a80b3f 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java @@ -111,7 +111,7 @@ private void assertContainsModdingRuntimeDependencies(String version, String con .extracting(this::describeDependency) .containsOnly( "build/moddev/artifacts/neoforge-" + version + ".jar", - "build/moddev/artifacts/neoforge-" + version + "-client-extra.jar", + "build/moddev/artifacts/neoforge-" + version + "-client-extra-aka-minecraft-resources.jar", "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]" ); } From 8a00cf37fd05640e8711a319aa805ddebd048d13 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Fri, 27 Dec 2024 23:48:47 +0100 Subject: [PATCH 24/25] Docs --- BREAKING_CHANGES.md | 5 +++++ LEGACY.md | 26 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 64ca1b38..c891f342 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -6,6 +6,11 @@ please refer to the changelog, which can be found on the [project page](https:// The breaking changes in this major version should not affect most projects. Nonetheless, every single breaking change is documented here, along with a suggested fix. +- Modding now needs to be enabled before dependencies are made available, and the NeoForge/NeoForm versions will + be fixed at the point in time when modding is enabled. Setting `neoForge.version` or `neoForge.neoFormVersion` will + enable modding when those properties are set. For more advanced use cases, the `neoForge.enable { ... }` block can + be used, i.e. to not enable modding for the `main` source set. You can only enable modding once for one version + of NeoForge/NeoForm per project. - Changes to access transformer and interface injection data publishing. - `accessTransformers.publish` and `interfaceInjectionData.publish` syntax was changed. - `accessTransformers.published` and `interfaceInjectionData.published` were removed. diff --git a/LEGACY.md b/LEGACY.md index 655bb463..71b04784 100644 --- a/LEGACY.md +++ b/LEGACY.md @@ -13,9 +13,9 @@ plugins { id 'net.neoforged.moddev.legacyforge' version '2.0.28-beta' } -neoForge { +legacyForge { // Develop against MinecraftForge version 47.3.0 for 1.20.1 (the versions can be found at https://files.minecraftforge.net/) - version = "1.20.1-47.3.0" + forgeVersion = "1.20.1-47.3.0" // Validate AT files and raise errors when they have invalid targets // This option is false by default, but turning it on is recommended @@ -91,6 +91,18 @@ obfuscation { } ``` +## Vanilla Mode + +You can get dependencies for Vanilla Minecraft added to your project by using the `mcpVersion` property instead of +setting the `forgeVersion` property. + +```groovy +legacyForge { + // This adds Minecraft 1.20.1 as a dependency to the main source set. + mcpVersion = "1.20.1" +} +``` + ## Mixins You need to create so-called "refmaps" for Mixin, which convert the names you used to declare injection points and reference other parts of Minecraft code to the names used at runtime (SRG). @@ -126,10 +138,10 @@ jar { } ``` -## Effects of applying the legacy plugin -When applied, the legacy plugin will change the base NeoForm and NeoForge artifact coordinates of the `neoForge` extension to -`de.oceanlabs.mcp:mcp_config` and `net.minecraftforge:forge`. -It will also trigger the creation of various intermediary (SRG) to named (official) mapping files used by various parts of the toolchain, such as -mod reobfuscation and runtime naming services. +## Effects of enabling legacy forge modding + +Enabling modding in the legacyForge extension triggers the creation of various intermediary (SRG) to named (official) mapping files used by various parts of the toolchain, such as +mod reobfuscation and runtime naming services. + Reobfuscation to the intermediary mappings will automatically be configured for the `jar` task, the non-obfuscated jar will have a `-dev` classifier and will not be published in favour of the reobfuscated variant. From f3cdd3951df67d0c2acefca84ece65e100be7316 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sat, 28 Dec 2024 12:04:59 +0100 Subject: [PATCH 25/25] Slight refactor --- .../internal/LegacyForgeModDevPlugin.java | 25 +++--- .../moddevgradle/internal/ModDevPlugin.java | 5 +- .../internal/ModdingDependencies.java | 47 +++++----- .../AbstractProjectBuilderTest.java | 57 +++++++++++++ .../internal/ModDevPluginTest.java | 81 ++++++------------ .../legacyforge/LegacyModDevPluginTest.java | 85 ++++++------------- 6 files changed, 147 insertions(+), 153 deletions(-) create mode 100644 src/test/java/net/neoforged/moddevgradle/AbstractProjectBuilderTest.java diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index 47e9031a..138e57c9 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -21,7 +21,6 @@ import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.type.ArtifactTypeDefinition; import org.gradle.api.attributes.Attribute; import org.gradle.api.plugins.JavaLibraryPlugin; @@ -119,10 +118,7 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF var neoForgeVersion = settings.getNeoForgeVersion(); var mcpVersion = settings.getMcpVersion(); - ModuleDependency neoForge = null; - ModuleDependency neoForm = null; - String neoFormNotation = null; - String neoForgeNotation = null; + ModdingDependencies dependencies; ArtifactNamingStrategy artifactNamingStrategy; VersionCapabilities versionCapabilities; if (forgeVersion != null || neoForgeVersion != null) { @@ -131,11 +127,6 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF throw new InvalidUserCodeException("Specifying a Forge version is mutually exclusive with NeoForge or MCP"); } - String groupId = forgeVersion != null ? "net.minecraftforge" : "net.neoforged"; - - neoForge = depFactory.create(groupId + ":forge:" + forgeVersion); - neoForgeNotation = groupId + ":forge:" + forgeVersion + ":userdev"; - var artifactPrefix = "forge-" + forgeVersion; // We have to ensure that client resources are named "client-extra" and *do not* contain forge- // otherwise FML might pick up the client resources as the main Minecraft jar. @@ -148,20 +139,24 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF }; versionCapabilities = VersionCapabilities.ofForgeVersion(forgeVersion); + + String groupId = forgeVersion != null ? "net.minecraftforge" : "net.neoforged"; + var neoForge = depFactory.create(groupId + ":forge:" + forgeVersion); + var neoForgeNotation = groupId + ":forge:" + forgeVersion + ":userdev"; + dependencies = ModdingDependencies.create(neoForge, neoForgeNotation, null, null, versionCapabilities); } else if (mcpVersion != null) { - neoForm = depFactory.create("de.oceanlabs.mcp:mcp_config:" + mcpVersion); - neoFormNotation = "de.oceanlabs.mcp:mcp_config:" + mcpVersion + "@zip"; artifactNamingStrategy = ArtifactNamingStrategy.createDefault("vanilla-" + mcpVersion); - versionCapabilities = VersionCapabilities.ofMinecraftVersion(mcpVersion); + + var neoForm = depFactory.create("de.oceanlabs.mcp:mcp_config:" + mcpVersion); + var neoFormNotation = "de.oceanlabs.mcp:mcp_config:" + mcpVersion + "@zip"; + dependencies = ModdingDependencies.createVanillaOnly(neoForm, neoFormNotation); } else { throw new InvalidUserCodeException("You must specify a Forge, NeoForge or MCP version"); } var configurations = project.getConfigurations(); - var dependencies = ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities); - var artifacts = ModDevArtifactsWorkflow.create( project, settings.getEnabledSourceSets(), diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index b2762073..1977a86b 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -15,8 +15,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map; - /** * The main plugin class. */ @@ -88,7 +86,8 @@ public void enable( var versionCapabilities = neoForgeVersion != null ? VersionCapabilities.ofNeoForgeVersion(neoForgeVersion) : VersionCapabilities.ofNeoFormVersion(neoFormVersion); - var dependencies = ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities); + var dependencies = neoForge != null ? ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities) + : ModdingDependencies.createVanillaOnly(neoForm, neoFormNotation); var artifacts = ModDevArtifactsWorkflow.create( project, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java b/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java index 6b6c223d..424175d8 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModdingDependencies.java @@ -19,29 +19,20 @@ public record ModdingDependencies( public static ModdingDependencies create(ModuleDependency neoForge, String neoForgeNotation, - ModuleDependency neoForm, - String neoFormNotation, + @Nullable ModuleDependency neoForm, + @Nullable String neoFormNotation, VersionCapabilities versionCapabilities) { - - ModuleDependency modulePathDependency = null; - ModuleDependency runTypesDataDependency = null; - ModuleDependency librariesDependency; - if (neoForge != null) { - runTypesDataDependency = neoForge.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); - modulePathDependency = neoForge.copy() - .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) - // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. - .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); - librariesDependency = neoForge.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); - } else { - librariesDependency = neoForm.copy() - .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); - } + var runTypesDataDependency = neoForge.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-config")); + var modulePathDependency = neoForge.copy() + .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-module-path")) + // TODO: this is ugly; maybe make the configuration transitive in neoforge, or fix the SJH dep. + .exclude(Map.of("group", "org.jetbrains", "module", "annotations")); + var librariesDependency = neoForge.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoforge-dependencies")); ModuleDependency testFixturesDependency = null; - if (neoForge != null && versionCapabilities.testFixtures()) { + if (versionCapabilities.testFixtures()) { testFixturesDependency = neoForge.copy() .capabilities(caps -> caps.requireCapability("net.neoforged:neoforge-moddev-test-fixtures")); } @@ -57,4 +48,20 @@ public static ModdingDependencies create(ModuleDependency neoForge, testFixturesDependency ); } + + public static ModdingDependencies createVanillaOnly(ModuleDependency neoForm, String neoFormNotation) { + var librariesDependency = neoForm.copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies")); + + return new ModdingDependencies( + null, + null, + neoForm, + neoFormNotation, + librariesDependency, + null, + null, + null + ); + } } diff --git a/src/test/java/net/neoforged/moddevgradle/AbstractProjectBuilderTest.java b/src/test/java/net/neoforged/moddevgradle/AbstractProjectBuilderTest.java new file mode 100644 index 00000000..327585f7 --- /dev/null +++ b/src/test/java/net/neoforged/moddevgradle/AbstractProjectBuilderTest.java @@ -0,0 +1,57 @@ +package net.neoforged.moddevgradle; + +import org.assertj.core.api.AbstractListAssert; +import org.assertj.core.api.ObjectAssert; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.FileCollectionDependency; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class AbstractProjectBuilderTest { + protected Project project; + + protected final AbstractListAssert, String, ObjectAssert> assertThatDependencies(String configurationName) { + var configuration = project.getConfigurations().getByName(configurationName); + return assertThat(configuration.getAllDependencies()) + .extracting(this::describeDependency); + } + + protected final String describeDependency(Dependency dependency) { + if (dependency instanceof FileCollectionDependency fileCollectionDependency) { + return fileCollectionDependency.getFiles().getFiles() + .stream() + .map(f -> project.getProjectDir().toPath().relativize(f.toPath()).toString().replace('\\', '/')) + .collect(Collectors.joining(";")); + } else if (dependency instanceof ExternalModuleDependency moduleDependency) { + return moduleDependency.getGroup() + + ":" + moduleDependency.getName() + + ":" + moduleDependency.getVersion() + + formatCapabilities(moduleDependency); + } else { + return dependency.toString(); + } + } + + protected final String formatCapabilities(ExternalModuleDependency moduleDependency) { + var capabilities = moduleDependency.getRequestedCapabilities(); + if (capabilities.isEmpty()) { + return ""; + } + + var mainVersion = moduleDependency.getVersion(); + return "[" + + capabilities.stream().map(cap -> { + if (Objects.equals(mainVersion, cap.getVersion()) || cap.getVersion() == null) { + return cap.getGroup() + ":" + cap.getName(); + } else { + return cap.getGroup() + ":" + cap.getName() + ":" + cap.getVersion(); + } + }).collect(Collectors.joining(",")) + "]"; + } +} diff --git a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java index d9a80b3f..ad58c391 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java @@ -1,28 +1,23 @@ package net.neoforged.moddevgradle.internal; +import net.neoforged.moddevgradle.AbstractProjectBuilderTest; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Project; import org.gradle.api.Task; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; -import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -public class ModDevPluginTest { - private final Project project; +public class ModDevPluginTest extends AbstractProjectBuilderTest { private final NeoForgeExtension extension; private final SourceSet mainSourceSet; private final SourceSet testSourceSet; @@ -49,6 +44,23 @@ void testModdingCannotBeEnabledTwice() { assertThat(e).hasMessage("You cannot enable modding in the same project twice."); } + @Test + void testEnableVanillaOnlyMode() { + extension.setNeoFormVersion("1.2.3"); + + assertThatDependencies(mainSourceSet.getCompileClasspathConfigurationName()) + .containsOnly( + "build/moddev/artifacts/vanilla-1.2.3.jar", + "net.neoforged:neoform:1.2.3[net.neoforged:neoform-dependencies]" + ); + assertThatDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()) + .containsOnly( + "build/moddev/artifacts/vanilla-1.2.3.jar", + "build/moddev/artifacts/vanilla-1.2.3-client-extra-aka-minecraft-resources.jar", + "net.neoforged:neoform:1.2.3[net.neoforged:neoform-dependencies]" + ); + } + @Test void testEnableForTestSourceSetOnly() { extension.enable(settings -> { @@ -57,8 +69,8 @@ void testEnableForTestSourceSetOnly() { }); // Both the compile and runtime classpath of the main source set had no dependencies added - assertNoDependencies(mainSourceSet.getCompileClasspathConfigurationName()); - assertNoDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()); + assertThatDependencies(mainSourceSet.getCompileClasspathConfigurationName()).isEmpty(); + assertThatDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()).isEmpty(); // While the test classpath should have modding dependencies assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); @@ -72,8 +84,8 @@ void testAddModdingDependenciesTo() { // Initially, only the main source set should have the dependencies assertContainsModdingCompileDependencies("1.2.3", mainSourceSet.getCompileClasspathConfigurationName()); assertContainsModdingRuntimeDependencies("1.2.3", mainSourceSet.getRuntimeClasspathConfigurationName()); - assertNoDependencies(testSourceSet.getCompileClasspathConfigurationName()); - assertNoDependencies(testSourceSet.getRuntimeClasspathConfigurationName()); + assertThatDependencies(testSourceSet.getCompileClasspathConfigurationName()).isEmpty(); + assertThatDependencies(testSourceSet.getRuntimeClasspathConfigurationName()).isEmpty(); // Now add it to the test source set too extension.addModdingDependenciesTo(testSourceSet); @@ -82,17 +94,8 @@ void testAddModdingDependenciesTo() { assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); } - private void assertNoDependencies(String configurationName) { - var configuration = project.getConfigurations().getByName(configurationName); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) - .isEmpty(); - } - private void assertContainsModdingCompileDependencies(String version, String configurationName) { - var configuration = project.getConfigurations().getByName(configurationName); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) + assertThatDependencies(configurationName) .containsOnly( "build/moddev/artifacts/neoforge-" + version + ".jar", "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]" @@ -107,45 +110,11 @@ private void assertContainsModdingRuntimeDependencies(String version, String con .extracting(Task::getName) .containsOnly("createMinecraftArtifacts"); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) + assertThatDependencies(configurationName) .containsOnly( "build/moddev/artifacts/neoforge-" + version + ".jar", "build/moddev/artifacts/neoforge-" + version + "-client-extra-aka-minecraft-resources.jar", "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]" ); } - - private String describeDependency(Dependency dependency) { - if (dependency instanceof FileCollectionDependency fileCollectionDependency) { - return fileCollectionDependency.getFiles().getFiles() - .stream() - .map(f -> project.getProjectDir().toPath().relativize(f.toPath()).toString().replace('\\', '/')) - .collect(Collectors.joining(";")); - } else if (dependency instanceof ExternalModuleDependency moduleDependency) { - return moduleDependency.getGroup() - + ":" + moduleDependency.getName() - + ":" + moduleDependency.getVersion() - + formatCapabilities(moduleDependency); - } else { - return dependency.toString(); - } - } - - private String formatCapabilities(ExternalModuleDependency moduleDependency) { - var capabilities = moduleDependency.getRequestedCapabilities(); - if (capabilities.isEmpty()) { - return ""; - } - - var mainVersion = moduleDependency.getVersion(); - return "[" + - capabilities.stream().map(cap -> { - if (Objects.equals(mainVersion, cap.getVersion()) || cap.getVersion() == null) { - return cap.getGroup() + ":" + cap.getName(); - } else { - return cap.getGroup() + ":" + cap.getName() + ":" + cap.getVersion(); - } - }).collect(Collectors.joining(",")) + "]"; - } } diff --git a/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java index 0ec01aee..17e13eb8 100644 --- a/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java +++ b/src/test/java/net/neoforged/moddevgradle/legacyforge/LegacyModDevPluginTest.java @@ -1,31 +1,23 @@ package net.neoforged.moddevgradle.legacyforge; -import net.neoforged.moddevgradle.dsl.NeoForgeExtension; -import net.neoforged.moddevgradle.internal.ModDevPlugin; +import net.neoforged.moddevgradle.AbstractProjectBuilderTest; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.legacyforge.dsl.LegacyForgeExtension; import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin; import org.gradle.api.InvalidUserCodeException; -import org.gradle.api.Project; import org.gradle.api.Task; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; -import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -public class LegacyModDevPluginTest { - private final Project project; +public class LegacyModDevPluginTest extends AbstractProjectBuilderTest { private final LegacyForgeExtension extension; private final SourceSet mainSourceSet; private final SourceSet testSourceSet; @@ -52,6 +44,24 @@ void testModdingCannotBeEnabledTwice() { assertThat(e).hasMessage("You cannot enable modding in the same project twice."); } + @Test + void testEnableVanillaOnlyMode() { + extension.setMcpVersion("1.17.1"); + + assertThatDependencies(mainSourceSet.getCompileClasspathConfigurationName()) + .containsOnly( + "build/moddev/artifacts/vanilla-1.17.1.jar", + "de.oceanlabs.mcp:mcp_config:1.17.1[net.neoforged:neoform-dependencies]" + ); + assertThatDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()) + .containsOnly( + "build/moddev/artifacts/vanilla-1.17.1.jar", + "build/moddev/artifacts/vanilla-1.17.1-client-extra-aka-minecraft-resources.jar", + "de.oceanlabs.mcp:mcp_config:1.17.1[net.neoforged:neoform-dependencies]", + "build/moddev/artifacts/intermediateToNamed.zip" + ); + } + @Test void testEnableForTestSourceSetOnly() { extension.enable(settings -> { @@ -60,8 +70,8 @@ void testEnableForTestSourceSetOnly() { }); // Both the compile and runtime classpath of the main source set had no dependencies added - assertNoDependencies(mainSourceSet.getCompileClasspathConfigurationName()); - assertNoDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()); + assertThatDependencies(mainSourceSet.getCompileClasspathConfigurationName()).isEmpty(); + assertThatDependencies(mainSourceSet.getRuntimeClasspathConfigurationName()).isEmpty(); // While the test classpath should have modding dependencies assertContainsModdingCompileDependencies("1.2.3", testSourceSet.getCompileClasspathConfigurationName()); @@ -75,8 +85,8 @@ void testAddModdingDependenciesTo() { // Initially, only the main source set should have the dependencies assertContainsModdingCompileDependencies("1.2.3", mainSourceSet.getCompileClasspathConfigurationName()); assertContainsModdingRuntimeDependencies("1.2.3", mainSourceSet.getRuntimeClasspathConfigurationName()); - assertNoDependencies(testSourceSet.getCompileClasspathConfigurationName()); - assertNoDependencies(testSourceSet.getRuntimeClasspathConfigurationName()); + assertThatDependencies(testSourceSet.getCompileClasspathConfigurationName()).isEmpty(); + assertThatDependencies(testSourceSet.getRuntimeClasspathConfigurationName()).isEmpty(); // Now add it to the test source set too extension.addModdingDependenciesTo(testSourceSet); @@ -85,17 +95,8 @@ void testAddModdingDependenciesTo() { assertContainsModdingRuntimeDependencies("1.2.3", testSourceSet.getRuntimeClasspathConfigurationName()); } - private void assertNoDependencies(String configurationName) { - var configuration = project.getConfigurations().getByName(configurationName); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) - .isEmpty(); - } - private void assertContainsModdingCompileDependencies(String version, String configurationName) { - var configuration = project.getConfigurations().getByName(configurationName); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) + assertThatDependencies(configurationName) .containsOnly( "build/moddev/artifacts/forge-" + version + ".jar", "net.minecraftforge:forge:" + version + "[net.neoforged:neoforge-dependencies]" @@ -110,8 +111,7 @@ private void assertContainsModdingRuntimeDependencies(String version, String con .extracting(Task::getName) .containsOnly("createMinecraftArtifacts"); - assertThat(configuration.getAllDependencies()) - .extracting(this::describeDependency) + assertThatDependencies(configurationName) .containsOnly( "build/moddev/artifacts/forge-" + version + ".jar", "build/moddev/artifacts/client-extra-1.2.3.jar", @@ -119,37 +119,4 @@ private void assertContainsModdingRuntimeDependencies(String version, String con "net.minecraftforge:forge:" + version + "[net.neoforged:neoforge-dependencies]" ); } - - private String describeDependency(Dependency dependency) { - if (dependency instanceof FileCollectionDependency fileCollectionDependency) { - return fileCollectionDependency.getFiles().getFiles() - .stream() - .map(f -> project.getProjectDir().toPath().relativize(f.toPath()).toString().replace('\\', '/')) - .collect(Collectors.joining(";")); - } else if (dependency instanceof ExternalModuleDependency moduleDependency) { - return moduleDependency.getGroup() - + ":" + moduleDependency.getName() - + ":" + moduleDependency.getVersion() - + formatCapabilities(moduleDependency); - } else { - return dependency.toString(); - } - } - - private String formatCapabilities(ExternalModuleDependency moduleDependency) { - var capabilities = moduleDependency.getRequestedCapabilities(); - if (capabilities.isEmpty()) { - return ""; - } - - var mainVersion = moduleDependency.getVersion(); - return "[" + - capabilities.stream().map(cap -> { - if (Objects.equals(mainVersion, cap.getVersion()) || cap.getVersion() == null) { - return cap.getGroup() + ":" + cap.getName(); - } else { - return cap.getGroup() + ":" + cap.getName() + ":" + cap.getVersion(); - } - }).collect(Collectors.joining(",")) + "]"; - } }