diff --git a/.github/workflows/build-prs.yml b/.github/workflows/build-prs.yml index 138a4107..d5a5bcbf 100644 --- a/.github/workflows/build-prs.yml +++ b/.github/workflows/build-prs.yml @@ -26,11 +26,15 @@ jobs: test-project: strategy: + max-parallel: 6 matrix: os: [ubuntu-latest, windows-latest] project: [testproject, legacytest] + recomp: ["recomp", "no recomp"] runs-on: ${{ matrix.os }} - name: Test ${{ matrix.project }} on ${{ matrix.os }} + name: Test ${{ matrix.project }} on ${{ matrix.os }} (${{ matrix.recomp }}) + env: + CI: ${{ matrix.recomp == 'no recomp' }} steps: - name: Checkout project sources uses: neoforged/actions/checkout@main diff --git a/LEGACY.md b/LEGACY.md index eb046132..e82c01fc 100644 --- a/LEGACY.md +++ b/LEGACY.md @@ -103,6 +103,19 @@ legacyForge { } ``` +## Disabling Decompilation and Recompilation +As of MDG Legacy 2.0.124, the decompilation/recompilation pipeline can be disabled, similarly to the regular plugin. +To disable it in CI, use the following: +```groovy +legacyForge { + enable { + forgeVersion = "..." // or mcpVersion = "..." if running in Vanilla mode + // Disable recompilation if the "CI" environment variable is set to true. It is automatically set by GitHub Actions. + disableRecompilation = System.getenv("CI") == "true" + } +} +``` + ## 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). diff --git a/README.md b/README.md index 5b771588..926193a4 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,30 @@ neoForge { } ``` +## Disabling Decompilation and Recompilation +By default, MDG will use the [NeoForm](https://github.com/neoforged/NeoForm) decompilation/recompilation pipeline to produce +Minecraft sources and a matching compiled game jar. This leads to a great debugging experience, at the cost of longer setup times. + +As of MDG 2.0.124, an alternative pipeline can be used, which will skip decompilation and recompilation entirely! +We recommend leaving recompilation on by default, but disabling it when running on CI such as GitHub Actions. + +To do so, replace: +```groovy +neoForge { + version = "..." // or neoFormVersion = "..." +} +``` +By: +```groovy +neoForge { + enable { + version = "..." // or neoFormVersion = "..." + // Disable recompilation if the "CI" environment variable is set to true. It is automatically set by GitHub Actions. + disableRecompilation = System.getenv("CI") == "true" + } +} +``` + ## Common Issues ### Clicking "Attach Sources" does nothing when viewing a Minecraft class (IntelliJ IDEA) diff --git a/legacytest/build.gradle b/legacytest/build.gradle index b537b4b9..28946026 100644 --- a/legacytest/build.gradle +++ b/legacytest/build.gradle @@ -13,7 +13,10 @@ java { } legacyForge { - mcpVersion = '1.19.2' + enable { + mcpVersion = '1.19.2' + disableRecompilation = System.getenv("CI") == "true" + } } publishing { diff --git a/legacytest/forge/build.gradle b/legacytest/forge/build.gradle index 5b116d2c..32279a17 100644 --- a/legacytest/forge/build.gradle +++ b/legacytest/forge/build.gradle @@ -4,7 +4,6 @@ plugins { } repositories { - mavenLocal() maven { name = "Jared's maven" url = "https://maven.blamejared.com/" @@ -33,7 +32,10 @@ dependencies { } legacyForge { - version = '1.20.1-47.3.12' + enable { + forgeVersion = '1.20.1-47.3.12' + disableRecompilation = System.getenv("CI") == "true" + } runs { client { client() diff --git a/legacytest/forge/src/main/java/mymod/MyMod.java b/legacytest/forge/src/main/java/mymod/MyMod.java index f7f1e519..b764da08 100644 --- a/legacytest/forge/src/main/java/mymod/MyMod.java +++ b/legacytest/forge/src/main/java/mymod/MyMod.java @@ -1,6 +1,10 @@ package mymod; import net.minecraft.DetectedVersion; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.EnderChestBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraftforge.fml.common.Mod; @Mod("mymod") @@ -9,6 +13,13 @@ public void run() { DetectedVersion.tryDetectVersion(); } + public static void doStuff() { + // Test our AT + ServerLevel.END_SPAWN_POINT = new BlockPos(1, 2, 3); + // Test a Forge AT (in a class that is not binpatched) + var block = new EnderChestBlock(BlockBehaviour.Properties.of()); + } + @javax.annotation.Nullable private static Object javaxNullableTest() { return null; diff --git a/legacytest/forge/src/main/resources/META-INF/accesstransformer.cfg b/legacytest/forge/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..749d29a0 --- /dev/null +++ b/legacytest/forge/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1 @@ +public-f net.minecraft.server.level.ServerLevel f_8562_ # END_SPAWN_POINT diff --git a/legacytest/forgedownstream/build.gradle b/legacytest/forgedownstream/build.gradle index 7d1ce560..3932a3b7 100644 --- a/legacytest/forgedownstream/build.gradle +++ b/legacytest/forgedownstream/build.gradle @@ -16,7 +16,10 @@ dependencies { } legacyForge { - version = '1.20.1-47.3.0' + enable { + forgeVersion = '1.20.1-47.3.0' + disableRecompilation = System.getenv("CI") == "true" + } } var copyJarJar = tasks.register('copyJarJar', Copy) { 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 0f567855..aeed2830 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/dsl/LegacyForgeModdingSettings.java @@ -20,6 +20,8 @@ public abstract class LegacyForgeModdingSettings { private Set enabledSourceSets = new HashSet<>(); + private boolean disableRecompilation = false; + private boolean obfuscateJar = true; @Inject @@ -78,6 +80,21 @@ public void setEnabledSourceSets(Set enabledSourceSets) { this.enabledSourceSets = enabledSourceSets; } + /** + * {@code true} if MDG should skip the NeoForm decompilation/recompilation pipeline, + * and instead apply transforms on the .class files and use binary patches. + * This leads to a faster setup since Minecraft doesn't need to be decompiled, + * however source files will not be available. + * {@code false} by default. + */ + public boolean isDisableRecompilation() { + return disableRecompilation; + } + + public void setDisableRecompilation(boolean disableRecompilation) { + this.disableRecompilation = disableRecompilation; + } + /** * {@return true if default reobfuscation task should be created} */ 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 d9663c21..a3f7ef17 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -153,7 +153,8 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF artifactNamingStrategy, configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), - versionCapabilities); + versionCapabilities, + settings.isDisableRecompilation()); var runs = ModDevRunWorkflow.create( project, diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java index bc36311b..e133055c 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java @@ -17,6 +17,8 @@ public abstract class ModdingVersionSettings { private Set enabledSourceSets = new HashSet<>(); + private boolean disableRecompilation = false; + @Inject public ModdingVersionSettings(Project project) { // By default, enable modding deps only for the main source set @@ -60,4 +62,19 @@ public Set getEnabledSourceSets() { public void setEnabledSourceSets(Set enabledSourceSets) { this.enabledSourceSets = enabledSourceSets; } + + /** + * {@code true} if MDG should skip the NeoForm decompilation/recompilation pipeline, + * and instead apply transforms on the .class files and use binary patches. + * This leads to a faster setup since Minecraft doesn't need to be decompiled, + * however source files will not be available. + * {@code false} by default. + */ + public boolean isDisableRecompilation() { + return disableRecompilation; + } + + public void setDisableRecompilation(boolean disableRecompilation) { + this.disableRecompilation = disableRecompilation; + } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index f5a14cc3..83020b86 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -67,7 +67,8 @@ public static ModDevArtifactsWorkflow create(Project project, ArtifactNamingStrategy artifactNamingStrategy, Configuration accessTransformers, Configuration interfaceInjectionData, - VersionCapabilitiesInternal versionCapabilities) { + VersionCapabilitiesInternal versionCapabilities, + boolean disableRecompilation) { if (project.getExtensions().findByName(EXTENSION_NAME) != null) { throw new InvalidUserCodeException("You cannot enable modding in the same project twice."); } @@ -141,10 +142,14 @@ public static ModDevArtifactsWorkflow create(Project project, Function> artifactPathStrategy = 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)); - task.getSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); + task.getGameJarArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); task.getResourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_RESOURCES)); + if (disableRecompilation) { + task.getDisableRecompilation().set(true); + } else { + task.getGameJarWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); + task.getGameSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); + } task.getNeoForgeArtifact().set(moddingDependencies.neoForgeDependencyNotation()); task.getNeoFormArtifact().set(moddingDependencies.neoFormDependencyNotation()); @@ -169,10 +174,10 @@ public static ModDevArtifactsWorkflow create(Project project, // 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 minecraftClassesDependency; - if (ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { - minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getCompiledWithSourcesArtifact())).map(dependencyFactory::create); + if (!disableRecompilation && ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameJarWithSourcesArtifact())).map(dependencyFactory::create); } else { - minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getCompiledArtifact())).map(dependencyFactory::create); + minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameJarArtifact())).map(dependencyFactory::create); } // Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath. @@ -200,11 +205,11 @@ public static ModDevArtifactsWorkflow create(Project project, }); // For IDEs that support it, link the source/binary artifacts if we use separated ones - if (!ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + if (!disableRecompilation && !ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { ideIntegration.attachSources( Map.of( - createArtifacts.get().getCompiledArtifact(), - createArtifacts.get().getSourcesArtifact())); + createArtifacts.get().getGameJarArtifact(), + createArtifacts.get().getGameSourcesArtifact())); } var result = new ModDevArtifactsWorkflow( diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 26a7e3dd..0489e94c 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -95,7 +95,8 @@ public void enable( artifactNamingStrategy, configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), - versionCapabilities); + versionCapabilities, + settings.isDisableRecompilation()); ModDevRunWorkflow.create( project, diff --git a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java index 6ffc53ee..7f753c51 100644 --- a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java +++ b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java @@ -40,6 +40,7 @@ public CreateMinecraftArtifacts() { getEnableCache().convention(true); getUseEclipseCompiler().convention(false); getAnalyzeCacheMisses().convention(false); + getDisableRecompilation().convention(false); getValidateAccessTransformers().convention(false); getParchmentEnabled().convention(false); } @@ -161,29 +162,37 @@ public CreateMinecraftArtifacts() { public abstract Property getUseEclipseCompiler(); /** - * This retrieves the result of the NeoForm process that produces a compiled Minecraft jar that includes + * Set to {@code true} to produce a {@link #getGameJarArtifact()} without the NeoForm decompile+recompile workflow. + * Rather it uses the original artifacts with binary patches applied (for NeoForge) or just remapped (for NeoForm only mode). + * In this mode {@link #getGameSourcesArtifact()} and {@link #getGameJarWithSourcesArtifact()} may not be requested. + */ + @Input + public abstract Property getDisableRecompilation(); + + /** + * This retrieves the result of the NeoForm process that produces a Minecraft jar that includes * the Minecraft sources as well. * This is useful for working around IntelliJ limitations related to not being able to attach sources as a * separate artifact automatically. */ @OutputFile @Optional - public abstract RegularFileProperty getCompiledWithSourcesArtifact(); + public abstract RegularFileProperty getGameJarWithSourcesArtifact(); /** - * This retrieves the same as {@link #getCompiledWithSourcesArtifact()}, but doesn't include the sources in the + * This retrieves the same as {@link #getGameJarWithSourcesArtifact()}, but doesn't include the sources in the * Jar file. */ @OutputFile @Optional - public abstract RegularFileProperty getCompiledArtifact(); + public abstract RegularFileProperty getGameJarArtifact(); /** - * This retrieves a Zip-File containing the sources used to compile {@link #getCompiledArtifact()}. + * This retrieves a Zip-File containing the sources used to compile {@link #getGameJarArtifact()}. */ @OutputFile @Optional - public abstract RegularFileProperty getSourcesArtifact(); + public abstract RegularFileProperty getGameSourcesArtifact(); /** * Also known as "client-extra". Contains the non-class files from the original Minecraft jar (excluding META-INF). @@ -192,6 +201,36 @@ public CreateMinecraftArtifacts() { @Optional public abstract RegularFileProperty getResourcesArtifact(); + /** + * @deprecated Use {@link #getGameJarWithSourcesArtifact()} instead. + */ + @Deprecated + @OutputFile + @Optional + public RegularFileProperty getCompiledWithSourcesArtifact() { + return getGameJarWithSourcesArtifact(); + } + + /** + * @deprecated Use {@link #getGameJarArtifact()} instead. + */ + @Deprecated + @OutputFile + @Optional + public RegularFileProperty getCompiledArtifact() { + return getGameJarArtifact(); + } + + /** + * @deprecated Use {@link #getGameSourcesArtifact()} instead. + */ + @Deprecated + @OutputFile + @Optional + public RegularFileProperty getSourcesArtifact() { + return getGameSourcesArtifact(); + } + @Inject protected abstract Problems getProblems(); @@ -280,26 +319,39 @@ public void createArtifacts() { requestedResults.add(new RequestedResult("clientResources", getResourcesArtifact().get().getAsFile())); } - // NOTE: When we use NeoForm standalone, the result-ids also change, a.k.a. "Vanilla Mode" - if (getNeoForgeArtifact().isPresent()) { - if (getCompiledArtifact().isPresent()) { - requestedResults.add(new RequestedResult("compiledWithNeoForge", getCompiledArtifact().get().getAsFile())); + if (getDisableRecompilation().get()) { + if (getGameJarArtifact().isPresent()) { + if (getNeoForgeArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameJarNoRecompWithNeoForge", getGameJarArtifact().get().getAsFile())); + } else { + requestedResults.add(new RequestedResult("gameJarNoRecomp", getGameJarArtifact().get().getAsFile())); + } + } + if (getGameSourcesArtifact().isPresent()) { + throw new IllegalArgumentException("Cannot request game sources if recompilation is disabled."); + } + if (getGameJarWithSourcesArtifact().isPresent()) { + throw new IllegalArgumentException("Cannot request game jar with sources if recompilation is disabled."); + } + } else if (getNeoForgeArtifact().isPresent()) { + if (getGameJarArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameJarWithNeoForge", getGameJarArtifact().get().getAsFile())); } - if (getSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("sourcesWithNeoForge", getSourcesArtifact().get().getAsFile())); + if (getGameSourcesArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameSourcesWithNeoForge", getGameSourcesArtifact().get().getAsFile())); } - if (getCompiledWithSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("sourcesAndCompiledWithNeoForge", getCompiledWithSourcesArtifact().get().getAsFile())); + if (getGameJarWithSourcesArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameJarWithSourcesAndNeoForge", getGameJarWithSourcesArtifact().get().getAsFile())); } } else { - if (getCompiledArtifact().isPresent()) { - requestedResults.add(new RequestedResult("compiled", getCompiledArtifact().get().getAsFile())); + if (getGameJarArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameJar", getGameJarArtifact().get().getAsFile())); } - if (getSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("sources", getSourcesArtifact().get().getAsFile())); + if (getGameSourcesArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameSources", getGameSourcesArtifact().get().getAsFile())); } - if (getCompiledWithSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("sourcesAndCompiled", getCompiledWithSourcesArtifact().get().getAsFile())); + if (getGameJarWithSourcesArtifact().isPresent()) { + requestedResults.add(new RequestedResult("gameJarWithSources", getGameJarWithSourcesArtifact().get().getAsFile())); } } diff --git a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeExtension.java b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeExtension.java index d51d830d..0b1a5a4f 100644 --- a/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeExtension.java +++ b/src/main/java/net/neoforged/nfrtgradle/NeoFormRuntimeExtension.java @@ -12,7 +12,7 @@ public abstract class NeoFormRuntimeExtension { public static final String NAME = "neoFormRuntime"; - private static final String DEFAULT_NFRT_VERSION = "1.0.44"; + private static final String DEFAULT_NFRT_VERSION = "2.0.5"; @Inject public NeoFormRuntimeExtension(Project project) { diff --git a/testproject/build.gradle b/testproject/build.gradle index fddd51e9..cb1ccc56 100644 --- a/testproject/build.gradle +++ b/testproject/build.gradle @@ -30,7 +30,10 @@ test { } neoForge { - version = project.neoforge_version + enable { + version = project.neoforge_version + disableRecompilation = System.getenv("CI") == "true" + } addModdingDependenciesTo(sourceSets.api) validateAccessTransformers = true diff --git a/testproject/common/build.gradle b/testproject/common/build.gradle index 770b1de3..61f3c88a 100644 --- a/testproject/common/build.gradle +++ b/testproject/common/build.gradle @@ -7,7 +7,10 @@ plugins { } neoForge { - neoFormVersion = "1.21-20240613.152323" + enable { + neoFormVersion = "1.21-20240613.152323" + disableRecompilation = System.getenv("CI") == "true" + } accessTransformers { validateAccessTransformers = true diff --git a/testproject/jijtest/build.gradle b/testproject/jijtest/build.gradle index 028146e3..9d634663 100644 --- a/testproject/jijtest/build.gradle +++ b/testproject/jijtest/build.gradle @@ -40,7 +40,10 @@ test { } neoForge { - version = project.neoforge_version + enable { + version = project.neoforge_version + disableRecompilation = System.getenv("CI") == "true" + } runs { data { diff --git a/testproject/subproject/build.gradle b/testproject/subproject/build.gradle index 5a178677..71d7b239 100644 --- a/testproject/subproject/build.gradle +++ b/testproject/subproject/build.gradle @@ -5,7 +5,10 @@ plugins { } neoForge { - version = project.neoforge_version + enable { + version = project.neoforge_version + disableRecompilation = System.getenv("CI") == "true" + } interfaceInjectionData { from(project.file('interfaces.json'))