mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Add XVFB support for headless client execution (#1432)
* Implement XVFB support * Remove unused import * Fix test * Fix test v2 * Explicitly install xvfb into the test environment * Rework xfvb execution * Fix compile error * Fix compile error v2 * We love testing with github ci * Fix code-style Build time speedup * Build time speedup * Fix java executable access Fix caching * Fix styling * Fix xvfb again * Fix xvfb again again * Fix xvfb again again again * Revert mistaken change * Fix MC-DEV * Update src/test/groovy/net/fabricmc/loom/test/integration/XvfbRunTest.groovy Co-authored-by: modmuss <modmuss50@gmail.com> * Cleanup * Fix test * Implement recommendations * Implement recommendations v2 * Fix left over code --------- Co-authored-by: modmuss <modmuss50@gmail.com>
This commit is contained in:
@@ -224,6 +224,8 @@ public interface LoomGradleExtensionAPI {
|
||||
|
||||
/**
|
||||
* Returns the tiny mappings file used to remap the game and mods.
|
||||
*
|
||||
* @return the mappings file, or null if in a non-obfuscated environment
|
||||
*/
|
||||
File getMappingsFile();
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
|
||||
@Override
|
||||
public File getMappingsFile() {
|
||||
if (notObfuscated()) {
|
||||
throw new UnsupportedOperationException("Cannot get mappings file in a non-obfuscated environment");
|
||||
return null;
|
||||
}
|
||||
|
||||
return LoomGradleExtension.get(getProject()).getMappingConfiguration().tinyMappings.toFile();
|
||||
|
||||
@@ -37,6 +37,8 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
@@ -48,6 +50,7 @@ import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.process.ExecOperations;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -55,11 +58,15 @@ import org.slf4j.LoggerFactory;
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.ide.RunConfig;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.Platform;
|
||||
|
||||
public abstract class AbstractRunTask extends JavaExec {
|
||||
private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRunTask.class);
|
||||
|
||||
@Inject
|
||||
protected abstract ExecOperations getExecOperations();
|
||||
|
||||
@Input
|
||||
protected abstract Property<String> getInternalRunDir();
|
||||
@Input
|
||||
@@ -73,6 +80,8 @@ public abstract class AbstractRunTask extends JavaExec {
|
||||
@Input
|
||||
// We use a string here, as it's technically an output, but we don't want to cache runs of this task by default.
|
||||
protected abstract Property<String> getArgFilePath();
|
||||
@Input
|
||||
protected abstract Property<Boolean> getUseXvfb();
|
||||
|
||||
// We control the classpath, as we use a ArgFile to pass it over the command line: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile
|
||||
@InputFiles
|
||||
@@ -100,6 +109,13 @@ public abstract class AbstractRunTask extends JavaExec {
|
||||
getUseArgFile().set(getProject().provider(this::canUseArgFile));
|
||||
getProjectDir().set(getProject().getProjectDir().getAbsolutePath());
|
||||
|
||||
// Set up useXvfb: convention is CI + Linux
|
||||
getUseXvfb().convention(
|
||||
getProject().getProviders().environmentVariable("CI")
|
||||
.map(value -> Platform.CURRENT.getOperatingSystem().isLinux())
|
||||
.orElse(false)
|
||||
);
|
||||
|
||||
File buildCache = LoomGradleExtension.get(getProject()).getFiles().getProjectBuildCache();
|
||||
File argFile = new File(buildCache, "argFiles/" + getName());
|
||||
getArgFilePath().set(argFile.getAbsolutePath());
|
||||
@@ -135,7 +151,34 @@ public abstract class AbstractRunTask extends JavaExec {
|
||||
setWorkingDir(new File(getProjectDir().get(), getInternalRunDir().get()));
|
||||
environment(getInternalEnvironmentVars().get());
|
||||
|
||||
super.exec();
|
||||
// Wrap with XVFB if enabled and on Linux
|
||||
if (getUseXvfb().get()) {
|
||||
LOGGER.info("Using XVFB for headless client execution");
|
||||
execWithXvfb();
|
||||
} else {
|
||||
super.exec();
|
||||
}
|
||||
}
|
||||
|
||||
private void execWithXvfb() {
|
||||
String xvfbRunPath = "/usr/bin/xvfb-run";
|
||||
|
||||
String javaExec = getJavaLauncher().get().getExecutablePath().getAsFile().getAbsolutePath();
|
||||
|
||||
// Build the complete command line: xvfb-run --auto-servernum java [jvm-args] mainclass [program-args]
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(xvfbRunPath);
|
||||
commandLine.add("--auto-servernum");
|
||||
commandLine.add(javaExec);
|
||||
commandLine.addAll(getJvmArguments().get());
|
||||
commandLine.add(getMainClass().get());
|
||||
commandLine.addAll(getArgs());
|
||||
|
||||
getExecOperations().exec(execSpec -> {
|
||||
execSpec.setCommandLine(commandLine);
|
||||
execSpec.setWorkingDir(getWorkingDir());
|
||||
execSpec.setEnvironment(getEnvironment());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -208,4 +208,41 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait {
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
|
||||
@Unroll
|
||||
@IgnoreIf({ !os.linux })
|
||||
def "client game tests with XVFB (gradle #version)"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.4"
|
||||
mappings "net.fabricmc:yarn:1.21.4+build.4:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.16.9"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4"
|
||||
}
|
||||
|
||||
fabricApi {
|
||||
configureTests {
|
||||
createSourceSet = true
|
||||
modId = "example-test"
|
||||
eula = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("runClientGameTest") {
|
||||
useXvfb.set(true)
|
||||
}
|
||||
'''
|
||||
when:
|
||||
def result = gradle.run(task: "runClientGameTest")
|
||||
def eula = new File(gradle.projectDir, "build/run/clientGameTest/eula.txt")
|
||||
|
||||
then:
|
||||
result.task(":runClientGameTest").outcome == SUCCESS
|
||||
eula.text.contains("eula=true")
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user