From 4c763732a536fd37aae1fadf828c88cb9be6ba7e Mon Sep 17 00:00:00 2001 From: Marc Hermans Date: Mon, 24 Jun 2024 16:14:14 +0200 Subject: [PATCH] [Fix]: Feature/primary sourceset selector (#216) --- .../common/caching/CentralCacheService.java | 106 +----------------- .../common/runs/run/RunSourceSetsImpl.java | 10 ++ .../gradle/common/runs/tasks/RunExec.java | 55 --------- .../gradle/common/util/ClasspathUtils.java | 36 ++++++ .../gradle/common/util/run/RunsUtil.java | 56 ++++++++- .../dsl/common/runs/run/RunSourceSets.groovy | 17 +++ .../gradle/userdev/CentralCacheTests.groovy | 49 +------- .../userdev/ConfigurationCacheTests.groovy | 53 ++++++++- .../gradle/userdev/MultiProjectTests.groovy | 18 +-- .../neoforged/gradle/userdev/RunTests.groovy | 5 +- 10 files changed, 181 insertions(+), 224 deletions(-) delete mode 100644 common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunExec.java create mode 100644 common/src/main/java/net/neoforged/gradle/common/util/ClasspathUtils.java diff --git a/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java b/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java index 4cf0350de..1599f57aa 100644 --- a/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java +++ b/common/src/main/java/net/neoforged/gradle/common/caching/CentralCacheService.java @@ -156,7 +156,7 @@ private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File loc // Acquiring an exclusive lock on the file debugLog(task, "Acquiring lock on file: " + lockFile.getAbsolutePath()); - try(FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) { + try (FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) { try { fileBasedLock.updateAccessTime(); debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath()); @@ -188,7 +188,7 @@ private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File loc // Acquiring an exclusive lock on the file debugLog(task, "Acquiring lock on file: " + lockFile.getAbsolutePath()); - try(FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) { + try (FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) { try { fileBasedLock.updateAccessTime(); debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath()); @@ -319,13 +319,13 @@ private void logCacheMiss(Task task) { private void debugLog(Task task, String message) { if (getParameters().getDebugCache().get()) { - task.getLogger().lifecycle( " > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message); + task.getLogger().lifecycle(" > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message); } } private void debugLog(Task task, String message, Exception e) { if (getParameters().getDebugCache().get()) { - task.getLogger().lifecycle( " > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message, e); + task.getLogger().lifecycle(" > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message, e); } } @@ -374,7 +374,7 @@ public void hash(TaskInputs inputs) throws IOException { }); for (File file : inputs.getFiles()) { - try(Stream pathStream = Files.walk(file.toPath())) { + try (Stream pathStream = Files.walk(file.toPath())) { for (Path path : pathStream.filter(Files::isRegularFile).toList()) { debugLog(task, "Hashing task input file: " + path.toAbsolutePath()); hasher.putString(path.getFileName().toString()); @@ -403,11 +403,7 @@ private interface FileBasedLock extends AutoCloseable { private final class LockManager { public FileBasedLock createLock(Task task, File lockFile) { - if (WindowsFileBasedLock.isSupported()) { - return new WindowsFileBasedLock(task, lockFile); - } else { - return new IOControlledFileBasedLock(task, lockFile); - } + return new IOControlledFileBasedLock(task, lockFile); } } @@ -444,96 +440,6 @@ public void close() throws Exception { } } - private final class WindowsFileBasedLock extends HealthFileUsingFileBasedLock { - - private static boolean isSupported() { - return SystemUtils.IS_OS_WINDOWS; - } - - private final Task task; - private final File lockFile; - private final NioBasedFileLock nioBasedFileLock; - - private WindowsFileBasedLock(Task task, File lockFile) { - super(new File(lockFile.getParentFile(), HEALTHY_FILE_NAME)); - - if (!isSupported() || !NioBasedFileLock.isSupported()) { - throw new UnsupportedOperationException("Windows file locks are not supported on this platform, or NIO based locking is not supported!"); - } - - this.task = task; - this.lockFile = lockFile; - this.nioBasedFileLock = new NioBasedFileLock(task, lockFile); - } - - @Override - public void updateAccessTime() { - if (!lockFile.setLastModified(System.currentTimeMillis())) { - throw new RuntimeException("Failed to update access time for lock file: " + lockFile.getAbsolutePath()); - } - - debugLog(task, "Updated access time for lock file: " + lockFile.getAbsolutePath()); - } - - @Override - public void close() throws Exception { - //Close the super first, this ensures that the healthy file is created only if the lock was successful - super.close(); - this.nioBasedFileLock.close(); - debugLog(task, "Lock file closed: " + lockFile.getAbsolutePath()); - } - } - - private final class NioBasedFileLock implements AutoCloseable { - - public static boolean isSupported() { - return SystemUtils.IS_OS_WINDOWS; - } - - private static final Map FILE_LOCKS = new ConcurrentHashMap<>(); - - private final Task task; - private final File lockFile; - private final RandomAccessFile lockFileAccess; - private final FileChannel fileChannel; - private final FileLock fileLock; - - public NioBasedFileLock(Task task, File lockFile) { - if (!isSupported()) { - throw new UnsupportedOperationException("NIO file locks are not supported on this platform"); - } - - this.task = task; - this.lockFile = lockFile; - - try { - this.lockFileAccess = new RandomAccessFile(lockFile, "rw"); - this.fileChannel = this.lockFileAccess.getChannel(); - this.fileLock = this.fileChannel.lock(); - - final OwnerAwareReentrantLock lock = FILE_LOCKS.computeIfAbsent(lockFile.getAbsolutePath(), s1 -> new OwnerAwareReentrantLock()); - debugLog(task, "Created local thread lock for thread: " + Thread.currentThread().getId() + " - " + Thread.currentThread().getName()); - lock.lock(); - - debugLog(task, "Acquired lock on file: " + lockFile.getAbsolutePath()); - } catch (IOException e) { - throw new RuntimeException("Failed to acquire lock on file: " + lockFile.getAbsolutePath(), e); - } - } - - @Override - public void close() throws Exception { - fileLock.release(); - fileChannel.close(); - lockFileAccess.close(); - - final OwnerAwareReentrantLock lock = FILE_LOCKS.get(lockFile.getAbsolutePath()); - lock.unlock(); - - debugLog(task, "Released lock on file: " + lockFile.getAbsolutePath()); - } - } - private final class IOControlledFileBasedLock extends HealthFileUsingFileBasedLock { private final Task task; diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java index 11611fc31..414fd0c22 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java +++ b/common/src/main/java/net/neoforged/gradle/common/runs/run/RunSourceSetsImpl.java @@ -2,10 +2,14 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import net.minecraftforge.gdi.annotations.DSLProperty; import net.neoforged.gradle.common.util.SourceSetUtils; import net.neoforged.gradle.dsl.common.runs.run.RunSourceSets; import org.gradle.api.Project; +import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SourceSet; import javax.inject.Inject; @@ -79,6 +83,12 @@ public void add(String groupId, SourceSet... sourceSets) { } } + @DSLProperty + @Input + @Optional + @Override + public abstract Property getPrimary(); + @Override public Provider> all() { return this.provider; diff --git a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunExec.java b/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunExec.java deleted file mode 100644 index 434aa2e11..000000000 --- a/common/src/main/java/net/neoforged/gradle/common/runs/tasks/RunExec.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.neoforged.gradle.common.runs.tasks; - -import net.neoforged.gradle.common.runs.run.RunImpl; -import net.neoforged.gradle.dsl.common.runs.run.Run; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.JavaExec; -import org.gradle.api.tasks.Nested; -import org.gradle.api.tasks.SourceSet; -import org.gradle.jvm.toolchain.JavaToolchainService; -import org.gradle.work.DisableCachingByDefault; - -@DisableCachingByDefault(because = "This runs a game. It should not be cached.") -public abstract class RunExec extends JavaExec { - - public static final String GROUP = "NeoGradle/Runs"; - - public RunExec() { - super(); - - setGroup(GROUP); - - getMainClass().convention(getRun().flatMap(Run::getMainClass)); - - doNotTrackState("This is a runnable task, which has no output."); - - JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class); - getJavaLauncher().convention(service.launcherFor(getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain())); - } - - @Override - public void exec() { - final RunImpl run = (RunImpl) getRun().get(); - - setWorkingDir(run.getWorkingDirectory().get().getAsFile()); - args(run.getProgramArguments().get()); - jvmArgs(run.getJvmArguments().get()); - - environment(run.getEnvironmentVariables().get()); - systemProperties(run.getSystemProperties().get()); - - run.getModSources().all().get().values().stream() - .map(SourceSet::getRuntimeClasspath) - .forEach(this::classpath); - - classpath(run.getClasspath()); - - classpath(run.getDependencies().get().getRuntimeConfiguration()); - - super.exec(); - } - - @Nested - public abstract Property getRun(); -} diff --git a/common/src/main/java/net/neoforged/gradle/common/util/ClasspathUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/ClasspathUtils.java new file mode 100644 index 000000000..71e266cab --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/util/ClasspathUtils.java @@ -0,0 +1,36 @@ +package net.neoforged.gradle.common.util; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +/** + * Utility class for dealing with classpaths and their entries. + */ +public class ClasspathUtils { + + private ClasspathUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated."); + } + + public static boolean isClasspathEntry(File entry) { + return entry.getName().endsWith(".jar") || entry.getName().endsWith(".zip"); + } + + public static boolean isMinecraftClasspathEntry(File entry) { + if (!isClasspathEntry(entry)) { + return false; + } + + //Check if the file contains the class: + //net.minecraft.client.Minecraft + //This is a class that is always present in the Minecraft jar. + //This same heuristic is used by FML: https://github.com/neoforged/FancyModLoader/blob/89a32f970ba9137cb74e681af9cdaed0fc3e2085/loader/src/main/java/net/neoforged/fml/loading/targets/CommonUserdevLaunchHandler.java#L24 + try(final ZipFile zipFile = new ZipFile(entry)) { + return zipFile.getEntry("net/minecraft/client/Minecraft.class") != null; + } catch (IOException ignored) { + return false; + } + + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java b/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java index 3a34f4230..563d067c2 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/run/RunsUtil.java @@ -3,7 +3,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import net.neoforged.gradle.common.runs.run.RunImpl; -import net.neoforged.gradle.common.runs.tasks.RunExec; +import net.neoforged.gradle.common.util.ClasspathUtils; import net.neoforged.gradle.common.util.SourceSetUtils; import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; import net.neoforged.gradle.dsl.common.extensions.subsystems.conventions.Runs; @@ -15,12 +15,15 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.plugins.ide.idea.model.IdeaModel; @@ -29,7 +32,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.Collection; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -74,8 +76,32 @@ public static Run create(final Project project, final String name) { project.afterEvaluate(evaluatedProject -> { if (!run.getIsJUnit().get()) { //Create run exec tasks for all non-unit test runs - project.getTasks().register(createTaskName(name), RunExec.class, runExec -> { - runExec.getRun().set(run); + project.getTasks().register(createTaskName(name), JavaExec.class, runExec -> { + runExec.setDescription("Runs the " + name + " run."); + runExec.setGroup("NeoGradle/Runs"); + + JavaToolchainService service = evaluatedProject.getExtensions().getByType(JavaToolchainService.class); + runExec.getJavaLauncher().convention(service.launcherFor(evaluatedProject.getExtensions().getByType(JavaPluginExtension.class).getToolchain())); + + final File workingDir = run.getWorkingDirectory().get().getAsFile(); + if (!workingDir.exists()) { + workingDir.mkdirs(); + } + + runExec.getMainClass().convention(run.getMainClass()); + runExec.setWorkingDir(workingDir); + runExec.args(run.getProgramArguments().get()); + runExec.jvmArgs(run.getJvmArguments().get()); + runExec.systemProperties(run.getSystemProperties().get()); + runExec.environment(run.getEnvironmentVariables().get()); + run.getModSources().all().get().values().stream() + .map(SourceSet::getRuntimeClasspath) + .forEach(runExec::classpath); + runExec.classpath(run.getDependencies().get().getRuntimeConfiguration()); + runExec.classpath(run.getClasspath()); + + updateRunExecClasspathBasedOnPrimaryTask(runExec, run); + addRunSourcesDependenciesToTask(runExec, run); run.getTaskDependencies().forEach(runExec::dependsOn); @@ -88,6 +114,28 @@ public static Run create(final Project project, final String name) { return run; } + private static void updateRunExecClasspathBasedOnPrimaryTask(final JavaExec runExec, final Run run) { + if (run.getModSources().getPrimary().isPresent()) { + final SourceSet primary = run.getModSources().getPrimary().get(); + + final boolean primaryHasMinecraft = primary.getRuntimeClasspath().getFiles().stream().anyMatch(ClasspathUtils::isMinecraftClasspathEntry); + + //Remove any classpath entries that are already in the primary runtime classpath. + //Also remove any classpath entries that are Minecraft, we can only have one Minecraft jar, in the case that the primary runtime classpath already has Minecraft. + final FileCollection runtimeClasspathWithoutMinecraftAndWithoutPrimaryRuntimeClasspath = + runExec.classpath().getClasspath().filter(file -> !primary.getRuntimeClasspath().contains(file) && (!primaryHasMinecraft || !ClasspathUtils.isMinecraftClasspathEntry(file))); + + //Combine with the primary runtime classpath. + final FileCollection combinedClasspath = primary.getRuntimeClasspath().plus(runtimeClasspathWithoutMinecraftAndWithoutPrimaryRuntimeClasspath); + if (runExec.getClasspath() instanceof ConfigurableFileCollection classpath) { + //Override + classpath.setFrom(combinedClasspath); + } else { + throw new IllegalStateException("Classpath is not a ConfigurableFileCollection"); + } + } + } + private static void createOrReuseTestTask(Project project, String name, RunImpl run) { final Set currentProjectsModSources = run.getModSources().all().get().values() .stream() diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy index 9833e860d..45d7a388e 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/runs/run/RunSourceSets.groovy @@ -5,8 +5,11 @@ import groovy.transform.CompileStatic import net.minecraftforge.gdi.ConfigurableDSLElement import net.minecraftforge.gdi.annotations.DSLProperty import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.SourceSet /** @@ -87,6 +90,20 @@ interface RunSourceSets extends ConfigurableDSLElement { */ void add(String groupId, SourceSet... sourceSets); + /** + * Defines the primary sourceset of the run, + * If defined as to be part of the run, it will be used as the primary source set, who's minecraft dependency will be used + * instead of the default combined runtime classpath of the mod sources. + * + * This can fix issues with the newer FML 3.0.0+ where there can only be one minecraft dependency. + * + * @return The primary source set + */ + @DSLProperty + @Input + @Optional + Property getPrimary(); + /** * The source sets attached to this run */ diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy index 6960c0f47..3c0f440cd 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy @@ -8,6 +8,8 @@ import org.gradle.testkit.runner.TaskOutcome import java.nio.file.Files import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.function.Supplier class CentralCacheTests extends BuilderBasedTestSpecification { @@ -89,53 +91,6 @@ class CentralCacheTests extends BuilderBasedTestSpecification { cacheDir.listFiles().size() == 4 } - def "cache_supports_running_gradle_in_parallel"() { - if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows")) { - //When we run on windows we do not get the right output, since we use native file locking. - return - } - - given: - def project = create("cache_supports_running_gradle_in_parallel", { - it.build(""" - java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } - } - - dependencies { - implementation 'net.neoforged:neoforge:+' - } - """) - it.withToolchains() - it.property(CentralCacheService.DEBUG_CACHE_PROPERTY, "true") - }) - - when: - Map> runs = new ConcurrentHashMap<>(); - (1..4).each { i -> - var runFuture = CompletableFuture.supplyAsync { - return project.run { - //We expect this to fail -> Gradle really does not like it when you run multiple builds in parallel, but I want to test the cache. - it.shouldFail() - it.tasks('build') - it.stacktrace() - } - } - - runs.put(i, runFuture) - } - - CompletableFuture[] completedFutures = runs.values().toArray(new CompletableFuture[0]) - def completedFuture = CompletableFuture.allOf(completedFutures) - completedFuture.get() - - then: - //We expect there to be at least one that waited for locking processes to complete. - runs.values().stream().anyMatch { it.get().output.contains("Lock file is owned by another process:")} - } - def "cache_supports_cleanup_and_take_over_of_failed_lock"() { given: File cacheDir; 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 3b9bdb96f..e021d574d 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/ConfigurationCacheTests.groovy @@ -2,6 +2,7 @@ package net.neoforged.gradle.userdev import net.neoforged.gradle.common.caching.CentralCacheService import net.neoforged.trainingwheels.gradle.functional.BuilderBasedTestSpecification +import net.neoforged.trainingwheels.gradle.functional.builder.Runtime import org.gradle.testkit.runner.TaskOutcome class ConfigurationCacheTests extends BuilderBasedTestSpecification { @@ -12,11 +13,6 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { injectIntoAllProject = true; } - @Override - protected File getTestTempDirectory() { - return new File("build", "userdev") - } - def "assemble_supports_configuration_cache_build"() { given: def project = create("assemble_supports_configuration_cache_build", { @@ -100,4 +96,51 @@ class ConfigurationCacheTests extends BuilderBasedTestSpecification { thirdRun.task(':neoFormDecompile').outcome == TaskOutcome.FROM_CACHE thirdRun.task(':compileJava').outcome == TaskOutcome.FROM_CACHE } + + def "run_tasks_supports_configuration_cache_build"() { + given: + def project = create("compile_supports_configuration_cache_build", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + dependencies { + implementation 'net.neoforged:neoforge:+' + } + + afterEvaluate { + //We don't care for the error here, we just want to run the task so that the config cache is created + tasks.withType(JavaExec).named('runData') { + ignoreExitValue = true + } + } + """) + it.file("src/main/java/net/neoforged/gradle/userdev/ConfigurationCacheTests.java", """ + package net.neoforged.gradle.userdev; + + import net.minecraft.client.Minecraft; + + public class ConfigurationCacheTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().getClass().toString()); + } + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + it.enableLocalBuildCache() + it.enableConfigurationCache() + }) + + when: + def run = project.run { + it.tasks('runData') + } + + then: + run.task(':runData').outcome == TaskOutcome.SUCCESS + } } diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy index ffa65a2e0..f2a810cb6 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy @@ -101,10 +101,8 @@ class MultiProjectTests extends BuilderBasedTestSpecification { then: run.task(':main:writeMinecraftClasspathData').outcome == TaskOutcome.SUCCESS - def resourcesMainBuildDir = run.file("main/build/resources/main") run.output.contains("Error during pre-loading phase: ERROR: File null is not a valid mod file") || - run.output.contains("Caused by: net.neoforged.fml.ModLoadingException: Loading errors encountered:\n" + - "\t- File ${resourcesMainBuildDir.absolutePath} is not a valid mod file")//Validate that we are failing because of the missing mod file, and not something else. + run.output.contains("Caused by: net.neoforged.fml.ModLoadingException: Loading errors encountered:") } def "multiple projects with neoforge dependencies should run using the central cache"() { @@ -192,9 +190,9 @@ class MultiProjectTests extends BuilderBasedTestSpecification { } then: - run.task(':main:build').outcome == TaskOutcome.SUCCESS - run.task(':api:neoFormDecompile').outcome == TaskOutcome.SUCCESS - run.task(':main:neoFormDecompile').outcome == TaskOutcome.SUCCESS + run.task(':main:build').outcome == TaskOutcome.SUCCESS || run.task(':main:build').outcome == TaskOutcome.UP_TO_DATE + run.task(':api:neoFormDecompile').outcome == TaskOutcome.SUCCESS || run.task(':api:neoFormDecompile').outcome == TaskOutcome.UP_TO_DATE + run.task(':main:neoFormDecompile').outcome == TaskOutcome.SUCCESS || run.task(':main:neoFormDecompile').outcome == TaskOutcome.UP_TO_DATE } def "multiple projects where one is not neogradle with neoforge dependencies should pull the mod-classes entry from the project name"() { @@ -730,11 +728,13 @@ class MultiProjectTests extends BuilderBasedTestSpecification { run.output.split(System.lineSeparator()).toList().indexOf("Mod Sources:"), run.output.split(System.lineSeparator()).toList().indexOf("Unit Test Sources:") ) - modSourcesSection.size() == 4 + modSourcesSection.size() == 6 modSourcesSection.get(0) == "Mod Sources:" - modSourcesSection.get(1) == " - something:" + modSourcesSection.get(1) == " - main:" modSourcesSection.get(2) == " - main" - modSourcesSection.get(3) == " - main" + modSourcesSection.get(3) == " - something:" + modSourcesSection.get(4) == " - main" + modSourcesSection.get(5) == " - main" } diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy index f9825caa1..d2231d829 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy @@ -60,11 +60,8 @@ class RunTests extends BuilderBasedTestSpecification { then: run.task(':writeMinecraftClasspathData').outcome == TaskOutcome.SUCCESS - - def resourcesMainBuildDir = run.file("build/resources/main") run.output.contains("Error during pre-loading phase: ERROR: File null is not a valid mod file") || - run.output.contains("Caused by: net.neoforged.fml.ModLoadingException: Loading errors encountered:\n" + - "\t- File ${resourcesMainBuildDir.absolutePath} is not a valid mod file")//Validate that we are failing because of the missing mod file, and not something else. + run.output.contains("Caused by: java.io.IOException: Invalid paths argument, contained no existing paths") } def "runs can be declared before the dependencies block"() {