Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix]: Feature/primary sourceset selector #216

Merged
merged 6 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
sciwhiz12 marked this conversation as resolved.
Show resolved Hide resolved
//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
Loading