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 2 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 @@ -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,35 @@
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -87,6 +90,20 @@ interface RunSourceSets extends ConfigurableDSLElement<RunSourceSets> {
*/
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<SourceSet> getPrimary();

/**
* The source sets attached to this run
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -89,53 +91,6 @@ class CentralCacheTests extends BuilderBasedTestSpecification {
cacheDir.listFiles().size() == 4
}

def "cache_supports_running_gradle_in_parallel"() {
marchermans marked this conversation as resolved.
Show resolved Hide resolved
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<Integer, CompletableFuture<Runtime.RunResult>> 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<Runtime.RunResult>[] 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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", {
Expand Down Expand Up @@ -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
}
}
Loading
Loading