Skip to content

Commit

Permalink
[Fix]: Feature/primary sourceset selector (#216)
Browse files Browse the repository at this point in the history
  • Loading branch information
marchermans authored Jun 24, 2024
1 parent 465aaa4 commit 4c76373
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -374,7 +374,7 @@ public void hash(TaskInputs inputs) throws IOException {
});

for (File file : inputs.getFiles()) {
try(Stream<Path> pathStream = Files.walk(file.toPath())) {
try (Stream<Path> 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());
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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<String, OwnerAwareReentrantLock> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,6 +83,12 @@ public void add(String groupId, SourceSet... sourceSets) {
}
}

@DSLProperty
@Input
@Optional
@Override
public abstract Property<SourceSet> getPrimary();

@Override
public Provider<Multimap<String, SourceSet>> all() {
return this.provider;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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<SourceSet> currentProjectsModSources = run.getModSources().all().get().values()
.stream()
Expand Down
Loading

0 comments on commit 4c76373

Please sign in to comment.