From 0b8939f1f9f5731254c2b0ab78cdafdfa2884539 Mon Sep 17 00:00:00 2001 From: Marc Hermans Date: Mon, 17 Jun 2024 19:01:21 +0200 Subject: [PATCH] [Fix]: Fix ATs not applying. (#211) --- build.gradle | 14 +- .../gradle/common/CommonProjectPlugin.java | 2 +- .../common/caching/CentralCacheService.java | 170 +++++++++++++++--- .../dependency/ExtraJarDependencyManager.java | 7 +- .../replacement/ReplacementLogic.java | 13 +- .../definition/CommonRuntimeDefinition.java | 2 +- .../extensions/CommonRuntimeExtension.java | 2 - .../renamer/RegexBasedSourceRenamer.java | 2 +- .../CommonRuntimeSpecification.java | 3 +- .../common/runtime/tasks/DefaultRuntime.java | 9 - .../common/runtime/tasks/DownloadAssets.java | 37 ++-- .../tasks/action/DownloadFileAction.java | 11 +- .../gradle/common/util/FileCacheUtils.java | 5 +- .../common/util/TaskDependencyUtils.java | 24 ++- .../gradle/common/util/VersionJson.java | 2 +- .../gradle/common/util/hash/Hashing.java | 22 ++- .../dsl/common/util/DistributionType.groovy | 2 +- .../extensions/NeoFormRuntimeExtension.java | 17 +- .../runtime/tasks/RecompileSourceJar.java | 19 ++ .../extensions/DynamicProjectExtension.java | 2 +- .../RuntimeBuilderExtensions.groovy | 10 ++ .../userdev/AccessTransformerTests.groovy | 42 +++++ .../gradle/userdev/CentralCacheTests.groovy | 73 +++++++- .../gradle/userdev/MultiProjectTests.groovy | 106 ++++++++++- .../neoforged/gradle/userdev/RunTests.groovy | 7 +- .../SourceSetConventionTests.groovy | 14 +- .../util/StringCapitalizationUtils.java | 5 +- .../runtime/VanillaRuntimeDefinition.java | 2 +- .../vanilla/runtime/steps/ParchmentStep.java | 21 ++- 29 files changed, 531 insertions(+), 114 deletions(-) diff --git a/build.gradle b/build.gradle index 650c2710f..e3555bc2b 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ subprojects.forEach { Project subProject -> //General project metadata. Everything has the same version and group. subProject.version = subProject.rootProject.version subProject.group = 'net.neoforged.gradle' - subProject.base.archivesName = "ng-${subProject.name.toLowerCase()}" + subProject.base.archivesName = "ng-${subProject.name.toLowerCase(Locale.ROOT)}" //Setup the java toolchain subProject.java.toolchain.languageVersion = JavaLanguageVersion.of(project.java_version) @@ -166,12 +166,14 @@ subprojects.forEach { subProject -> evalSubProject.dependencies.functionalTestImplementation project(':test-utils') //Configure the plugin metadata, so we can publish it. + evalSubProject.gradlePlugin.website = "https://github.com/NeoForged/NeoGradle" + evalSubProject.gradlePlugin.vcsUrl = "https://github.com/NeoForged/NeoGradle.git" evalSubProject.gradlePlugin.plugins { NamedDomainObjectContainer plugins -> - plugins.register(evalSubProject.name.toLowerCase()) { + plugins.register(evalSubProject.name.toLowerCase(Locale.ROOT)) { //Determine the class name and package of the plugin. def pluginFile = evalSubProject.fileTree('src/main/java') - .filter { it.name.toLowerCase() == "${evalSubProject.getName()}plugin.java".toLowerCase() } + .filter { it.name.toLowerCase(Locale.ROOT) == "${evalSubProject.getName()}plugin.java".toLowerCase(Locale.ROOT) } .first() //We need to handle the case of a fresh new project, no files exist yet, so the pluginFile object will be null. @@ -180,7 +182,7 @@ subprojects.forEach { subProject -> pluginClassName = evalSubProject.file('src/main/java/').toPath().relativize(pluginFile.toPath()).toString().replace('/', '.').replace('\\', '.').replace(".java", "") } - def pluginId = 'net.neoforged.gradle.' + evalSubProject.name.toLowerCase() + def pluginId = 'net.neoforged.gradle.' + evalSubProject.name.toLowerCase(Locale.ROOT) try { var propertyValue = evalSubProject.property('pluginId') if (propertyValue != null) { @@ -192,6 +194,10 @@ subprojects.forEach { subProject -> id = pluginId //And the implementation class. implementationClass = pluginClassName + + displayName = "NeoForged Gradle ${evalSubProject.name.capitalize()} Plugin" + description = "A plugin for NeoForged Gradle, providing ${evalSubProject.name.capitalize()} functionality. Allowing you to setup Minecraft Modding workspaces with ease." + tags = ['minecraft', 'modding', 'gradle', 'neoforged', 'neoforge', evalSubProject.name.toLowerCase(Locale.ROOT)] } } diff --git a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java index cd8d19bd8..c3c9a3cb5 100644 --- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java +++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java @@ -159,7 +159,7 @@ public void apply(Project project) { if (devLogin.getEnabled().get()) { runs.configureEach(run -> { final RunDevLogin runsDevLogin = run.getExtensions().create("devLogin", RunDevLogin.class); - runsDevLogin.getIsEnabled().convention(devLogin.getConventionForRun()); + runsDevLogin.getIsEnabled().convention(devLogin.getConventionForRun().zip(run.getIsClient(), (conventionForRun, isClient) -> conventionForRun && isClient)); }); } 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 72afc6468..4cf0350de 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 @@ -6,6 +6,7 @@ import net.neoforged.gradle.common.util.hash.Hasher; import net.neoforged.gradle.common.util.hash.Hashing; import net.neoforged.gradle.util.FileUtils; +import org.apache.commons.io.filefilter.AbstractFileFilter; import org.apache.commons.lang3.SystemUtils; import org.gradle.api.GradleException; import org.gradle.api.Project; @@ -13,6 +14,7 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.services.BuildService; @@ -25,13 +27,12 @@ import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.text.DateFormat; -import java.util.Comparator; -import java.util.Date; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -120,6 +121,27 @@ public void doCached(Task task, DoCreate onCreate, Provider target) debugLog(task, "Cached task: " + task.getPath() + " finished"); } + public void doCachedDirectory(Task task, DoCreate onCreate, DirectoryProperty output) throws Throwable { + if (!getParameters().getIsEnabled().get()) { + debugLog(task, "Cache is disabled, skipping cache"); + onCreate.create(); + return; + } + + final TaskHasher hasher = new TaskHasher(task); + final String hashDirectoryName = hasher.create().toString(); + final Directory cacheDirectory = getParameters().getCacheDirectory().get().dir(hashDirectoryName); + final Directory targetDirectory = output.get(); + + debugLog(task, "Cache directory: " + cacheDirectory.getAsFile().getAbsolutePath()); + debugLog(task, "Target directory: " + targetDirectory.getAsFile().getAbsolutePath()); + + final File lockFile = new File(cacheDirectory.getAsFile(), "lock"); + debugLog(task, "Lock file: " + lockFile.getAbsolutePath()); + executeCacheLookupOrCreation(task, onCreate, lockFile, cacheDirectory, targetDirectory); + debugLog(task, "Cached task: " + task.getPath() + " finished"); + } + @SuppressWarnings("ResultOfMethodCallIgnored") private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File lockFile, Directory cacheDirectory, RegularFile targetFile) throws Throwable { if (!lockFile.exists()) { @@ -139,7 +161,7 @@ private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File loc fileBasedLock.updateAccessTime(); debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath()); - executeCacheLookupOrCreationLocked(task, onCreate, cacheDirectory, targetFile.getAsFile(), fileBasedLock.hasPreviousFailure()); + executeCacheLookupOrCreationLocked(task, onCreate, cacheDirectory, targetFile.getAsFile(), fileBasedLock.hasPreviousFailure(), false); // Release the lock when done debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath()); @@ -151,15 +173,47 @@ private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File loc } } - private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Directory cacheDirectory, File targetFile, boolean failedPreviously) throws Throwable { + + @SuppressWarnings("ResultOfMethodCallIgnored") + private void executeCacheLookupOrCreation(Task task, DoCreate onCreate, File lockFile, Directory cacheDirectory, Directory targetDirectory) throws Throwable { + if (!lockFile.exists()) { + debugLog(task, "Lock file does not exist: " + lockFile.getAbsolutePath()); + try { + lockFile.getParentFile().mkdirs(); + lockFile.createNewFile(); + } catch (IOException e) { + throw new GradleException("Failed to create lock file: " + lockFile.getAbsolutePath(), e); + } + } + + // Acquiring an exclusive lock on the file + debugLog(task, "Acquiring lock on file: " + lockFile.getAbsolutePath()); + try(FileBasedLock fileBasedLock = lockManager.createLock(task, lockFile)) { + try { + fileBasedLock.updateAccessTime(); + debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath()); + + executeCacheLookupOrCreationLocked(task, onCreate, cacheDirectory, targetDirectory.getAsFile(), fileBasedLock.hasPreviousFailure(), true); + + // Release the lock when done + debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath()); + } catch (Exception ex) { + debugLog(task, "Exception occurred while executing cached task: " + targetDirectory.getAsFile().getAbsolutePath(), ex); + fileBasedLock.markAsFailed(); + throw new GradleException("Cached execution failed for: " + task.getName(), ex); + } + } + } + + private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Directory cacheDirectory, File targetFile, boolean failedPreviously, boolean isDirectory) throws Throwable { final File cacheFile = new File(cacheDirectory.getAsFile(), targetFile.getName()); final File noCacheFile = new File(cacheDirectory.getAsFile(), "nocache"); if (failedPreviously) { //Previous execution failed debugLog(task, "Last cache run failed: " + cacheFile.getAbsolutePath()); - Files.deleteIfExists(cacheFile.toPath()); - Files.deleteIfExists(noCacheFile.toPath()); + FileUtils.delete(cacheFile.toPath()); + FileUtils.delete(noCacheFile.toPath()); } if (noCacheFile.exists()) { @@ -167,9 +221,16 @@ private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Di debugLog(task, "Last cache run indicated no output: " + noCacheFile.getAbsolutePath()); logCacheHit(task, noCacheFile); task.setDidWork(false); - Files.deleteIfExists(targetFile.toPath()); + FileUtils.delete(targetFile.toPath()); + if (isDirectory) { + //Create the directory, we always ensure the empty directory will get created! + if (!targetFile.mkdirs()) { + throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath()); + } + } return; } + if (cacheFile.exists()) { debugLog(task, "Cached file exists: " + cacheFile.getAbsolutePath()); if (targetFile.exists() && Hashing.hashFile(cacheFile).equals(Hashing.hashFile(targetFile))) { @@ -181,8 +242,22 @@ private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Di debugLog(task, "Cached file does not equal target file"); logCacheHit(task, cacheFile); - Files.deleteIfExists(targetFile.toPath()); - Files.copy(cacheFile.toPath(), targetFile.toPath()); + FileUtils.delete(targetFile.toPath()); + if (!isDirectory) { + Files.copy(cacheFile.toPath(), targetFile.toPath()); + } else { + //Copy the directory + Files.walkFileTree(cacheFile.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final Path relativePath = cacheFile.toPath().relativize(file); + final Path targetPath = targetFile.toPath().resolve(relativePath); + Files.createDirectories(targetPath.getParent()); + Files.copy(file, targetPath); + return FileVisitResult.CONTINUE; + } + }); + } task.setDidWork(false); } else { debugLog(task, "Cached file does not exist: " + cacheFile.getAbsolutePath()); @@ -190,9 +265,12 @@ private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Di debugLog(task, "Creating output: " + targetFile.getAbsolutePath()); File createdFile = onCreate.create(); + if (isDirectory && !createdFile.isDirectory()) + throw new IllegalStateException("Expected a directory, but got a file: " + createdFile.getAbsolutePath()); + debugLog(task, "Created output: " + createdFile.getAbsolutePath()); - if (!createdFile.exists()) { + if (!createdFile.exists() || (isDirectory && Objects.requireNonNull(createdFile.listFiles()).length == 0)) { //No output was created debugLog(task, "No output was created: " + createdFile.getAbsolutePath()); Files.createFile(noCacheFile.toPath()); @@ -201,7 +279,21 @@ private void executeCacheLookupOrCreationLocked(Task task, DoCreate onCreate, Di //Output was created debugLog(task, "Output was created: " + createdFile.getAbsolutePath()); Files.deleteIfExists(noCacheFile.toPath()); - Files.copy(createdFile.toPath(), cacheFile.toPath()); + if (!isDirectory) { + Files.copy(createdFile.toPath(), cacheFile.toPath()); + } else { + //Copy the directory + Files.walkFileTree(createdFile.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final Path relativePath = createdFile.toPath().relativize(file); + final Path targetPath = cacheFile.toPath().resolve(relativePath); + Files.createDirectories(targetPath.getParent()); + Files.copy(file, targetPath); + return FileVisitResult.CONTINUE; + } + }); + } } task.setDidWork(true); } @@ -227,13 +319,13 @@ private void logCacheMiss(Task task) { private void debugLog(Task task, String message) { if (getParameters().getDebugCache().get()) { - task.getLogger().lifecycle( " > [" + new Date(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( " > [" + new Date(System.currentTimeMillis()) + "] (" + ProcessHandle.current().pid() + "): " + message, e); + task.getLogger().lifecycle( " > [" + System.currentTimeMillis() + "] (" + ProcessHandle.current().pid() + "): " + message, e); } } @@ -282,12 +374,14 @@ public void hash(TaskInputs inputs) throws IOException { }); for (File file : inputs.getFiles()) { - for (Path path : Files.walk(file.toPath()).filter(Files::isRegularFile).toList()) { - debugLog(task, "Hashing task input file: " + path.toAbsolutePath()); - hasher.putString(path.getFileName().toString()); - final HashCode code = hashFunction.hashFile(path.toFile()); - debugLog(task, "Hashing task input file hash: " + code); - hasher.putHash(code); + 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()); + final HashCode code = hashFunction.hashFile(path.toFile()); + debugLog(task, "Hashing task input file hash: " + code); + hasher.putHash(code); + } } } } @@ -396,6 +490,8 @@ 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; @@ -415,6 +511,10 @@ public NioBasedFileLock(Task task, File lockFile) { 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); @@ -427,6 +527,9 @@ public void close() throws Exception { fileChannel.close(); lockFileAccess.close(); + final OwnerAwareReentrantLock lock = FILE_LOCKS.get(lockFile.getAbsolutePath()); + lock.unlock(); + debugLog(task, "Released lock on file: " + lockFile.getAbsolutePath()); } } @@ -464,6 +567,7 @@ public void close() throws Exception { private final class PIDBasedFileLock implements AutoCloseable { + private static final Map FILE_LOCKS = new ConcurrentHashMap<>(); private final Task task; private final File lockFile; @@ -474,6 +578,7 @@ private PIDBasedFileLock(Task task, File lockFile) { } private void lockFile() { + debugLog(task, "Attempting to acquire lock on file: " + lockFile.getAbsolutePath()); while (!attemptFileLock()) { //We attempt a lock every 500ms try { @@ -482,9 +587,10 @@ private void lockFile() { throw new RuntimeException("Failed to acquire lock on file: " + lockFile.getAbsolutePath(), e); } } + debugLog(task, "Lock acquired on file: " + lockFile.getAbsolutePath()); } - private boolean attemptFileLock() { + private synchronized boolean attemptFileLock() { try { if (!lockFile.exists()) { //No lock file exists, create one @@ -498,6 +604,9 @@ private boolean attemptFileLock() { int pid = Integer.parseInt(s); if (ProcessHandle.current().pid() == pid) { debugLog(task, "Lock file is owned by current process: " + lockFile.getAbsolutePath() + " pid: " + pid); + final OwnerAwareReentrantLock lock = FILE_LOCKS.computeIfAbsent(lockFile.getAbsolutePath(), s1 -> new OwnerAwareReentrantLock()); + debugLog(task, "Lock file is held by thread: " + lock.getOwner().getId() + " - " + lock.getOwner().getName() + " current thread: " + Thread.currentThread().getId() + " - " + Thread.currentThread().getName()); + lock.lock(); return true; } @@ -518,6 +627,9 @@ private boolean attemptFileLock() { //No pid found in lock file, we can take over the lock debugLog(task, "Lock file is empty: " + lockFile.getAbsolutePath()); Files.write(lockFile.toPath(), String.valueOf(ProcessHandle.current().pid()).getBytes(), StandardOpenOption.TRUNCATE_EXISTING); + 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(); return true; } catch (Exception e) { debugLog(task, "Failed to acquire lock on file: " + lockFile.getAbsolutePath() + " - Failure message: " + e.getLocalizedMessage(), e); @@ -529,6 +641,16 @@ private boolean attemptFileLock() { public void close() throws Exception { debugLog(task, "Releasing lock on file: " + lockFile.getAbsolutePath()); Files.write(lockFile.toPath(), Lists.newArrayList(), StandardOpenOption.TRUNCATE_EXISTING); + if (FILE_LOCKS.containsKey(lockFile.getAbsolutePath())) { + FILE_LOCKS.get(lockFile.getAbsolutePath()).unlock(); + } + } + } + + private static final class OwnerAwareReentrantLock extends ReentrantLock { + @Override + public Thread getOwner() { + return super.getOwner(); } } } diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java b/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java index 30d92f846..d6bd37664 100644 --- a/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/ExtraJarDependencyManager.java @@ -16,10 +16,7 @@ import org.gradle.api.tasks.TaskProvider; import javax.inject.Inject; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; public abstract class ExtraJarDependencyManager { @@ -34,7 +31,7 @@ public static String generateServerCoordinateFor(final String version) { } public static String generateCoordinateFor(final DistributionType type, final String version) { - return String.format("net.minecraft:%s:%s:%s-extra", type.getName().toLowerCase(), version, type.getName().toLowerCase()); + return String.format("net.minecraft:%s:%s:%s-extra", type.getName().toLowerCase(Locale.ROOT), version, type.getName().toLowerCase(Locale.ROOT)); } @Inject diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java index 14e4b8f71..910f73158 100644 --- a/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/dependency/replacement/ReplacementLogic.java @@ -90,9 +90,18 @@ public NamedDomainObjectContainer getReplacementHa @NotNull @Override - public Dependency optionallyConvertBackToOriginal(Dependency dependency, Configuration configuration) { + public Dependency optionallyConvertBackToOriginal(@NotNull final Dependency dependency, Configuration configuration) { final Dependency originalDependency = originalDependencyLookup.get(dependency, configuration); - return originalDependency == null ? dependency : originalDependency; + if (originalDependency == null && !configuration.getExtendsFrom().isEmpty()) { + //Check if we have a parent configuration that might have the original dependency. + for (Configuration parentConfiguration : configuration.getExtendsFrom()) { + return optionallyConvertBackToOriginal(dependency, parentConfiguration); + } + } else if (originalDependency != null) { + return originalDependency; + } + + return dependency; } /** diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java index d1acbd65b..0e9960136 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/definition/CommonRuntimeDefinition.java @@ -177,7 +177,7 @@ protected Map buildRunInterpolationData(RunImpl run) { interpolationData.put("runtime_name", specification.getVersionedName()); interpolationData.put("mc_version", specification.getMinecraftVersion()); - interpolationData.put("assets_root", getAssets().get().getOutputDirectory().get().getAsFile().getAbsolutePath()); + interpolationData.put("assets_root", DownloadAssets.getAssetsDirectory(specification.getProject(), specification.getProject().provider(this::getVersionJson)).get().getAsFile().getAbsolutePath()); interpolationData.put("asset_index", getAssets().get().getAssetIndexFile().get().getAsFile().getName().substring(0, getAssets().get().getAssetIndexFile().get().getAsFile().getName().lastIndexOf('.'))); interpolationData.put("natives", getNatives().get().getOutputDirectory().get().getAsFile().getAbsolutePath()); diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java b/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java index faee4f806..6ab9cba56 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/extensions/CommonRuntimeExtension.java @@ -188,8 +188,6 @@ public Set findIn(final Configuration configuration) { protected final TaskProvider createDownloadAssetsTasks(final CommonRuntimeSpecification specification, final Map symbolicDataSources, final File runtimeDirectory, final VersionJson versionJson) { return specification.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(specification, "downloadAssets"), DownloadAssets.class, task -> { task.getVersionJson().set(versionJson); - - configureCommonRuntimeTaskParameters(task, symbolicDataSources, "downloadAssets", specification, runtimeDirectory); }); } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/renamer/RegexBasedSourceRenamer.java b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/renamer/RegexBasedSourceRenamer.java index eaf89a749..b4bebe869 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/naming/renamer/RegexBasedSourceRenamer.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/naming/renamer/RegexBasedSourceRenamer.java @@ -185,7 +185,7 @@ private String getMapped(String srg, @Nullable Set blacklist) { String ret = getNames().getOrDefault(srg, srg); if (cap) - ret = ret.substring(0, 1).toUpperCase(Locale.ENGLISH) + ret.substring(1); + ret = ret.substring(0, 1).toUpperCase(Locale.ROOT) + ret.substring(1); return ret; } diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/specification/CommonRuntimeSpecification.java b/common/src/main/java/net/neoforged/gradle/common/runtime/specification/CommonRuntimeSpecification.java index 1baafa663..a8053ed5b 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/specification/CommonRuntimeSpecification.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/specification/CommonRuntimeSpecification.java @@ -15,6 +15,7 @@ import org.gradle.api.provider.Provider; import org.jetbrains.annotations.NotNull; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -63,7 +64,7 @@ public String getName() { @NotNull @Override public String getIdentifier() { - return getName() + StringUtils.capitalize(getDistribution().getName().toLowerCase()) + version; + return getName() + StringUtils.capitalize(getDistribution().getName().toLowerCase(Locale.ROOT)) + version; } @Override diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java index bbd20a108..f4f4d8755 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DefaultRuntime.java @@ -73,19 +73,10 @@ public String getGroup() { return String.format("NeoGradle/Runtime/%s", name); } - protected Provider getFileInOutputDirectory(final String fileName) { return getOutputDirectory().map(directory -> directory.file(fileName).getAsFile()); } - protected Provider getFileInOutputDirectory(final Provider fileName) { - return getOutputDirectory().flatMap(directory -> fileName.map(f -> directory.file(f).getAsFile())); - } - - protected Provider getRegularFileInOutputDirectory(final Provider fileName) { - return getOutputDirectory().flatMap(directory -> fileName.map(directory::file)); - } - @Internal public abstract MapProperty getRuntimeData(); diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java index 5673d9447..7ca3fb365 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/DownloadAssets.java @@ -4,11 +4,16 @@ import net.neoforged.gradle.common.CommonProjectPlugin; import net.neoforged.gradle.common.caching.CentralCacheService; import net.neoforged.gradle.common.util.FileCacheUtils; +import net.neoforged.gradle.dsl.common.tasks.WithWorkspace; import net.neoforged.gradle.util.TransformerUtils; import net.neoforged.gradle.common.runtime.tasks.action.DownloadFileAction; import net.neoforged.gradle.common.util.SerializationUtils; import net.neoforged.gradle.common.util.VersionJson; +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -16,26 +21,40 @@ import org.gradle.api.tasks.*; import org.gradle.workers.WorkQueue; import org.gradle.workers.WorkerExecutor; +import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.File; import java.util.Map; -@SuppressWarnings({"UnstableApiUsage", "ResultOfMethodCallIgnored"}) +@SuppressWarnings({"UnstableApiUsage"}) @CacheableTask -public abstract class DownloadAssets extends DefaultRuntime { +public abstract class DownloadAssets extends DefaultTask implements WithWorkspace { + + private final Provider assetsCache; public DownloadAssets() { - getAssetsDirectory().convention(FileCacheUtils.getAssetsCacheDirectory(getProject()).flatMap(assetsDir -> assetsDir.dir(getVersionJson().map(VersionJson::getId))).map(TransformerUtils.ensureExists())); - getOutputDirectory().convention(getAssetsDirectory()); + this.assetsCache = getAssetsDirectory(getProject(), getVersionJson()); getAssetIndex().convention("asset-index"); getAssetIndexFileName().convention(getAssetIndex().map(index -> index + ".json")); - getAssetIndexFile().convention(getRegularFileInOutputDirectory(getAssetIndexFileName().map(name -> "indexes/" + name))); + getAssetIndexFile().convention(getRegularFileInAssetsDirectory(getAssetIndexFileName().map(name -> "indexes/" + name))); getVersionJson().convention(getVersionJsonFile().map(TransformerUtils.guard(file -> VersionJson.get(file.getAsFile())))); getAssetRepository().convention("https://resources.download.minecraft.net/"); getIsOffline().convention(getProject().getGradle().getStartParameter().isOffline()); } - + + public static @NotNull Provider getAssetsDirectory(final Project project, final Provider versionJsonProvider) { + return FileCacheUtils.getAssetsCacheDirectory(project).flatMap(assetsDir -> assetsDir.dir(versionJsonProvider.map(VersionJson::getId))).map(TransformerUtils.ensureExists()); + } + + protected Provider getFileInAssetsDirectory(final String fileName) { + return assetsCache.map(directory -> directory.file(fileName).getAsFile()); + } + + protected Provider getRegularFileInAssetsDirectory(final Provider fileName) { + return assetsCache.flatMap(directory -> fileName.map(directory::file)); + } + @ServiceReference(CommonProjectPlugin.ASSETS_SERVICE) public abstract Property getAssetsCache(); @@ -67,7 +86,7 @@ private void downloadAssets() { final WorkQueue executor = getWorkerExecutor().noIsolation(); assetIndex.getObjects().values().stream().distinct().forEach((asset) -> { - final Provider assetFile = getFileInOutputDirectory(String.format("objects%s%s", File.separator, asset.getPath())); + final Provider assetFile = getFileInAssetsDirectory(String.format("objects%s%s", File.separator, asset.getPath())); final Provider assetUrl = getAssetRepository() .map(repo -> repo.endsWith("/") ? repo : repo + "/") .map(TransformerUtils.guard(repository -> repository + asset.getPath())); @@ -110,10 +129,6 @@ private void downloadAssets() { @Input public abstract Property getIsOffline(); - @InputDirectory - @PathSensitive(PathSensitivity.NONE) - public abstract DirectoryProperty getAssetsDirectory(); - private static class AssetIndex { private Map objects = Maps.newHashMap(); diff --git a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/action/DownloadFileAction.java b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/action/DownloadFileAction.java index fcd6b37fe..4a6369ea9 100644 --- a/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/action/DownloadFileAction.java +++ b/common/src/main/java/net/neoforged/gradle/common/runtime/tasks/action/DownloadFileAction.java @@ -106,12 +106,7 @@ public void execute() { } } - private static final class Monitor implements CopyProgressListener { - private final GradleInternalUtils.ProgressLoggerWrapper progress; - - private Monitor(GradleInternalUtils.ProgressLoggerWrapper progress) { - this.progress = progress; - } + private record Monitor(GradleInternalUtils.ProgressLoggerWrapper progress) implements CopyProgressListener { @Override public void start(CopyProgressEvent evt) { @@ -148,9 +143,13 @@ public int getReadTimeout() { public interface Params extends WorkParameters { Property getUrl(); + Property getSha1(); + Property getShouldValidateHash(); + RegularFileProperty getOutputFile(); + Property getIsOffline(); } } diff --git a/common/src/main/java/net/neoforged/gradle/common/util/FileCacheUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/FileCacheUtils.java index b99ed7feb..83868f87a 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/FileCacheUtils.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/FileCacheUtils.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; public final class FileCacheUtils { @@ -52,9 +53,9 @@ public static TaskProvider createVers public static TaskProvider createArtifactFileCacheProvidingTask(final Project project, final String minecraftVersion, final DistributionType distributionType, final MinecraftArtifactType type, final TaskProvider versionManifestProvider, final Collection> otherProviders) { final String taskName = NamingConstants.Task.CACHE_VERSION_PREFIX + StringUtils.capitalize( - type.name().toLowerCase() + type.name().toLowerCase(Locale.ROOT) ) + StringUtils.capitalize( - distributionType.getName().toLowerCase() + distributionType.getName().toLowerCase(Locale.ROOT) ) + minecraftVersion; if (project.getTasks().getNames().contains(taskName)) { diff --git a/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java b/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java index 5383e4b6e..bc0154828 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/TaskDependencyUtils.java @@ -10,6 +10,7 @@ import net.neoforged.gradle.dsl.common.util.Artifact; import org.gradle.api.Buildable; import org.gradle.api.Project; +import org.gradle.api.ProjectConfigurationException; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; @@ -212,6 +213,12 @@ public void add(@NotNull Object dependency) { } private void processTask(Task task) { + //Tasks that are outside our project are not relevant, they can cause issues in parallel projects anyway + //And their runtimes should not configure our runs anyway! + if (task.getProject() != this.project) { + return; + } + final Optional> rawJarRuntime = this.runtimes.stream().filter(runtime -> runtime.getRawJarTask().get().equals(task)).findFirst(); final Optional> sourceJarRuntime = this.runtimes.stream().filter(runtime -> runtime.getSourceJarTask().get().equals(task)).findFirst(); if (rawJarRuntime.isPresent()) { @@ -226,7 +233,7 @@ private void processTask(Task task) { } private void processConfiguration(Configuration configuration) { - DependencySet dependencies = configuration.getDependencies(); + DependencySet dependencies = configuration.getAllDependencies(); //Grab the original dependencies if we have a replacement extension final DependencyReplacement replacement = project.getExtensions().findByType(DependencyReplacement.class); @@ -244,14 +251,6 @@ private void processConfiguration(Configuration configuration) { return false; } }).forEach(this::add); - - configuration.getExtendsFrom().forEach(this::add); - - if (configuration.isCanBeResolved()) { - for (Task task : configuration.getBuildDependencies().getDependencies(null)) { - add(task); - } - } } private void processSourceDirectorySet(SourceDirectorySet sourceDirectorySet) { @@ -268,6 +267,13 @@ private void processSourceDirectorySet(SourceDirectorySet sourceDirectorySet) { } private void processSourceSet(SourceSet sourceSet) { + //We only care about source sets in our project + //We don't want to configure runtimes for other projects + //This can cause issues with parallel projects + if (SourceSetUtils.getProject(sourceSet) != this.project) { + return; + } + Property> runtimeDefinition = (Property>) sourceSet.getExtensions().findByName("runtimeDefinition"); if (runtimeDefinition != null && runtimeDefinition.isPresent()) { this.add(runtimeDefinition.get()); diff --git a/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java b/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java index 8574873cf..19209b939 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/VersionJson.java @@ -400,7 +400,7 @@ public String getName() { } public static OS getCurrent() { - String prop = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + String prop = System.getProperty("os.name").toLowerCase(Locale.ROOT); for (OS os : OS.values()) { for (String key : os.keys) { if (prop.contains(key)) { diff --git a/common/src/main/java/net/neoforged/gradle/common/util/hash/Hashing.java b/common/src/main/java/net/neoforged/gradle/common/util/hash/Hashing.java index 03c365dec..d479998aa 100644 --- a/common/src/main/java/net/neoforged/gradle/common/util/hash/Hashing.java +++ b/common/src/main/java/net/neoforged/gradle/common/util/hash/Hashing.java @@ -13,6 +13,7 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Objects; public class Hashing { private static final HashFunction MD5 = MessageDigestHashFunction.of("MD5"); @@ -355,9 +356,24 @@ public HashCode hashStream(InputStream stream) throws IOException { } public HashCode hashFile(File file) throws IOException { - HashingOutputStream hashingOutputStream = this.primitiveStreamHasher(); - Files.copy(file, hashingOutputStream); - return hashingOutputStream.hash(); + if (file.exists()) { + if (file.isDirectory()) { + final Hasher hasher = this.newHasher(); + + for (File listFile : Objects.requireNonNull(file.listFiles())) { + final HashCode innerHash = this.hashFile(listFile); + hasher.putHash(innerHash); + } + + return hasher.hash(); + } else { + HashingOutputStream hashingOutputStream = this.primitiveStreamHasher(); + Files.copy(file, hashingOutputStream); + return hashingOutputStream.hash(); + } + } else { + return HashCode.fromString(""); + } } private HashingOutputStream primitiveStreamHasher() { diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/util/DistributionType.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/util/DistributionType.groovy index 156d77a42..a1f90d261 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/util/DistributionType.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/util/DistributionType.groovy @@ -81,6 +81,6 @@ enum DistributionType { * @param suffix The suffix for the task name. */ String createTaskName(final String prefix, final String suffix) { - return "${StringUtils.uncapitalize(prefix)}${StringUtils.capitalize(this.name().toLowerCase())}${StringUtils.capitalize(suffix)}".toString(); + return "${StringUtils.uncapitalize(prefix)}${StringUtils.capitalize(this.name().toLowerCase(Locale.ROOT))}${StringUtils.capitalize(suffix)}".toString(); } } 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 67b54233d..af8355511 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 @@ -509,15 +509,24 @@ private static TaskProvider maybeApplyParchment(NeoFormRun File mappingFile = ToolUtilities.resolveTool(project, parchment.getParchmentArtifact().get()); File toolExecutable = ToolUtilities.resolveTool(project, tools.getJST().get()); - task.getInputs().file(mappingFile); + task.getArguments().putFile("mappings", project.provider(() -> mappingFile)); + task.getArguments().putRegularFile("libraries", listLibrariesOutput); + task.getArguments().putRegularFile("input", recompileInput.flatMap(WithOutput::getOutput)); + task.getExecutingJar().set(toolExecutable); - task.getProgramArguments().add(listLibrariesOutput.map(f -> "--libraries-list=" + f.getAsFile().getAbsolutePath())); + task.getProgramArguments().add("--libraries-list"); + task.getProgramArguments().add("{libraries}"); task.getProgramArguments().add("--enable-parchment"); - task.getProgramArguments().add("--parchment-mappings=" + mappingFile.getAbsolutePath()); + task.getProgramArguments().add("--parchment-mappings"); + task.getProgramArguments().add("{mappings}"); task.getProgramArguments().add("--in-format=archive"); task.getProgramArguments().add("--out-format=archive"); - task.getProgramArguments().add(recompileInput.flatMap(WithOutput::getOutput).map(f -> f.getAsFile().getAbsolutePath())); + task.getProgramArguments().add("{input}"); task.getProgramArguments().add("{output}"); + + task.dependsOn(listLibrariesOutput); + task.dependsOn(recompileInput); + configureCommonRuntimeTaskParameters(task, symbolicDataSources, "applyParchment", spec, neoFormDirectory); }); diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/RecompileSourceJar.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/RecompileSourceJar.java index ccc228022..ec472c17f 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/RecompileSourceJar.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/RecompileSourceJar.java @@ -1,5 +1,7 @@ package net.neoforged.gradle.neoform.runtime.tasks; +import net.neoforged.gradle.common.CommonProjectPlugin; +import net.neoforged.gradle.common.caching.CentralCacheService; import net.neoforged.gradle.common.runtime.tasks.RuntimeArgumentsImpl; import net.neoforged.gradle.common.runtime.tasks.RuntimeMultiArgumentsImpl; import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime; @@ -11,9 +13,11 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.services.ServiceReference; import org.gradle.api.tasks.*; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.internal.jvm.Jvm; +import org.gradle.jvm.toolchain.JavaCompiler; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.work.InputChanges; @@ -121,4 +125,19 @@ public Property getJavaVersion() { @Inject @Override public abstract ProviderFactory getProviderFactory(); + + @ServiceReference(CommonProjectPlugin.EXECUTE_SERVICE) + public abstract Property getCacheService(); + + @Override + protected void compile(InputChanges inputs) { + try { + getCacheService().get().doCachedDirectory(this, () -> { + super.compile(inputs); + return getDestinationDirectory().get().getAsFile(); + }, getDestinationDirectory()); + } catch (Throwable e) { + throw new RuntimeException("Failed to recompile using caching.", e); + } + } } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java index dfeae5e87..778e2e059 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java @@ -770,7 +770,7 @@ private void configureRunType(final Project project, final RunType runType, fina runType.getClasspath().from(runtimeClasspath); - Provider assetsDir = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetsDirectory).map(Directory::getAsFile).map(File::getAbsolutePath); + Provider assetsDir = DownloadAssets.getAssetsDirectory(project, project.provider(runtimeDefinition::getVersionJson)).map(Directory::getAsFile).map(File::getAbsolutePath); Provider assetIndex = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetIndex); runType.getRunAdapter().set(run -> { diff --git a/test-utils/src/main/groovy/net/neoforged/gradle/utils/test/extensions/RuntimeBuilderExtensions.groovy b/test-utils/src/main/groovy/net/neoforged/gradle/utils/test/extensions/RuntimeBuilderExtensions.groovy index 099c92cfa..024f919c6 100644 --- a/test-utils/src/main/groovy/net/neoforged/gradle/utils/test/extensions/RuntimeBuilderExtensions.groovy +++ b/test-utils/src/main/groovy/net/neoforged/gradle/utils/test/extensions/RuntimeBuilderExtensions.groovy @@ -43,4 +43,14 @@ class RuntimeBuilderExtensions { static Runtime.Builder disableConventions(final Runtime.Builder self) { self.property("neogradle.subsystems.conventions.enabled", "false") } + + /** + * Enables parallel running for the runtime. + * + * @param self the runtime builder + * @return the runtime builder + */ + static Runtime.Builder enableGradleParallelRunning(final Runtime.Builder self) { + self.property("org.gradle.parallel", "true") + } } diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/AccessTransformerTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/AccessTransformerTests.groovy index 13115fb6e..043a28610 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/AccessTransformerTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/AccessTransformerTests.groovy @@ -54,6 +54,48 @@ class AccessTransformerTests extends BuilderBasedTestSpecification { initialRun.task(":build").outcome == TaskOutcome.SUCCESS } + def "the userdev runtime supports loading ats from a file after the dependencies block"() { + given: + def project = create("userdev_supports_ats_from_file", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + dependencies { + implementation 'net.neoforged:neoforge:+' + } + + minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') + """) + it.file("src/main/resources/META-INF/accesstransformer.cfg", """public-f net.minecraft.client.Minecraft fixerUpper # fixerUpper""") + it.file("src/main/java/net/neoforged/gradle/userdev/FunctionalTests.java", """ + package net.neoforged.gradle.userdev; + + import net.minecraft.client.Minecraft; + + public class FunctionalTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().fixerUpper.getClass().toString()); + } + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + }) + + when: + def initialRun = project.run { + it.tasks('build') + } + + then: + initialRun.task(":neoFormRecompile").outcome == TaskOutcome.SUCCESS + initialRun.task(":build").outcome == TaskOutcome.SUCCESS + } + def "the userdev runtime supports loading ats from the script"() { given: def project = create("userdev_supports_ats_in_scripts", { 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 325740d57..6960c0f47 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/CentralCacheTests.groovy @@ -90,7 +90,7 @@ class CentralCacheTests extends BuilderBasedTestSpecification { } def "cache_supports_running_gradle_in_parallel"() { - if (System.getProperty("os.name").toLowerCase().contains("windows")) { + 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 } @@ -207,4 +207,75 @@ class CentralCacheTests extends BuilderBasedTestSpecification { run.task(':build').outcome == TaskOutcome.SUCCESS run.output.contains("Cache is disabled, skipping cache") } + + def "updating an AT after cache run should work."() { + given: + def project = create("userdev_supports_ats_from_file", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') + + dependencies { + implementation 'net.neoforged:neoforge:+' + } + """) + it.file("src/main/resources/META-INF/accesstransformer.cfg", """public-f net.minecraft.client.Minecraft fixerUpper # fixerUpper""") + it.file("src/main/java/net/neoforged/gradle/userdev/FunctionalTests.java", """ + package net.neoforged.gradle.userdev; + + import net.minecraft.client.Minecraft; + + public class FunctionalTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().fixerUpper.getClass().toString()); + } + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + it.property(CentralCacheService.LOG_CACHE_HITS_PROPERTY, "true") + it.property(CentralCacheService.DEBUG_CACHE_PROPERTY, "true") + }) + + when: + def initialRun = project.run { + it.tasks('build') + } + + then: + initialRun.task(":neoFormRecompile").outcome == TaskOutcome.SUCCESS + initialRun.task(":build").outcome == TaskOutcome.SUCCESS + + when: + File atFile = initialRun.file("src/main/resources/META-INF/accesstransformer.cfg") + atFile.delete() + atFile << """public-f net.minecraft.client.Minecraft REGIONAL_COMPLIANCIES # REGIONAL_COMPLIANCIES""" + + File codeFile = initialRun.file("src/main/java/net/neoforged/gradle/userdev/FunctionalTests.java") + codeFile.delete() + codeFile << """ + package net.neoforged.gradle.userdev; + + import net.minecraft.client.Minecraft; + + public class FunctionalTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().REGIONAL_COMPLIANCIES.getClass().toString()); + } + } + """ + + def secondRun = project.run { + it.tasks('build') + } + + then: + secondRun.task(":neoFormRecompile").outcome == TaskOutcome.SUCCESS + secondRun.task(":build").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 ccc1128eb..ffa65a2e0 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/MultiProjectTests.groovy @@ -100,10 +100,11 @@ 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" + - "\tfml.modloading.brokenfile\n" + - "]")//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:\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. } def "multiple projects with neoforge dependencies should run using the central cache"() { @@ -641,7 +642,6 @@ class MultiProjectTests extends BuilderBasedTestSpecification { """) it.withToolchains() it.withGlobalCacheDirectory(tempDir) - it.disableConventions() }) create(rootProject, "api", { @@ -736,4 +736,102 @@ class MultiProjectTests extends BuilderBasedTestSpecification { modSourcesSection.get(2) == " - main" modSourcesSection.get(3) == " - main" } + + + def "multiple projects with neoforge dependencies should run when parallel is enabled"() { + given: + def rootProject = create("multi_neoforge_root_cached", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + """) + it.withGlobalCacheDirectory(tempDir) + it.withToolchains() + it.enableGradleParallelRunning() + }) + + def apiProject = create(rootProject, "api", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + dependencies { + implementation 'net.neoforged:neoforge:+' + } + """) + it.file("src/main/java/net/neoforged/gradle/apitest/FunctionalTests.java", """ + package net.neoforged.gradle.apitest; + + import net.minecraft.client.Minecraft; + + public class FunctionalTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().getClass().toString()); + } + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + it.plugin(this.pluginUnderTest) + }) + + def mainProject = create(rootProject,"main", { + it.build(""" + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + dependencies { + implementation 'net.neoforged:neoforge:+' + implementation project(':api') + } + + runs { + client { + modSource project(':api').sourceSets.main + } + } + """) + it.file("src/main/java/net/neoforged/gradle/main/ApiTests.java", """ + package net.neoforged.gradle.main; + + import net.minecraft.client.Minecraft; + import net.neoforged.gradle.apitest.FunctionalTests; + + public class ApiTests { + public static void main(String[] args) { + System.out.println(Minecraft.getInstance().getClass().toString()); + FunctionalTests.main(args); + } + } + """) + it.withToolchains() + it.withGlobalCacheDirectory(tempDir) + it.plugin(this.pluginUnderTest) + }) + + when: + def run = rootProject.run { + it.tasks(':main:build') + it.stacktrace() + } + + then: + run.task(':main:build').outcome == TaskOutcome.SUCCESS + 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 + + if (run.task(':api:neoFormDecompile').outcome == TaskOutcome.SUCCESS) + run.task(':main:neoFormDecompile').outcome == TaskOutcome.UP_TO_DATE + else if (run.task(':main:neoFormDecompile').outcome == TaskOutcome.SUCCESS) + run.task(':api:neoFormDecompile').outcome == TaskOutcome.UP_TO_DATE + } } 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 2b3339039..f9825caa1 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/RunTests.groovy @@ -60,10 +60,11 @@ 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" + - "\tfml.modloading.brokenfile\n" + - "]")//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:\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. } def "runs can be declared before the dependencies block"() { diff --git a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy index b62e89ae1..f2cc66354 100644 --- a/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy +++ b/userdev/src/functionalTest/groovy/net/neoforged/gradle/userdev/convention/SourceSetConventionTests.groovy @@ -248,7 +248,7 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { } then: - run.output.contains("Run sources: []") + run.output.contains("Run: client has no source sets configured. Please configure at least one source set.") } def "disabling sourceset conventions prevents registration of main sourceset to run"() { @@ -289,7 +289,7 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { } then: - run.output.contains("Run sources: []") + run.output.contains("Run: client has no source sets configured. Please configure at least one source set.") } def "disabling main source set registration conventions prevents registration of main sourceset to run"() { @@ -330,7 +330,7 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { } then: - run.output.contains("Run sources: []") + run.output.contains("Run: client has no source sets configured. Please configure at least one source set.") } def "having the conventions for main sourceset registration enabled registers it"() { @@ -356,7 +356,7 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { } afterEvaluate { - logger.lifecycle("Run sources: \${project.runs.client.modSources.get()}") + logger.lifecycle("Run sources: \${project.runs.client.modSources.all().get()}") } """) it.withToolchains() @@ -370,7 +370,7 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { then: run.task(':dependencies').outcome == TaskOutcome.SUCCESS - run.output.contains("Run sources: [source set 'main']") + run.output.contains("Run sources: {registration_enabled=[source set 'main']}") } def "disabling sourceset local run runtime registration conventions prevents registration of localRunRuntime"() { @@ -392,10 +392,6 @@ class SourceSetConventionTests extends BuilderBasedTestSpecification { implementation 'net.neoforged:neoforge:+' localRunRuntime 'org.jgrapht:jgrapht-core:+' } - - runs { - client { } - } """) it.withToolchains() it.withGlobalCacheDirectory(tempDir) diff --git a/utils/src/main/java/net/neoforged/gradle/util/StringCapitalizationUtils.java b/utils/src/main/java/net/neoforged/gradle/util/StringCapitalizationUtils.java index 140e23440..d36d88da2 100644 --- a/utils/src/main/java/net/neoforged/gradle/util/StringCapitalizationUtils.java +++ b/utils/src/main/java/net/neoforged/gradle/util/StringCapitalizationUtils.java @@ -1,6 +1,7 @@ package net.neoforged.gradle.util; import javax.annotation.Nonnull; +import java.util.Locale; /** * Util class for handling string capitalization @@ -13,11 +14,11 @@ private StringCapitalizationUtils() { @Nonnull public static String capitalize(@Nonnull final String toCapitalize) { - return toCapitalize.length() > 1 ? toCapitalize.substring(0, 1).toUpperCase() + toCapitalize.substring(1) : toCapitalize; + return toCapitalize.length() > 1 ? toCapitalize.substring(0, 1).toUpperCase(Locale.ROOT) + toCapitalize.substring(1) : toCapitalize; } @Nonnull public static String deCapitalize(@Nonnull final String toCapitalize) { - return toCapitalize.length() > 1 ? toCapitalize.substring(0, 1).toLowerCase() + toCapitalize.substring(1) : toCapitalize; + return toCapitalize.length() > 1 ? toCapitalize.substring(0, 1).toLowerCase(Locale.ROOT) + toCapitalize.substring(1) : toCapitalize; } } diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java index 07b0b6238..40f1ce60d 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/VanillaRuntimeDefinition.java @@ -79,7 +79,7 @@ protected Map buildRunInterpolationData(RunImpl run) { final String fgVersion = this.getClass().getPackage().getImplementationVersion(); interpolationData.put(InterpolationConstants.VERSION_NAME, getSpecification().getMinecraftVersion()); - interpolationData.put(InterpolationConstants.ASSETS_ROOT, getAssets().get().getOutputDirectory().get().getAsFile().getAbsolutePath()); + interpolationData.put(InterpolationConstants.ASSETS_ROOT, DownloadAssets.getAssetsDirectory(run.getProject(), run.getProject().provider(this::getVersionJson)).get().getAsFile().getAbsolutePath()); interpolationData.put(InterpolationConstants.ASSETS_INDEX_NAME, getAssets().get().getAssetIndexFile().get().getAsFile().getName().substring(0, getAssets().get().getAssetIndexFile().get().getAsFile().getName().lastIndexOf('.'))); interpolationData.put(InterpolationConstants.AUTH_ACCESS_TOKEN, "0"); interpolationData.put(InterpolationConstants.USER_TYPE, "legacy"); diff --git a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/ParchmentStep.java b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/ParchmentStep.java index 5c4793a2d..2fff73a37 100644 --- a/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/ParchmentStep.java +++ b/vanilla/src/main/java/net/neoforged/gradle/vanilla/runtime/steps/ParchmentStep.java @@ -14,6 +14,8 @@ import net.neoforged.gradle.vanilla.runtime.VanillaRuntimeDefinition; import net.neoforged.gradle.vanilla.runtime.spec.VanillaRuntimeSpecification; import org.gradle.api.Project; +import org.gradle.api.Transformer; +import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; @@ -58,17 +60,24 @@ private static TaskProvider maybeApplyParchment(VanillaRuntim File mappingFile = ToolUtilities.resolveTool(project, parchment.getParchmentArtifact().get()); File toolExecutable = ToolUtilities.resolveTool(project, tools.getJST().get()); - task.getInputs().file(mappingFile); - task.getInputs().file(inputProvidingTask.flatMap(WithOutput::getOutput)); - task.getInputs().file(listLibrariesOutput); + task.getArguments().putFile("mappings", project.provider(() -> mappingFile)); + task.getArguments().putRegularFile("libraries", listLibrariesOutput); + task.getArguments().putRegularFile("input", inputProvidingTask.flatMap(WithOutput::getOutput)); + task.getExecutingJar().set(toolExecutable); - task.getProgramArguments().add(listLibrariesOutput.map(f -> "--libraries-list=" + f.getAsFile().getAbsolutePath())); + task.getProgramArguments().add("--libraries-list"); + task.getProgramArguments().add("{libraries}"); task.getProgramArguments().add("--enable-parchment"); - task.getProgramArguments().add("--parchment-mappings=" + mappingFile.getAbsolutePath()); + task.getProgramArguments().add("--parchment-mappings"); + task.getProgramArguments().add("{mappings}"); task.getProgramArguments().add("--in-format=archive"); task.getProgramArguments().add("--out-format=archive"); - task.getProgramArguments().add(inputProvidingTask.flatMap(WithOutput::getOutput).map(f -> f.getAsFile().getAbsolutePath())); + task.getProgramArguments().add("{input}"); task.getProgramArguments().add("{output}"); + + task.dependsOn(inputProvidingTask); + task.dependsOn(listLibrariesOutput); + configureCommonRuntimeTaskParameters(task, Maps.newHashMap(), "applyParchment", spec, vanillaDirectory); }); }