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.
diff --git a/legacytest/build.gradle b/legacytest/build.gradle
index 95b0ef83..b537b4b9 100644
--- a/legacytest/build.gradle
+++ b/legacytest/build.gradle
@@ -12,8 +12,8 @@ java {
}
}
-neoForge {
- neoFormVersion = '1.19.2'
+legacyForge {
+ 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..aa0d0140
--- /dev/null
+++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeExtension.java
@@ -0,0 +1,61 @@
+package net.neoforged.moddevgradle.legacyforge.dsl;
+
+import net.neoforged.moddevgradle.dsl.DataFileCollection;
+import net.neoforged.moddevgradle.dsl.ModDevExtension;
+import net.neoforged.moddevgradle.legacyforge.internal.LegacyForgeModDevPlugin;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+
+import javax.inject.Inject;
+
+/**
+ * This is the top-level {@code legacyForge} extension, used to configure the moddev plugin.
+ */
+public abstract class LegacyForgeExtension extends ModDevExtension {
+ private final Project project;
+
+ @Inject
+ public LegacyForgeExtension(Project project,
+ DataFileCollection accessTransformers,
+ DataFileCollection interfaceInjectionData) {
+ super(project, accessTransformers, interfaceInjectionData);
+ this.project = project;
+ }
+
+ /**
+ * Enables modding for the main source-set using the given Forge version.
+ *
+ * Shorthand for:
+ *
+ * enable { forgeVersion = '...' }
+ *
+ */
+ public void setVersion(String version) {
+ enable(settings -> {
+ settings.setForgeVersion(version);
+ });
+ }
+
+ /**
+ * Enables modding for the main source-set in Vanilla-mode.
+ *
+ * Shorthand for:
+ *
+ * enable { mcpVersion = '...' }
+ *
+ */
+ public void setMcpVersion(String version) {
+ enable(settings -> {
+ settings.setMcpVersion(version);
+ });
+ }
+
+ public void enable(Action customizer) {
+ var plugin = project.getPlugins().getPlugin(LegacyForgeModDevPlugin.class);
+
+ var settings = project.getObjects().newInstance(LegacyForgeModdingSettings.class);
+ 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
new file mode 100644
index 00000000..a004c936
--- /dev/null
+++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java
@@ -0,0 +1,84 @@
+package net.neoforged.moddevgradle.legacyforge.dsl;
+
+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;
+
+ @Nullable
+ private String forgeVersion;
+
+ @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;
+ }
+
+ 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.
+ */
+
+ /**
+ * 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/dsl/Obfuscation.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/ObfuscationExtension.java
similarity index 86%
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 39a56f70..a27bb342 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;
@@ -12,7 +13,7 @@
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;
@@ -23,41 +24,53 @@
import javax.inject.Inject;
import java.util.List;
-public abstract class Obfuscation {
+public abstract class ObfuscationExtension {
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) {
+ public ObfuscationExtension(Project project,
+ Configuration autoRenamingToolRuntime,
+ Configuration installerToolsRuntime,
+ FileCollection extraMixinMappings) {
this.project = project;
- this.officialToSrg = officialToSrg;
- this.mappingsCsv = mappingsCsv;
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.enable()");
+ }));
+ }
+
+ /**
+ * Format is TSRG.
+ */
+ @ApiStatus.Internal
+ public abstract RegularFileProperty getNamedToSrgMappings();
+
+ /**
+ * Format is a ZIP file containing CSV files with mapping data.
+ */
+ @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(assertConfigured(getNamedToSrgMappings()));
}
@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(assertConfigured(getSrgToNamedMappings()));
}
/**
@@ -68,7 +81,8 @@ public void configureSrgToNamedOperation(RemapOperation operation) {
* @return a provider of the created task
*/
public TaskProvider reobfuscate(TaskProvider extends AbstractArchiveTask> jar, SourceSet sourceSet) {
- return reobfuscate(jar, sourceSet, ignored -> {});
+ return reobfuscate(jar, sourceSet, ignored -> {
+ });
}
/**
@@ -82,7 +96,6 @@ public TaskProvider reobfuscate(TaskProvider extends AbstractArchive
public TaskProvider reobfuscate(TaskProvider extends AbstractArchiveTask> 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 2b88b739..138e57c9 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,36 @@
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.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.ModDevPlugin;
+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.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.nfrtgradle.CreateMinecraftArtifacts;
+import net.neoforged.moddevgradle.legacyforge.dsl.ObfuscationExtension;
+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.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 +38,12 @@
@ApiStatus.Internal
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";
@@ -35,7 +51,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 +94,107 @@ public void apply(Project project) {
spec.getDependencies().add(depFactory.create("net.neoforged.installertools:installertools:2.1.10:fatjar"));
});
- // 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"));
-
// 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 = DataFileCollections.create(project);
+ project.getExtensions().create(
+ LEGACYFORGE_EXTENSION,
+ LegacyForgeExtension.class,
+ project,
+ dataFileCollections.accessTransformers().extension(),
+ dataFileCollections.interfaceInjectionData().extension()
+ );
+ }
+
+ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyForgeExtension extension) {
+ var depFactory = project.getDependencyFactory();
+
+ var forgeVersion = settings.getForgeVersion();
+ var neoForgeVersion = settings.getNeoForgeVersion();
+ var mcpVersion = settings.getMcpVersion();
+
+ ModdingDependencies dependencies;
+ 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");
+ }
+
+ 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);
+
+ 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) {
+ 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 artifacts = ModDevArtifactsWorkflow.create(
+ project,
+ settings.getEnabledSourceSets(),
+ Branding.MDG,
+ extension,
+ dependencies,
+ artifactNamingStrategy,
+ configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS),
+ configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA),
+ versionCapabilities
+ );
- 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);
+ var runs = ModDevRunWorkflow.create(
+ project,
+ Branding.MDG,
+ artifacts,
+ extension.getRuns()
+ );
- 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));
+ // Configure the mixin and obfuscation extensions
+ var mixin = ExtensionUtils.getExtension(project, MIXIN_EXTENSION, MixinExtension.class);
+ var obf = ExtensionUtils.getExtension(project, OBFUSCATION_EXTENSION, ObfuscationExtension.class);
- extension.getAdditionalMinecraftArtifacts().put("namedToIntermediaryMapping", namedToIntermediate.map(RegularFile::getAsFile));
- extension.getAdditionalMinecraftArtifacts().put("intermediaryToNamedMapping", intermediateToNamed.map(RegularFile::getAsFile));
- extension.getAdditionalMinecraftArtifacts().put("csvMapping", mappingsCsv.map(RegularFile::getAsFile));
+ // We use this directory to store intermediate files used during moddev
+ var namedToIntermediate = artifacts.requestAdditionalMinecraftArtifact("namedToIntermediaryMapping", "namedToIntermediate.tsrg");
+ obf.getNamedToSrgMappings().set(namedToIntermediate);
+ var intermediateToNamed = artifacts.requestAdditionalMinecraftArtifact("intermediaryToNamedMapping", "intermediateToNamed.srg");
+ var mappingsCsv = artifacts.requestAdditionalMinecraftArtifact("csvMapping", "intermediateToNamed.zip");
+ obf.getSrgToNamedMappings().set(mappingsCsv);
- 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 +205,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 +235,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, ObfuscationExtension 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/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java
new file mode 100644
index 00000000..5ccfee1b
--- /dev/null
+++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java
@@ -0,0 +1,161 @@
+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;
+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;
+import java.io.File;
+
+public abstract class ModDevExtension {
+ private final NamedDomainObjectContainer mods;
+ private final NamedDomainObjectContainer runs;
+ private final Parchment parchment;
+
+ private final Project project;
+ 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.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());
+ }
+
+ /**
+ * 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();
+
+ /**
+ * 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) {
+ 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
new file mode 100644
index 00000000..08c61f3c
--- /dev/null
+++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java
@@ -0,0 +1,64 @@
+package net.neoforged.moddevgradle.dsl;
+
+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;
+
+ @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;
+ }
+
+ public @Nullable String getNeoFormVersion() {
+ return neoFormVersion;
+ }
+
+ /**
+ * NeoForge version number. You have to set either this or {@link #setNeoFormVersion}.
+ */
+ public void setVersion(String version) {
+ this.version = 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 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 312d7a54..04984dfc 100644
--- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java
+++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java
@@ -3,175 +3,64 @@
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.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.
+ * Enables modding on the main source set with the given NeoForge version.
*
- * @see Access Transformer File Format
+ * Shorthand for:
+ *
+ * enable { version = '...' }
+ *
*/
- public void accessTransformers(Action action) {
- action.execute(accessTransformers);
- }
-
- public DataFileCollection getAccessTransformers() {
- return accessTransformers;
+ public void setVersion(String version) {
+ enable(settings -> {
+ settings.setVersion(version);
+ });
}
/**
- * 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.
+ * Enables the Vanilla-only mode of ModDevGradle.
*
- * @see Interface Injection Data Format
+ * Shorthand for:
+ *
+ * enable { neoFormVersion = '...' }
+ *
*/
- public void interfaceInjectionData(Action action) {
- action.execute(interfaceInjectionData);
+ public void setNeoFormVersion(String version) {
+ enable(settings -> {
+ settings.setNeoFormVersion(version);
+ });
}
- public DataFileCollection getInterfaceInjectionData() {
- return interfaceInjectionData;
- }
+ public void enable(Action customizer) {
+ var modDevPlugin = project.getPlugins().getPlugin(ModDevPlugin.class);
- /**
- * 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);
- }
+ var settings = project.getObjects().newInstance(ModdingVersionSettings.class);
+ customizer.execute(settings);
- 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.enable(project, settings, this);
}
public UnitTest getUnitTest() {
@@ -181,33 +70,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..5efcac01 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/Branding.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/Branding.java
@@ -6,7 +6,7 @@
* @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) {
+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/DataFileCollections.java b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollections.java
new file mode 100644
index 00000000..896ec937
--- /dev/null
+++ b/src/main/java/net/neoforged/moddevgradle/internal/DataFileCollections.java
@@ -0,0 +1,128 @@
+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;
+
+/**
+ * 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 record DataFileCollections(CollectionWrapper accessTransformers,
+ CollectionWrapper interfaceInjectionData) {
+ public static final String CONFIGURATION_ACCESS_TRANSFORMERS = "accessTransformers";
+
+ public static final String CONFIGURATION_INTERFACE_INJECTION_DATA = "interfaceInjectionData";
+
+ /**
+ * Constructs the default data file collections for access transformers and intrface injection data
+ * with sensible defaults.
+ */
+ public static DataFileCollections create(Project project) {
+ // Create an access transformer configuration
+ var accessTransformers = createCollection(
+ 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 = createCollection(
+ project,
+ CONFIGURATION_INTERFACE_INJECTION_DATA,
+ "Interface injection data adds extend/implements clauses for interfaces to Minecraft code at development time",
+ "interfaceinjection"
+ );
+
+ return new DataFileCollections(accessTransformers, interfaceInjectionData);
+ }
+
+ 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);
+ 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