diff --git a/.github/workflows/test-prs.yml b/.github/workflows/test-prs.yml index 3b458cce7..853eeb269 100644 --- a/.github/workflows/test-prs.yml +++ b/.github/workflows/test-prs.yml @@ -1,5 +1,5 @@ name: Test PRs -run-name: Tests for PR ${{ github.event.pull_request.number }} +run-name: Tests for ${{ github.event.pull_request.number || github.event.inputs.gradle-version }} on: pull_request: @@ -8,17 +8,40 @@ on: - opened - ready_for_review - reopened + workflow_dispatch: + inputs: + gradle-version: + description: 'Gradle version to use (required for manual run)' + required: true + default: '' concurrency: group: ci-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: + set-gradle-version: + name: Set Gradle Version + runs-on: ubuntu-latest + outputs: + gradle-version: ${{ steps.set-version.outputs.gradle-version }} + steps: + - name: Determine gradle version + id: set-version + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "gradle-version=default" >> "$GITHUB_OUTPUT" + else + echo "gradle-version=${{ github.event.inputs.gradle-version }}" >> "$GITHUB_OUTPUT" + fi + setup: name: Setup runs-on: ubuntu-latest + needs: set-gradle-version outputs: tests-to-run: ${{ steps.test.outputs.tests-to-run }} + gradle-version: ${{ needs.set-gradle-version.outputs.gradle-version }} steps: - name: Checkout repository uses: neoforged/actions/checkout@main @@ -42,10 +65,12 @@ jobs: #!/bin/bash ./.github/scripts/collect-tests.sh - build: name: Build runs-on: ubuntu-latest + needs: set-gradle-version + env: + GRADLE_VERSION: ${{ needs.set-gradle-version.outputs.gradle-version }} steps: - name: Checkout repository uses: neoforged/actions/checkout@main @@ -60,18 +85,20 @@ jobs: uses: gradle/gradle-build-action@v3 - name: Build - run: ./gradlew --info -s -x assemble + run: ./gradlew --info -s -x assemble -Ptraining-wheels.gradle-version=${{ env.GRADLE_VERSION }} test: name: "${{ matrix.test.displayName }} (${{ matrix.os }})" runs-on: "${{ matrix.os }}-latest" - needs: setup + needs: [setup, set-gradle-version] strategy: max-parallel: 15 fail-fast: false matrix: test: ${{ fromJSON(needs.setup.outputs.tests-to-run) }} os: [ubuntu, windows, macos] + env: + GRADLE_VERSION: ${{ needs.set-gradle-version.outputs.gradle-version }} steps: - name: Checkout repository uses: neoforged/actions/checkout@main @@ -87,11 +114,11 @@ jobs: - name: Test if: ${{ matrix.test.filter != null }} - run: ./gradlew --info -s ${{ matrix.test.path }} --tests "${{ matrix.test.filter }}" + run: ./gradlew --info -s ${{ matrix.test.path }} --tests "${{ matrix.test.filter }}" "-Ptraining-wheels.gradle-version=${{ env.GRADLE_VERSION }}" - name: Test if: ${{ matrix.test.filter == null }} - run: ./gradlew --info -s ${{ matrix.test.path }} + run: ./gradlew --info -s ${{ matrix.test.path }} "-Ptraining-wheels.gradle-version=${{ env.GRADLE_VERSION }}" # Always upload test results - name: Merge Test Reports @@ -123,7 +150,7 @@ jobs: run: | # We need to remove all invalid characters from the test name: # Invalid characters include: Double quote ", Colon :, Less than <, Greater than >, Vertical bar |, Asterisk *, Question mark ?, Carriage return \r, Line feed \n, Backslash \, Forward slash / - NAME=$(echo "${{ matrix.test.displayName }}" | tr '":<>|*?\\/' '-' | tr -d ' ') + NAME=$(echo "${{ matrix.test.displayName }}" | tr '":<>|*?\\/' '-' | tr -d ' ') # Check if the GITHUB_OUTPUT is set echo "Determined name to be $NAME-${{ matrix.os }}" if [ -z "$GITHUB_OUTPUT" ]; then @@ -191,4 +218,3 @@ jobs: with: script: | core.setFailed('Test build failure!') - diff --git a/README.md b/README.md index 2b935652b..42ddb9a05 100644 --- a/README.md +++ b/README.md @@ -486,17 +486,17 @@ subsystems { ## Advanced Settings -### Override Decompiler Settings +### Override Decompiler Settings The settings used by the decompiler when preparing Minecraft dependencies can be overridden using [Gradle properties](https://docs.gradle.org/current/userguide/project_properties.html). This can be useful to run NeoGradle on lower-end machines, at the cost of slower build times. -| Property | Description | -|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| -| `neogradle.subsystems.decompiler.maxMemory` | How much heap memory is given to the decompiler. Can be specified either in gigabyte (`4g`) or megabyte (`4096m`). | -| `neogradle.subsystems.decompiler.maxThreads` | By default the decompiler uses all available CPU cores. This setting can be used to limit it to a given number of threads. | -| `neogradle.subsystems.decompiler.logLevel` | Can be used to override the [decompiler loglevel](https://vineflower.org/usage/#cmdoption-log). | +| Property | Description | +|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `neogradle.subsystems.decompiler.maxMemory` | How much heap memory is given to the decompiler. Can be specified either in gigabyte (`4g`) or megabyte (`4096m`). | +| `neogradle.subsystems.decompiler.maxThreads` | By default the decompiler uses all available CPU cores. This setting can be used to limit it to a given number of threads, the lower the amount of threads the less memory is consumed. If set to 0 then the limit is disabled. | +| `neogradle.subsystems.decompiler.logLevel` | Can be used to override the [decompiler loglevel](https://vineflower.org/usage/#cmdoption-log). | ### Override Recompiler Settings diff --git a/build.gradle b/build.gradle index a2915bad9..97ab16781 100644 --- a/build.gradle +++ b/build.gradle @@ -228,6 +228,12 @@ subprojects.forEach { subProject -> classpath = evalSubProject.sourceSets.functionalTest.runtimeClasspath it.extensions.add('test-source-set', evalSubProject.sourceSets.functionalTest) + + if (evalSubProject.rootProject.getProperties().get("training-wheels.gradle-version") != null) + it.systemProperty( + "training-wheels.gradle-version", + evalSubProject.rootProject.getProperties().get("training-wheels.gradle-version") + ) } //Wire them up so they run as part of the check task (and as such through build, but not through test!) diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DecompilerExecute.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DecompilerExecute.java new file mode 100644 index 000000000..69147d219 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DecompilerExecute.java @@ -0,0 +1,112 @@ +package net.neoforged.gradle.common.runtime.tasks; + +import net.neoforged.gradle.common.extensions.problems.IProblemReporter; +import net.neoforged.gradle.dsl.common.tasks.Execute; +import org.gradle.api.GradleException; +import org.gradle.api.problems.Problems; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Internal; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +@CacheableTask +public abstract class DecompilerExecute extends DefaultExecute +{ + + private final OOMDetector detector = new OOMDetector(); + + @Override + public OutputStream createErrorOutputStream() + { + return new BifurcatingOutputStream( + super.createErrorOutputStream(), + new OOMDetectorStream(this.detector) + ); + } + + @Internal + public OOMDetector getDetector() + { + return detector; + } + + @Override + public void doExecute() throws Exception + { + try { + super.doExecute(); + } catch (Exception ex) { + detectError(false); + throw ex; + } + + detectError(true); + } + + private void detectError(boolean throwError) + { + if (getDetector().failed()) { + //We failed, so we can access the project now, to get the reporter + //We should not need to care about the config cache here, + getProject().getExtensions().getByType(IProblemReporter.class) + .reporting(problem -> { + problem.contextualLabel("decompiler") + .id("decompiler", "memory") + .details("The Decompiler could not successfully decompile Minecraft because it ran out of memory. Modify your runtime configuration and the decompiler subsystems configuration, and try again.") + .solution("Either increase the memory allowance for the decompiler, or reduce the concurrency on the decompiler to reduce memory consumption.") + .section("common-decompiler-settings"); + }, getLogger()); + + if (throwError) { + throw new GradleException("The decompiler failed to run. It ran out of memory."); + } + } + } + + public static final class OOMDetector { + private final StringBuilder resultBuilder = new StringBuilder(); + + private OOMDetector() { + } + + private void addLog(String log) { + resultBuilder.append(log); + } + + public boolean failed() { + return this.resultBuilder.toString().contains("java.lang.OutOfMemoryError"); + } + } + + public static final class OOMDetectorStream extends OutputStream { + private final OOMDetector detector; + private final ByteArrayOutputStream collectionDelegate = new ByteArrayOutputStream(); + + public OOMDetectorStream(final OOMDetector detector) {this.detector = detector;} + + @Override + public void write(final int b) throws IOException + { + collectionDelegate.write(b); + } + + @Override + public void flush() throws IOException + { + super.flush(); + collectionDelegate.flush(); + } + + @Override + public void close() throws IOException + { + super.close(); + collectionDelegate.close(); + + this.detector.addLog(collectionDelegate.toString()); + } + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/RecompileSourceJar.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/RecompileSourceJar.java index e1abb40ee..ea37400e6 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/RecompileSourceJar.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/RecompileSourceJar.java @@ -46,10 +46,6 @@ public abstract class RecompileSourceJar extends JavaCompile implements Runtime public RecompileSourceJar() { super(); - //We use a custom instance here that marks the sourcepath as an incremental field, allowing us to provide the compiler - //with all required elements directly while keeping incremental compile support for II. - ReflectionUtils.setFinalFieldUnchecked(this, "compileOptions", getObjectFactory().newInstance(RecompileOptions.class)); - arguments = getObjectFactory().newInstance(RuntimeArgumentsImpl.class, getProviderFactory()); multiArguments = getObjectFactory().newInstance(RuntimeMultiArgumentsImpl.class, getProviderFactory()); @@ -90,8 +86,8 @@ public RecompileSourceJar() { getOptions().setWarnings(false); getOptions().setVerbose(false); getOptions().setDeprecation(false); - getOptions().setIncremental(true); - getOptions().getIncrementalAfterFailure().set(true); + getOptions().setIncremental(false); + getOptions().getIncrementalAfterFailure().set(false); setSource(getCompileFileRoot()); @@ -230,25 +226,4 @@ public Provider getOutputAsTree() { return getOutput().map(it -> getArchiveOperations().zipTree(it)); } - - public static abstract class RecompileOptions extends CompileOptions { - - @Inject - public RecompileOptions(final ObjectFactory objectFactory) - { - super(objectFactory); - } - - @Incremental - @Optional - @IgnoreEmptyDirectories - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - @ToBeReplacedByLazyProperty - @Override - public @Nullable FileCollection getSourcepath() - { - return super.getSourcepath(); - } - } } diff --git a/common/src/main/java/net/neoforged/gradle/common/util/DependencyCollectorInjector.java b/common/src/main/java/net/neoforged/gradle/common/util/DependencyCollectorInjector.java new file mode 100644 index 000000000..fe08b8007 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/util/DependencyCollectorInjector.java @@ -0,0 +1,12 @@ +package net.neoforged.gradle.common.util; + +import org.gradle.api.artifacts.dsl.DependencyCollector; + +import javax.inject.Inject; + +public interface DependencyCollectorInjector +{ + + @Inject + public DependencyCollector dependencyCollector(); +} diff --git a/common/src/main/java/net/neoforged/gradle/common/util/ReflectionUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/ReflectionUtils.java index d31b7f45e..4163f9710 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/ReflectionUtils.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/ReflectionUtils.java @@ -1,6 +1,9 @@ package net.neoforged.gradle.common.util; -import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; public class ReflectionUtils { /** @@ -44,4 +47,25 @@ public static void setFinalFieldUnchecked(Object target, String fieldName, Objec throw new RuntimeException("Failed to set final field '" + fieldName + "'", e); } } + + public static void setFinalFieldUncheckedWithAlternatives(Object target, Object value, String... fieldNames) { + final List exceptions = new ArrayList<>(fieldNames.length); + for (final String fieldName : fieldNames) + { + try { + setFinalField(target, fieldName, value); + return; + } catch (ReflectiveOperationException e) { + exceptions.add(e); + } + } + + final RuntimeException ex = new RuntimeException("Failed to set final field '" + String.join(", ", fieldNames) + "'"); + for (final ReflectiveOperationException exception : exceptions) + { + ex.addSuppressed(exception); + } + + throw ex; + } } diff --git a/common/src/main/java/net/neoforged/gradle/common/util/ToolUtilities.java b/common/src/main/java/net/neoforged/gradle/common/util/ToolUtilities.java index 80d13be71..eec60e0bf 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/ToolUtilities.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/ToolUtilities.java @@ -5,18 +5,13 @@ import net.neoforged.gradle.util.ModuleDependencyUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.artifacts.dsl.Dependencies; import org.gradle.api.artifacts.dsl.DependencyCollector; -import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.result.ResolvedArtifactResult; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Provider; import java.io.File; -import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -41,7 +36,8 @@ public static Provider resolveTool(final Project project, final Provider> jvmArgs = applyVariableSubstitutions(getJvmArguments()) final Provider> programArgs = applyVariableSubstitutions(getRuntimeProgramArguments()) @@ -183,9 +187,10 @@ interface Execute extends WithWorkspace, WithOutput, WithJavaVersion, ExecuteSpe final Execute me = this - try (LoggerOutputStream error_out = new LoggerOutputStream(me.getLogger(), me.getLogLevel().get()) + try (OutputStream error_out = createErrorOutputStream() BufferedOutputStream log_out = new BufferedOutputStream(new FileOutputStream(consoleLogFile)) - LogLevelAwareOutputStream standard_out = new LogLevelAwareOutputStream(log_out, ExecuteSpecification.LogLevel.WARN, getLogLevel().get()) ){ + LogLevelAwareOutputStream standard_out = new LogLevelAwareOutputStream(log_out, ExecuteSpecification.LogLevel.WARN, getLogLevel().get()) + BifurcatingOutputStream wrapped_error_out = new BifurcatingOutputStream(error_out, log_out)){ getExecuteOperation().javaexec({ JavaExecSpec java -> PrintWriter writer = new PrintWriter(log_out) Function quote = s -> (CharSequence) ('"' + s + '"') @@ -215,7 +220,7 @@ interface Execute extends WithWorkspace, WithOutput, WithJavaVersion, ExecuteSpe java.setWorkingDir(me.getOutputDirectory().get()) java.getMainClass().set(mainClass) java.setStandardOutput(standard_out) - java.setErrorOutput(error_out) + java.setErrorOutput(wrapped_error_out) }).rethrowFailure().assertNormalExitValue() } } @@ -279,4 +284,35 @@ interface Execute extends WithWorkspace, WithOutput, WithJavaVersion, ExecuteSpe } } } + + protected static final class BifurcatingOutputStream + extends OutputStream { + + private final OutputStream[] streams; + + BifurcatingOutputStream(final OutputStream... streams) { + this.streams = streams + } + + @Override + void write(final int b) throws IOException { + for (final def stream in streams) { + stream.write(b) + } + } + + @Override + void flush() throws IOException { + for (final def stream in streams) { + stream.flush(); + } + } + + @Override + void close() throws IOException { + for (final def stream in streams) { + stream.close() + } + } + } } diff --git a/dsl/userdev/src/main/groovy/net/neoforged/gradle/dsl/userdev/configurations/UserdevProfile.groovy b/dsl/userdev/src/main/groovy/net/neoforged/gradle/dsl/userdev/configurations/UserdevProfile.groovy index c286d2b31..f58d2e3d3 100644 --- a/dsl/userdev/src/main/groovy/net/neoforged/gradle/dsl/userdev/configurations/UserdevProfile.groovy +++ b/dsl/userdev/src/main/groovy/net/neoforged/gradle/dsl/userdev/configurations/UserdevProfile.groovy @@ -7,6 +7,7 @@ import net.neoforged.gdi.annotations.DSLProperty import net.neoforged.gradle.dsl.common.runs.type.RunType import org.gradle.api.Action import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty @@ -101,13 +102,7 @@ abstract class UserdevProfile implements ConfigurableDSLElement @Nested @DSLProperty @Optional - abstract NamedDomainObjectCollection getRunTypes(); - - void runType(final String name, Action configurer) { - final RunType runType = factory.newInstance(RunType.class, name) - configurer.execute(runType) - runTypes.add(runType) - } + abstract NamedDomainObjectContainer getRunTypes(); @Input @DSLProperty diff --git a/gradle.properties b/gradle.properties index 73ed9270b..f3879c2e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,6 +36,6 @@ spock_version=2.1 spock_groovy_version=3.0 mockito_version=4.11.0 jimfs_version=1.2 -trainingwheels_version=2.0.0 +trainingwheels_version=2.0.2 githubCiTesting=true \ No newline at end of file diff --git a/neoform/src/functionalTest/groovy/net/neoforged/gradle/neoform/FunctionalTests.groovy b/neoform/src/functionalTest/groovy/net/neoforged/gradle/neoform/FunctionalTests.groovy index b50d67bbb..5a4293881 100644 --- a/neoform/src/functionalTest/groovy/net/neoforged/gradle/neoform/FunctionalTests.groovy +++ b/neoform/src/functionalTest/groovy/net/neoforged/gradle/neoform/FunctionalTests.groovy @@ -128,5 +128,9 @@ class FunctionalTests extends BuilderBasedTestSpecification { secondRun.task(':neoFormRecompile').outcome == TaskOutcome.FROM_CACHE } + @Override + protected File getTestTempDirectory() { + return new File("./decomp_oom") + } } diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java index 143d04882..e06e99638 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java @@ -5,6 +5,7 @@ import com.google.common.collect.Sets; import net.neoforged.gdi.ConfigurableDSLElement; import net.neoforged.gradle.common.runtime.extensions.CommonRuntimeExtension; +import net.neoforged.gradle.common.runtime.tasks.DecompilerExecute; import net.neoforged.gradle.common.runtime.tasks.DefaultExecute; import net.neoforged.gradle.common.runtime.tasks.ListLibraries; import net.neoforged.gradle.common.tasks.ArtifactFromOutput; @@ -184,7 +185,7 @@ private static TaskProvider createDecompile(NeoFormRuntimeSpe } decompilerArgs.add(0, "-log=" + logLevel); - return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), DefaultExecute.class, task -> { + return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), DecompilerExecute.class, task -> { task.getExecutingJar().set(ToolUtilities.resolveTool(task.getProject(), function.getVersion())); task.getJvmArguments().addAll(jvmArgs); task.getProgramArguments().addAll(decompilerArgs); diff --git a/test-utils/build.gradle b/test-utils/build.gradle index 4b234197a..80e987b26 100644 --- a/test-utils/build.gradle +++ b/test-utils/build.gradle @@ -12,6 +12,8 @@ dependencies.api "org.mockito:mockito-core:${project.mockito_version}" dependencies.api "org.mockito:mockito-inline:${project.mockito_version}" dependencies.api "net.neoforged.trainingwheels:base:${project.trainingwheels_version}" dependencies.api "net.neoforged.trainingwheels:gradle-base:${project.trainingwheels_version}" -dependencies.api "net.neoforged.trainingwheels:gradle-functional:${project.trainingwheels_version}" +dependencies.api ("net.neoforged.trainingwheels:gradle-functional:${project.trainingwheels_version}") { + exclude group: 'org.spockframework', module: 'spock-core' +} dependencies.implementation project(':common') \ No newline at end of file diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy index 699cb32d2..b86ca1f63 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy @@ -30,7 +30,6 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { it.withGlobalCacheDirectory(tempDir) it.enableLocalBuildCache() it.enableConfigurationCache() - it.enableBuildScan() }) when: diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java index e63df19cd..4b6e3d44e 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/dependency/UserDevDependencyManager.java @@ -1,6 +1,7 @@ package net.neoforged.gradle.userdev.dependency; import net.neoforged.gradle.common.util.ConfigurationUtils; +import net.neoforged.gradle.common.util.DependencyCollectorInjector; import net.neoforged.gradle.common.util.SourceSetUtils; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement; import net.neoforged.gradle.dsl.common.runs.run.RunManager; @@ -52,7 +53,8 @@ private void registerUnitTestDependencyMapping(Project project) { .map(parser::parse).collect(Collectors.toList())) .flatMap(TransformerUtils.combineAllLists(project, String.class, Function.identity())) .map(dependencyCoordinates -> { - final DependencyCollector collector = project.getObjects().dependencyCollector(); + final DependencyCollectorInjector injector = project.getObjects().newInstance(DependencyCollectorInjector.class); + final DependencyCollector collector = injector.dependencyCollector(); dependencyCoordinates.forEach(collector::add); return collector; }) diff --git a/utils/src/main/java/net/neoforged/gradle/util/FileUtils.java b/utils/src/main/java/net/neoforged/gradle/util/FileUtils.java index 81906dcde..717336513 100644 --- a/utils/src/main/java/net/neoforged/gradle/util/FileUtils.java +++ b/utils/src/main/java/net/neoforged/gradle/util/FileUtils.java @@ -165,7 +165,7 @@ public static T processFileFromZip(File zipArchivePath, String pathInArchive try (InputStream in = zipFile.getInputStream(entry)) { return processor.apply(in); } catch (Throwable e) { - throw new IOException("Failed to process file " + pathInArchive + " from " + zipArchivePath); + throw new IOException("Failed to process file " + pathInArchive + " from " + zipArchivePath, e); } } }