mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Parallelized mod builds
- Mod targets run through bounded Gradle workers with separate caches and daemon slots - Target logs retain verification failures and bounded output summaries - Nested tests use bounded forks and JVM thread counts - Lifecycle tests control deadlines through injected clocks
This commit is contained in:
1 parent
d7059d84a4
commit
fc810221bb
17 files changed
+447
-61
No files matched your search
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import net.minecraftforge.gradle.shadow.net.minecraftforge.gradleutils.shared.EnhancedPlugin
|
||||||
import org.gradle.api.file.DeleteSpec
|
import org.gradle.api.file.DeleteSpec
|
||||||
import org.gradle.api.file.FileCopyDetails
|
import org.gradle.api.file.FileCopyDetails
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
@@ -62,6 +63,14 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
|
|||||||
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.3.5'))
|
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.3.5'))
|
||||||
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3'))
|
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3'))
|
||||||
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(irisModTarget.forge.toString())
|
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(irisModTarget.forge.toString())
|
||||||
|
String buildLane = gradle.extensions.extraProperties.has('irisBuildLane')
|
||||||
|
? gradle.extensions.extraProperties.get('irisBuildLane').toString() : null
|
||||||
|
if (buildLane != null) {
|
||||||
|
plugins.withType(EnhancedPlugin).configureEach { EnhancedPlugin<?> forgePlugin ->
|
||||||
|
forgePlugin.globalCaches().set(layout.projectDirectory.dir(".gradle/iris-lanes/${buildLane}/forgegradle"))
|
||||||
|
}
|
||||||
|
minecraft.mavenizerOutput.set(layout.projectDirectory.dir(".gradle/iris-lanes/${buildLane}/mavenizer/repo"))
|
||||||
|
}
|
||||||
Closure<String> loaderDisplayVersion = { String loaderVersion ->
|
Closure<String> loaderDisplayVersion = { String loaderVersion ->
|
||||||
String coordinatePrefix = "${minecraftVersion}-"
|
String coordinatePrefix = "${minecraftVersion}-"
|
||||||
if (loaderVersion.startsWith(coordinatePrefix)) {
|
if (loaderVersion.startsWith(coordinatePrefix)) {
|
||||||
@@ -161,7 +170,9 @@ configurations.testRuntimeClasspath.extendsFrom(configurations.runtimeClasspath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation minecraft.dependency("net.minecraftforge:forge:${forgeVersion}")
|
implementation(buildLane == null
|
||||||
|
? minecraft.dependency("net.minecraftforge:forge:${forgeVersion}")
|
||||||
|
: minecraft.dependency("iris-${buildLane}", "net.minecraftforge:forge:${forgeVersion}"))
|
||||||
testImplementation('junit:junit:4.13.2')
|
testImplementation('junit:junit:4.13.2')
|
||||||
testImplementation('org.mockito:mockito-core:5.23.0')
|
testImplementation('org.mockito:mockito-core:5.23.0')
|
||||||
testImplementation(testFixtures('art.arcane:core'))
|
testImplementation(testFixtures('art.arcane:core'))
|
||||||
|
|||||||
+12
-4
@@ -30,6 +30,7 @@ import art.arcane.iris.generation.runtime.Engine;
|
|||||||
import art.arcane.iris.modded.ModdedGenPool;
|
import art.arcane.iris.modded.ModdedGenPool;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
@@ -39,6 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
public final class ModdedPregenMethod implements PregeneratorMethod {
|
public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||||
private static final int ADAPTIVE_TIMEOUT_STEP = 3;
|
private static final int ADAPTIVE_TIMEOUT_STEP = 3;
|
||||||
@@ -68,8 +70,14 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
|||||||
private final int timeoutSeconds;
|
private final int timeoutSeconds;
|
||||||
private final PregenMantleBackpressure backpressure;
|
private final PregenMantleBackpressure backpressure;
|
||||||
private final PauseWhenEmptyGuard pauseGuard;
|
private final PauseWhenEmptyGuard pauseGuard;
|
||||||
|
private final LongSupplier clock;
|
||||||
|
|
||||||
public ModdedPregenMethod(Configuration configuration) {
|
public ModdedPregenMethod(Configuration configuration) {
|
||||||
|
this(configuration, System::nanoTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModdedPregenMethod(Configuration configuration, LongSupplier clock) {
|
||||||
|
this.clock = Objects.requireNonNull(clock);
|
||||||
runtime = configuration.runtime();
|
runtime = configuration.runtime();
|
||||||
engine = configuration.engine();
|
engine = configuration.engine();
|
||||||
sync = configuration.sync();
|
sync = configuration.sync();
|
||||||
@@ -178,7 +186,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
|||||||
if (sync || runtime.isServerThread()) {
|
if (sync || runtime.isServerThread()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds)
|
long deadline = clock.getAsLong() + TimeUnit.SECONDS.toNanos(timeoutSeconds)
|
||||||
+ TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
+ TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
||||||
boolean interrupted = false;
|
boolean interrupted = false;
|
||||||
try {
|
try {
|
||||||
@@ -186,7 +194,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
|||||||
if (finalSaveOnServerThread.get() || !runtime.isRunning()) {
|
if (finalSaveOnServerThread.get() || !runtime.isRunning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (System.nanoTime() >= deadline) {
|
if (clock.getAsLong() >= deadline) {
|
||||||
throw new IllegalStateException("Iris pregen chunk callbacks did not finish for "
|
throw new IllegalStateException("Iris pregen chunk callbacks did not finish for "
|
||||||
+ runtime.worldIdentity() + ": " + inFlight.get() + " pending");
|
+ runtime.worldIdentity() + ": " + inFlight.get() + " pending");
|
||||||
}
|
}
|
||||||
@@ -263,14 +271,14 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void awaitFinalSave(FinalSaveRequest request) {
|
private void awaitFinalSave(FinalSaveRequest request) {
|
||||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
long deadline = clock.getAsLong() + TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
||||||
boolean interrupted = false;
|
boolean interrupted = false;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (deferFinalSaveIfRequested()) {
|
if (deferFinalSaveIfRequested()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long remainingNanos = deadline - System.nanoTime();
|
long remainingNanos = deadline - clock.getAsLong();
|
||||||
if (remainingNanos <= 0L) {
|
if (remainingNanos <= 0L) {
|
||||||
ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", runtime.worldIdentity());
|
ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", runtime.worldIdentity());
|
||||||
return;
|
return;
|
||||||
|
|||||||
+17
-7
@@ -21,6 +21,7 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -97,6 +98,9 @@ public class ModdedPregenMethodLifecycleTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void finalSaveWaitsForEveryCallbackBeyondFiveSeconds() throws Exception {
|
public void finalSaveWaitsForEveryCallbackBeyondFiveSeconds() throws Exception {
|
||||||
|
AtomicLong clock = new AtomicLong();
|
||||||
|
CountDownLatch started = new CountDownLatch(1);
|
||||||
|
CountDownLatch advanced = new CountDownLatch(2);
|
||||||
CompletableFuture<ChunkLoad> load = new CompletableFuture<>();
|
CompletableFuture<ChunkLoad> load = new CompletableFuture<>();
|
||||||
CompletableFuture<ChunkLoad> failedLoad = new CompletableFuture<>();
|
CompletableFuture<ChunkLoad> failedLoad = new CompletableFuture<>();
|
||||||
PregenListener listener = mock(PregenListener.class);
|
PregenListener listener = mock(PregenListener.class);
|
||||||
@@ -110,16 +114,22 @@ public class ModdedPregenMethodLifecycleTest {
|
|||||||
}).when(runtime).execute(any());
|
}).when(runtime).execute(any());
|
||||||
try (MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
try (MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
||||||
pressure.when(MantleHeapPressure::memoryBudgetBytes).thenReturn(512L << 20);
|
pressure.when(MantleHeapPressure::memoryBudgetBytes).thenReturn(512L << 20);
|
||||||
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, false));
|
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, false), () -> {
|
||||||
|
long now = clock.get();
|
||||||
|
if (now >= TimeUnit.SECONDS.toNanos(6)) {
|
||||||
|
advanced.countDown();
|
||||||
|
} else {
|
||||||
|
started.countDown();
|
||||||
|
}
|
||||||
|
return now;
|
||||||
|
});
|
||||||
method.generateChunk(2, 3, listener);
|
method.generateChunk(2, 3, listener);
|
||||||
method.generateChunk(4, 5, listener);
|
method.generateChunk(4, 5, listener);
|
||||||
CountDownLatch started = new CountDownLatch(1);
|
Future<?> closing = executor.submit(method::close);
|
||||||
Future<?> closing = executor.submit(() -> {
|
|
||||||
started.countDown();
|
|
||||||
method.close();
|
|
||||||
});
|
|
||||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||||
assertThrows(TimeoutException.class, () -> closing.get(6, TimeUnit.SECONDS));
|
clock.set(TimeUnit.SECONDS.toNanos(6));
|
||||||
|
assertTrue(advanced.await(5, TimeUnit.SECONDS));
|
||||||
|
assertFalse(closing.isDone());
|
||||||
verify(runtime, never()).save();
|
verify(runtime, never()).save();
|
||||||
verify(engine, never()).syncGenerationHistory();
|
verify(engine, never()).syncGenerationHistory();
|
||||||
load.complete(new ChunkLoad(true, null));
|
load.complete(new ChunkLoad(true, null));
|
||||||
|
|||||||
+71
-38
@@ -329,7 +329,12 @@ List<Object> moddedSharedInputs = []
|
|||||||
'gradle/mod-target.gradle',
|
'gradle/mod-target.gradle',
|
||||||
'gradle/mod-targets.json',
|
'gradle/mod-targets.json',
|
||||||
'gradle/runtime-cache.settings.gradle',
|
'gradle/runtime-cache.settings.gradle',
|
||||||
'gradle/modded-version-sources.gradle'
|
'gradle/nested-build.init.gradle',
|
||||||
|
'gradle/modded-version-sources.gradle',
|
||||||
|
'gradle/wrapper/gradle-wrapper.properties',
|
||||||
|
'gradle/wrapper/gradle-wrapper.jar',
|
||||||
|
'gradlew',
|
||||||
|
'gradlew.bat'
|
||||||
].each { String path -> moddedSharedInputs.add(layout.projectDirectory.file(path).asFile) }
|
].each { String path -> moddedSharedInputs.add(layout.projectDirectory.file(path).asFile) }
|
||||||
if (localVolmLibCheckout != null) {
|
if (localVolmLibCheckout != null) {
|
||||||
moddedSharedInputs.add(fileTree(localVolmLibCheckout) {
|
moddedSharedInputs.add(fileTree(localVolmLibCheckout) {
|
||||||
@@ -343,11 +348,11 @@ if (localVolmLibCheckout != null) {
|
|||||||
|
|
||||||
// The published master-SNAPSHOT is resolved inside the nested build, so no outer input changes when it
|
// The published master-SNAPSHOT is resolved inside the nested build, so no outer input changes when it
|
||||||
// moves. Without a local checkout the Exec tasks always run and the nested build decides what is stale.
|
// moves. Without a local checkout the Exec tasks always run and the nested build decides what is stale.
|
||||||
Closure<Void> declareVolmLibInputs = { Exec task ->
|
Closure<Void> declareVolmLibInputs = { IrisModdedBuild task ->
|
||||||
task.inputs.property('volmLibStamp', volmLibStamp)
|
task.inputs.property('volmLibStamp', volmLibStamp)
|
||||||
String nativeBillow = providers.gradleProperty('includeNativeBillow').getOrElse('false')
|
String nativeBillow = providers.gradleProperty('includeNativeBillow').getOrElse('false')
|
||||||
task.inputs.property('includeNativeBillow', nativeBillow)
|
task.inputs.property('includeNativeBillow', nativeBillow)
|
||||||
task.environment('ORG_GRADLE_PROJECT_includeNativeBillow', nativeBillow)
|
task.environment.put('ORG_GRADLE_PROJECT_includeNativeBillow', nativeBillow)
|
||||||
if (localVolmLibCheckout == null) {
|
if (localVolmLibCheckout == null) {
|
||||||
task.outputs.upToDateWhen { false }
|
task.outputs.upToDateWhen { false }
|
||||||
}
|
}
|
||||||
@@ -413,9 +418,29 @@ TaskProvider<Task> verifyModdedMatrix = tasks.register('verifyModdedMatrix') {
|
|||||||
description = 'Builds, tests and verifies the Fabric, Forge and NeoForge jars of every supported Minecraft mod target.'
|
description = 'Builds, tests and verifies the Fabric, Forge and NeoForge jars of every supported Minecraft mod target.'
|
||||||
}
|
}
|
||||||
List<TaskProvider<Copy>> moddedExports = []
|
List<TaskProvider<Copy>> moddedExports = []
|
||||||
Map<String, TaskProvider<Exec>> selectedModdedBuilds = [:]
|
Map<String, TaskProvider<IrisModdedBuild>> selectedModdedBuilds = [:]
|
||||||
Map<String, TaskProvider<Copy>> selectedModdedExports = [:]
|
Map<String, TaskProvider<Copy>> selectedModdedExports = [:]
|
||||||
TaskProvider<Exec> previousMatrixBuild = null
|
int matrixParallelism = Math.min(gradle.startParameter.maxWorkerCount, Math.max(1,
|
||||||
|
providers.gradleProperty('moddedBuildParallelism').getOrElse(
|
||||||
|
Math.min(12, Math.max(1, Runtime.runtime.availableProcessors() - 4)).toString()).toInteger()))
|
||||||
|
Provider<IrisModdedBuildService> matrixWorkers = gradle.sharedServices.registerIfAbsent(
|
||||||
|
'irisModdedBuilds', IrisModdedBuildService) {
|
||||||
|
maxParallelUsages.set(matrixParallelism)
|
||||||
|
}
|
||||||
|
List<String> nestedBuildArguments = [gradle.startParameter.buildCacheEnabled ? '--build-cache' : '--no-build-cache']
|
||||||
|
String nestedJvmArguments = providers.gradleProperty('org.gradle.jvmargs').getOrElse('') +
|
||||||
|
' -XX:ActiveProcessorCount=2'
|
||||||
|
if (gradle.startParameter.rerunTasks) {
|
||||||
|
nestedBuildArguments.add('--rerun-tasks')
|
||||||
|
}
|
||||||
|
if (gradle.startParameter.refreshDependencies) {
|
||||||
|
nestedBuildArguments.add('--refresh-dependencies')
|
||||||
|
}
|
||||||
|
for (File initScript : gradle.startParameter.initScripts) {
|
||||||
|
nestedBuildArguments.addAll(['--init-script', initScript.absolutePath])
|
||||||
|
}
|
||||||
|
Map<Integer, TaskProvider<IrisModdedBuild>> daemonSlots = [:]
|
||||||
|
int nextDaemonSlot = 0
|
||||||
irisModTargets.each { String targetMinecraft, Map<String, Object> target ->
|
irisModTargets.each { String targetMinecraft, Map<String, Object> target ->
|
||||||
String suffix = targetMinecraft.replace('.', '_')
|
String suffix = targetMinecraft.replace('.', '_')
|
||||||
boolean selected = targetMinecraft == minecraftVersion
|
boolean selected = targetMinecraft == minecraftVersion
|
||||||
@@ -426,46 +451,66 @@ irisModTargets.each { String targetMinecraft, Map<String, Object> target ->
|
|||||||
String artifactName = irisArtifactName(label, "${targetMinecraft}+${displayedLoader}")
|
String artifactName = irisArtifactName(label, "${targetMinecraft}+${displayedLoader}")
|
||||||
File artifact = file("adapters/${loader}/build/${targetMinecraft}/libs/${artifactName}")
|
File artifact = file("adapters/${loader}/build/${targetMinecraft}/libs/${artifactName}")
|
||||||
boolean unitTests = ((List<String>) (target.testLoaders ?: moddedLoaderLabels.keySet().toList())).contains(loader)
|
boolean unitTests = ((List<String>) (target.testLoaders ?: moddedLoaderLabels.keySet().toList())).contains(loader)
|
||||||
TaskProvider<Exec> buildTarget = tasks.register("verify${label}${suffix}", Exec) {
|
int daemonSlot = nextDaemonSlot++ % matrixParallelism
|
||||||
|
TaskProvider<IrisModdedBuild> buildTarget = project(':adapters').tasks.register("verify${label}${suffix}", IrisModdedBuild) {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = "Builds, tests and verifies the ${label} ${targetMinecraft} jar in its own Gradle build."
|
description = "Builds, tests and verifies the ${label} ${targetMinecraft} jar in its own Gradle build."
|
||||||
workingDir = file("adapters/${loader}")
|
usesService(matrixWorkers)
|
||||||
List<String> command = [file(moddedWrapperScript).absolutePath, 'assemble', 'compileTestJava',
|
dependsOn(project(':core').tasks.named('jar'), project(':core').tasks.named('testFixturesJar'))
|
||||||
|
workingDirectory.set(rootProject.file("adapters/${loader}"))
|
||||||
|
getLabel().set("${label} ${targetMinecraft}".toString())
|
||||||
|
artifactFile.set(artifact)
|
||||||
|
logFile.set(rootProject.layout.buildDirectory.file("reports/modded/${loader}-${targetMinecraft}.log"))
|
||||||
|
requiredEntries.set(commonModdedArtifactEntries + moddedLoaderEntries[loader])
|
||||||
|
forbiddenCoreClasses.set(bukkitCoupledCoreClasses)
|
||||||
|
javaRuntime.set((int) target.java)
|
||||||
|
List<String> command = [rootProject.file(moddedWrapperScript).absolutePath, 'assemble', 'compileTestJava',
|
||||||
'verifyLoggingPolicy', 'verifyNativeClassBoundary', '--console=plain',
|
'verifyLoggingPolicy', 'verifyNativeClassBoundary', '--console=plain',
|
||||||
"-PirisVersion=${project.version}", "-PminecraftVersion=${targetMinecraft}",
|
"--max-workers=${Math.min(2, gradle.startParameter.maxWorkerCount)}",
|
||||||
|
"-Dorg.gradle.jvmargs=${nestedJvmArguments.trim()}",
|
||||||
|
"-Dorg.gradle.daemon.registry.base=${rootProject.file(".gradle/iris-daemons/slot-${daemonSlot}").absolutePath}",
|
||||||
|
'-Dorg.gradle.daemon.idletimeout=3600000',
|
||||||
|
"-PirisBuildLane=${loader}-${targetMinecraft}",
|
||||||
|
'--project-cache-dir', rootProject.file("adapters/${loader}/.gradle/iris-lanes/${loader}-${targetMinecraft}").absolutePath,
|
||||||
|
'--init-script', rootProject.file('gradle/nested-build.init.gradle').absolutePath,
|
||||||
|
"-PirisVersion=${rootProject.version}", "-PminecraftVersion=${targetMinecraft}",
|
||||||
"-P${moddedLoaderProperties[loader]}=${loaderPin}",
|
"-P${moddedLoaderProperties[loader]}=${loaderPin}",
|
||||||
"-PuseLocalVolmLib=${localVolmLibCheckout != null}"]
|
"-PuseLocalVolmLib=${localVolmLibCheckout != null}"]
|
||||||
if (unitTests) {
|
if (unitTests) {
|
||||||
command.add('test')
|
command.add('test')
|
||||||
}
|
}
|
||||||
commandLine(command)
|
command.add("-PtestForks=${providers.gradleProperty('moddedTestForks').getOrElse('1')}")
|
||||||
|
command.addAll(nestedBuildArguments)
|
||||||
|
commandLine.set(command.collect { Object argument -> argument.toString() })
|
||||||
if (localVolmLibCheckout != null) {
|
if (localVolmLibCheckout != null) {
|
||||||
environment('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath)
|
environment.put('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath)
|
||||||
}
|
}
|
||||||
inputs.files(moddedSharedInputs).withPropertyName('moddedSharedSources')
|
inputs.files(moddedSharedInputs).withPropertyName('moddedSharedSources')
|
||||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
inputs.files(file("adapters/${loader}/src"), file("adapters/${loader}/build.gradle"),
|
inputs.files(rootProject.file("adapters/${loader}/src"), rootProject.file("adapters/${loader}/build.gradle"),
|
||||||
file("adapters/${loader}/settings.gradle")).withPropertyName('adapterSources')
|
rootProject.file("adapters/${loader}/settings.gradle"), rootProject.file("adapters/${loader}/gradle.properties"))
|
||||||
|
.withPropertyName('adapterSources')
|
||||||
|
inputs.files(gradle.startParameter.allInitScripts).withPropertyName('initScripts')
|
||||||
|
.withPathSensitivity(PathSensitivity.NONE)
|
||||||
|
outputs.upToDateWhen { !gradle.startParameter.refreshDependencies }
|
||||||
inputs.properties(target)
|
inputs.properties(target)
|
||||||
inputs.property('loaderVersion', loaderPin)
|
inputs.property('loaderVersion', loaderPin)
|
||||||
inputs.property('irisVersion', project.version.toString())
|
inputs.property('irisVersion', rootProject.version.toString())
|
||||||
declareVolmLibInputs(it)
|
declareVolmLibInputs(it)
|
||||||
outputs.file(artifact)
|
|
||||||
if (unitTests) {
|
if (unitTests) {
|
||||||
outputs.dir(file("adapters/${loader}/build/${targetMinecraft}/test-results/test"))
|
outputs.dir(rootProject.file("adapters/${loader}/build/${targetMinecraft}/test-results/test"))
|
||||||
}
|
|
||||||
doLast {
|
|
||||||
ModdedArtifactVerifier.verify(artifact, commonModdedArtifactEntries + moddedLoaderEntries[loader],
|
|
||||||
bukkitCoupledCoreClasses)
|
|
||||||
ModdedArtifactVerifier.verifyJavaRuntime(artifact, (int) target.java)
|
|
||||||
logger.lifecycle("Verified ${artifact.name}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (previousMatrixBuild != null) {
|
TaskProvider<IrisModdedBuild> previousSlotBuild = daemonSlots[daemonSlot]
|
||||||
TaskProvider<Exec> preceding = previousMatrixBuild
|
if (previousSlotBuild != null) {
|
||||||
buildTarget.configure { mustRunAfter(preceding) }
|
buildTarget.configure { mustRunAfter(previousSlotBuild) }
|
||||||
|
}
|
||||||
|
daemonSlots[daemonSlot] = buildTarget
|
||||||
|
tasks.register("verify${label}${suffix}") {
|
||||||
|
group = 'verification'
|
||||||
|
description = "Builds, tests and verifies the ${label} ${targetMinecraft} jar."
|
||||||
|
dependsOn(buildTarget)
|
||||||
}
|
}
|
||||||
previousMatrixBuild = buildTarget
|
|
||||||
verifyModdedMatrix.configure { dependsOn(buildTarget) }
|
verifyModdedMatrix.configure { dependsOn(buildTarget) }
|
||||||
File legacyOutJar = new File(pluginOutDirectory, "Iris-${project.version}+mc${targetMinecraft}-${loader}.jar")
|
File legacyOutJar = new File(pluginOutDirectory, "Iris-${project.version}+mc${targetMinecraft}-${loader}.jar")
|
||||||
FileTree staleTargetJars = fileTree(pluginOutDirectory) {
|
FileTree staleTargetJars = fileTree(pluginOutDirectory) {
|
||||||
@@ -512,18 +557,6 @@ tasks.register('buildNeoforge') {
|
|||||||
dependsOn(selectedModdedExports.neoforge)
|
dependsOn(selectedModdedExports.neoforge)
|
||||||
}
|
}
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
|
||||||
List<Task> sharedOutputTasks = allprojects.collectMany { Project candidate ->
|
|
||||||
candidate.tasks.findAll { Task task ->
|
|
||||||
task instanceof JavaCompile || task instanceof Jar || task instanceof Test
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tasks.withType(Exec).matching { Task task -> task.name.startsWith('verify') }.configureEach {
|
|
||||||
mustRunAfter(sharedOutputTasks)
|
|
||||||
mustRunAfter(tasks.named('buildBukkit'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('check').configure {
|
tasks.named('check').configure {
|
||||||
dependsOn(selectedModdedBuilds.fabric)
|
dependsOn(selectedModdedBuilds.fabric)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,14 @@ repositories {
|
|||||||
// Nothing else asks buildSrc for `test`: Gradle only requests the jar that carries the build logic.
|
// Nothing else asks buildSrc for `test`: Gradle only requests the jar that carries the build logic.
|
||||||
// Gating the jar on the tests is what makes them run at all, and it costs nothing once they are green.
|
// Gating the jar on the tests is what makes them run at all, and it costs nothing once they are green.
|
||||||
tasks.named('jar').configure {
|
tasks.named('jar').configure {
|
||||||
dependsOn(tasks.named('test'))
|
if (!gradle.extensions.extraProperties.has('irisBuildLane')) {
|
||||||
|
dependsOn(tasks.named('test'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test').configure {
|
tasks.named('test').configure {
|
||||||
jvmArgs('--add-opens=java.base/java.lang=ALL-UNNAMED')
|
jvmArgs('--add-opens=java.base/java.lang=ALL-UNNAMED')
|
||||||
|
jvmArgs('-XX:ParallelGCThreads=2', '-XX:ConcGCThreads=1', '-XX:CICompilerCount=2')
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.file.DirectoryProperty;
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.provider.ListProperty;
|
||||||
|
import org.gradle.api.provider.MapProperty;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.provider.SetProperty;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.Internal;
|
||||||
|
import org.gradle.api.tasks.LocalState;
|
||||||
|
import org.gradle.api.tasks.OutputFile;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
import org.gradle.workers.WorkerExecutor;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class IrisModdedBuild extends DefaultTask {
|
||||||
|
public IrisModdedBuild() {
|
||||||
|
getEnvironment().convention(Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract Property<String> getLabel();
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public abstract DirectoryProperty getWorkingDirectory();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract ListProperty<String> getCommandLine();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract MapProperty<String, String> getEnvironment();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract ListProperty<String> getRequiredEntries();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract SetProperty<String> getForbiddenCoreClasses();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract Property<Integer> getJavaRuntime();
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
public abstract RegularFileProperty getArtifactFile();
|
||||||
|
|
||||||
|
@LocalState
|
||||||
|
public abstract RegularFileProperty getLogFile();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected abstract WorkerExecutor getWorkerExecutor();
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void build() {
|
||||||
|
getWorkerExecutor().noIsolation().submit(IrisModdedBuildAction.class, parameters -> {
|
||||||
|
parameters.getLabel().set(getLabel());
|
||||||
|
parameters.getWorkingDirectory().set(getWorkingDirectory());
|
||||||
|
parameters.getCommandLine().set(getCommandLine());
|
||||||
|
parameters.getEnvironment().set(getEnvironment());
|
||||||
|
parameters.getRequiredEntries().set(getRequiredEntries());
|
||||||
|
parameters.getForbiddenCoreClasses().set(getForbiddenCoreClasses());
|
||||||
|
parameters.getJavaRuntime().set(getJavaRuntime());
|
||||||
|
parameters.getArtifactFile().set(getArtifactFile());
|
||||||
|
parameters.getLogFile().set(getLogFile());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import org.gradle.api.GradleException;
|
||||||
|
import org.gradle.api.logging.Logger;
|
||||||
|
import org.gradle.api.logging.Logging;
|
||||||
|
import org.gradle.process.ExecOperations;
|
||||||
|
import org.gradle.workers.WorkAction;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public abstract class IrisModdedBuildAction implements WorkAction<IrisModdedBuildParameters> {
|
||||||
|
private static final Logger LOGGER = Logging.getLogger(IrisModdedBuildAction.class);
|
||||||
|
private static final int LOG_TAIL_BYTES = 16 * 1024;
|
||||||
|
private static final int LOG_TAIL_LINES = 40;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected abstract ExecOperations getExecOperations();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
IrisModdedBuildParameters parameters = getParameters();
|
||||||
|
String label = parameters.getLabel().get();
|
||||||
|
Path log = parameters.getLogFile().get().getAsFile().toPath();
|
||||||
|
long started = System.nanoTime();
|
||||||
|
LOGGER.lifecycle("{} started; log: {}", label, log);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(log.getParent());
|
||||||
|
try (OutputStream output = Files.newOutputStream(log)) {
|
||||||
|
getExecOperations().exec(spec -> {
|
||||||
|
spec.setWorkingDir(parameters.getWorkingDirectory().get().getAsFile());
|
||||||
|
spec.commandLine(parameters.getCommandLine().get());
|
||||||
|
spec.environment(parameters.getEnvironment().get());
|
||||||
|
spec.setStandardOutput(output);
|
||||||
|
spec.setErrorOutput(output);
|
||||||
|
}).assertNormalExitValue();
|
||||||
|
}
|
||||||
|
ModdedArtifactVerifier.verify(parameters.getArtifactFile().get().getAsFile(),
|
||||||
|
parameters.getRequiredEntries().get(), parameters.getForbiddenCoreClasses().get());
|
||||||
|
ModdedArtifactVerifier.verifyJavaRuntime(parameters.getArtifactFile().get().getAsFile(),
|
||||||
|
parameters.getJavaRuntime().get());
|
||||||
|
LOGGER.lifecycle("{} succeeded in {} ms", label,
|
||||||
|
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - started));
|
||||||
|
} catch (Exception failure) {
|
||||||
|
appendFailure(log, failure);
|
||||||
|
String details;
|
||||||
|
try {
|
||||||
|
details = logTail(log);
|
||||||
|
} catch (IOException unreadable) {
|
||||||
|
failure.addSuppressed(unreadable);
|
||||||
|
details = "Log could not be read.";
|
||||||
|
}
|
||||||
|
LOGGER.error(label + " failed; log: " + log + System.lineSeparator() + details, failure);
|
||||||
|
throw new GradleException(label + " failed; log: " + log, failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String logTail(Path log) throws IOException {
|
||||||
|
try (RandomAccessFile input = new RandomAccessFile(log.toFile(), "r")) {
|
||||||
|
long offset = Math.max(0L, input.length() - LOG_TAIL_BYTES);
|
||||||
|
input.seek(offset);
|
||||||
|
byte[] bytes = new byte[(int) (input.length() - offset)];
|
||||||
|
input.readFully(bytes);
|
||||||
|
String[] lines = new String(bytes, StandardCharsets.UTF_8).split("\\R");
|
||||||
|
return String.join(System.lineSeparator(), Arrays.copyOfRange(lines,
|
||||||
|
Math.max(0, lines.length - LOG_TAIL_LINES), lines.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendFailure(Path log, Exception failure) {
|
||||||
|
try (PrintWriter output = new PrintWriter(Files.newBufferedWriter(log, StandardCharsets.UTF_8,
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
|
||||||
|
failure.printStackTrace(output);
|
||||||
|
} catch (IOException unwritable) {
|
||||||
|
failure.addSuppressed(unwritable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import org.gradle.api.file.DirectoryProperty;
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.provider.ListProperty;
|
||||||
|
import org.gradle.api.provider.MapProperty;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.provider.SetProperty;
|
||||||
|
import org.gradle.workers.WorkParameters;
|
||||||
|
|
||||||
|
public interface IrisModdedBuildParameters extends WorkParameters {
|
||||||
|
Property<String> getLabel();
|
||||||
|
|
||||||
|
DirectoryProperty getWorkingDirectory();
|
||||||
|
|
||||||
|
ListProperty<String> getCommandLine();
|
||||||
|
|
||||||
|
MapProperty<String, String> getEnvironment();
|
||||||
|
|
||||||
|
ListProperty<String> getRequiredEntries();
|
||||||
|
|
||||||
|
SetProperty<String> getForbiddenCoreClasses();
|
||||||
|
|
||||||
|
Property<Integer> getJavaRuntime();
|
||||||
|
|
||||||
|
RegularFileProperty getArtifactFile();
|
||||||
|
|
||||||
|
RegularFileProperty getLogFile();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import org.gradle.api.services.BuildService;
|
||||||
|
import org.gradle.api.services.BuildServiceParameters;
|
||||||
|
|
||||||
|
public abstract class IrisModdedBuildService implements BuildService<BuildServiceParameters.None> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.testfixtures.ProjectBuilder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class IrisModdedBuildTest {
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporary = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void capturesTheCompleteTargetConfiguration() throws Exception {
|
||||||
|
Project project = ProjectBuilder.builder().withProjectDir(temporary.newFolder()).build();
|
||||||
|
IrisModdedBuild task = project.getTasks().create("verifyFabric", IrisModdedBuild.class);
|
||||||
|
task.getLabel().set("Fabric 26.3");
|
||||||
|
task.getWorkingDirectory().set(project.getLayout().getProjectDirectory());
|
||||||
|
task.getCommandLine().set(List.of("gradlew", "assemble", "test"));
|
||||||
|
task.getRequiredEntries().set(List.of("fabric.mod.json"));
|
||||||
|
task.getForbiddenCoreClasses().set(Set.of("org/bukkit/Test"));
|
||||||
|
task.getJavaRuntime().set(25);
|
||||||
|
task.getArtifactFile().set(project.getLayout().getProjectDirectory().file("build/mod.jar"));
|
||||||
|
task.getLogFile().set(project.getLayout().getProjectDirectory().file("build/reports/matrix/fabric.log"));
|
||||||
|
|
||||||
|
assertEquals(Map.of(), task.getEnvironment().get());
|
||||||
|
assertEquals(List.of("gradlew", "assemble", "test"), task.getCommandLine().get());
|
||||||
|
assertEquals(List.of("fabric.mod.json"), task.getRequiredEntries().get());
|
||||||
|
assertEquals(Set.of("org/bukkit/Test"), task.getForbiddenCoreClasses().get());
|
||||||
|
assertEquals(Integer.valueOf(25), task.getJavaRuntime().get());
|
||||||
|
assertEquals(Set.of(task.getArtifactFile().get().getAsFile()), task.getOutputs().getFiles().getFiles());
|
||||||
|
assertFalse(task.getOutputs().getFiles().contains(task.getLogFile().get().getAsFile()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void failureSummaryRetainsTheFinalLinesWithoutReadingAnEntireLog() throws Exception {
|
||||||
|
Path log = temporary.newFile().toPath();
|
||||||
|
StringBuilder content = new StringBuilder("discarded first line\n");
|
||||||
|
for (int index = 0; index < 2000; index++) {
|
||||||
|
content.append("task output ").append(index).append('\n');
|
||||||
|
}
|
||||||
|
content.append("final failure\n");
|
||||||
|
Files.writeString(log, content);
|
||||||
|
|
||||||
|
String tail = IrisModdedBuildAction.logTail(log);
|
||||||
|
|
||||||
|
assertEquals(40, tail.lines().count());
|
||||||
|
assertTrue(tail.endsWith("final failure"));
|
||||||
|
assertFalse(tail.contains("discarded first line"));
|
||||||
|
assertTrue(tail.length() <= 16 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void failureSummaryAcceptsEmptyAndUnterminatedLogs() throws Exception {
|
||||||
|
Path log = temporary.newFile().toPath();
|
||||||
|
assertEquals("", IrisModdedBuildAction.logTail(log));
|
||||||
|
Files.writeString(log, "first\r\nlast");
|
||||||
|
assertEquals("first" + System.lineSeparator() + "last", IrisModdedBuildAction.logTail(log));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void failureSummaryBoundsAnUnbrokenFailureLine() throws Exception {
|
||||||
|
Path log = temporary.newFile().toPath();
|
||||||
|
Files.writeString(log, "x".repeat(100_000) + "final failure");
|
||||||
|
|
||||||
|
String tail = IrisModdedBuildAction.logTail(log);
|
||||||
|
|
||||||
|
assertEquals(16 * 1024, tail.length());
|
||||||
|
assertTrue(tail.endsWith("final failure"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -33,6 +34,7 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
import static art.arcane.iris.generation.runtime.EngineShutdownSequence.appendFailure;
|
import static art.arcane.iris.generation.runtime.EngineShutdownSequence.appendFailure;
|
||||||
import static art.arcane.iris.generation.runtime.EngineShutdownSequence.propagate;
|
import static art.arcane.iris.generation.runtime.EngineShutdownSequence.propagate;
|
||||||
@@ -47,9 +49,18 @@ final class EngineBackgroundTasks {
|
|||||||
|
|
||||||
private final Object backgroundTaskLock = new Object();
|
private final Object backgroundTaskLock = new Object();
|
||||||
private final List<TrackedBackgroundTask> backgroundTasks = new ArrayList<>();
|
private final List<TrackedBackgroundTask> backgroundTasks = new ArrayList<>();
|
||||||
|
private final LongSupplier clock;
|
||||||
private boolean backgroundTaskAdmission;
|
private boolean backgroundTaskAdmission;
|
||||||
private int activeDrains;
|
private int activeDrains;
|
||||||
|
|
||||||
|
EngineBackgroundTasks() {
|
||||||
|
this(System::nanoTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineBackgroundTasks(LongSupplier clock) {
|
||||||
|
this.clock = Objects.requireNonNull(clock);
|
||||||
|
}
|
||||||
|
|
||||||
boolean scheduleTrackedTask(Runnable task) {
|
boolean scheduleTrackedTask(Runnable task) {
|
||||||
return scheduleTrackedTask(task, false);
|
return scheduleTrackedTask(task, false);
|
||||||
}
|
}
|
||||||
@@ -106,7 +117,7 @@ final class EngineBackgroundTasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BackgroundTaskDrain drainBackgroundTasks(String reason) {
|
BackgroundTaskDrain drainBackgroundTasks(String reason) {
|
||||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(BACKGROUND_TASK_TIMEOUT_MILLIS);
|
long deadline = clock.getAsLong() + TimeUnit.MILLISECONDS.toNanos(BACKGROUND_TASK_TIMEOUT_MILLIS);
|
||||||
Set<TrackedBackgroundTask> observed = Collections.newSetFromMap(new IdentityHashMap<>());
|
Set<TrackedBackgroundTask> observed = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
Set<TrackedBackgroundTask> settledAtEntry = Collections.newSetFromMap(new IdentityHashMap<>());
|
Set<TrackedBackgroundTask> settledAtEntry = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
List<TrackedBackgroundTask> tasks;
|
List<TrackedBackgroundTask> tasks;
|
||||||
@@ -135,7 +146,7 @@ final class EngineBackgroundTasks {
|
|||||||
return new BackgroundTaskDrain(failure, allTasksComplete());
|
return new BackgroundTaskDrain(failure, allTasksComplete());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (System.nanoTime() >= deadline) {
|
if (clock.getAsLong() >= deadline) {
|
||||||
for (TrackedBackgroundTask task : tasks) {
|
for (TrackedBackgroundTask task : tasks) {
|
||||||
cancelBackgroundTask(task, reason);
|
cancelBackgroundTask(task, reason);
|
||||||
}
|
}
|
||||||
@@ -170,7 +181,7 @@ final class EngineBackgroundTasks {
|
|||||||
long deadline, String reason) {
|
long deadline, String reason) {
|
||||||
Throwable failure = null;
|
Throwable failure = null;
|
||||||
for (TrackedBackgroundTask task : tasks) {
|
for (TrackedBackgroundTask task : tasks) {
|
||||||
long remaining = deadline - System.nanoTime();
|
long remaining = deadline - clock.getAsLong();
|
||||||
if (remaining <= 0L) {
|
if (remaining <= 0L) {
|
||||||
cancelBackgroundTask(task, reason);
|
cancelBackgroundTask(task, reason);
|
||||||
failure = appendFailure(failure, new TimeoutException("Timed out waiting for Iris background tasks during " + reason + "."));
|
failure = appendFailure(failure, new TimeoutException("Timed out waiting for Iris background tasks during " + reason + "."));
|
||||||
|
|||||||
+9
-4
@@ -14,6 +14,7 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.LockSupport;
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
@@ -115,7 +116,8 @@ public class EngineBackgroundTasksAdmissionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void continuationsShareTheOriginalDrainDeadline() throws Exception {
|
public void continuationsShareTheOriginalDrainDeadline() throws Exception {
|
||||||
Fixture fixture = fixture();
|
AtomicLong clock = new AtomicLong();
|
||||||
|
Fixture fixture = fixture(new EngineBackgroundTasks(clock::get));
|
||||||
CountDownLatch firstEntered = new CountDownLatch(1);
|
CountDownLatch firstEntered = new CountDownLatch(1);
|
||||||
CountDownLatch firstRelease = new CountDownLatch(1);
|
CountDownLatch firstRelease = new CountDownLatch(1);
|
||||||
CountDownLatch saveEntered = new CountDownLatch(1);
|
CountDownLatch saveEntered = new CountDownLatch(1);
|
||||||
@@ -132,9 +134,9 @@ public class EngineBackgroundTasksAdmissionTest {
|
|||||||
fixture.tasks().scheduleAdmittedTask(fixture.engine(),
|
fixture.tasks().scheduleAdmittedTask(fixture.engine(),
|
||||||
() -> waitForRelease(saveEntered, saveRelease))));
|
() -> waitForRelease(saveEntered, saveRelease))));
|
||||||
assertTrue(saveEntered.await(5, TimeUnit.SECONDS));
|
assertTrue(saveEntered.await(5, TimeUnit.SECONDS));
|
||||||
TimeUnit.SECONDS.sleep(7);
|
clock.set(TimeUnit.SECONDS.toNanos(15) - TimeUnit.MILLISECONDS.toNanos(1));
|
||||||
firstRelease.countDown();
|
firstRelease.countDown();
|
||||||
EngineBackgroundTasks.BackgroundTaskDrain result = drain.get(10, TimeUnit.SECONDS);
|
EngineBackgroundTasks.BackgroundTaskDrain result = drain.get(5, TimeUnit.SECONDS);
|
||||||
assertTrue(result.failure() instanceof TimeoutException);
|
assertTrue(result.failure() instanceof TimeoutException);
|
||||||
assertThrows(IllegalStateException.class, () -> result.requireComplete("Studio cutover"));
|
assertThrows(IllegalStateException.class, () -> result.requireComplete("Studio cutover"));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -287,7 +289,10 @@ public class EngineBackgroundTasksAdmissionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Fixture fixture() {
|
private static Fixture fixture() {
|
||||||
EngineBackgroundTasks tasks = new EngineBackgroundTasks();
|
return fixture(new EngineBackgroundTasks());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Fixture fixture(EngineBackgroundTasks tasks) {
|
||||||
tasks.openBackgroundTaskAdmission();
|
tasks.openBackgroundTaskAdmission();
|
||||||
GenerationSessionManager sessions = new GenerationSessionManager(true);
|
GenerationSessionManager sessions = new GenerationSessionManager(true);
|
||||||
Engine engine = mock(Engine.class);
|
Engine engine = mock(Engine.class);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#
|
#
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
org.gradle.workers.max=32
|
||||||
org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.configureondemand=false
|
org.gradle.configureondemand=false
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ if (coreBuild == null) {
|
|||||||
throw new GradleException('The modded artifact requires the included Iris core build and its generation build revision.')
|
throw new GradleException('The modded artifact requires the included Iris core build and its generation build revision.')
|
||||||
}
|
}
|
||||||
TaskReference buildRevisionVerification = coreBuild.task(':core:verifyGenerationBuildRevision')
|
TaskReference buildRevisionVerification = coreBuild.task(':core:verifyGenerationBuildRevision')
|
||||||
|
String buildLogicVariant = gradle.extensions.extraProperties.has('irisBuildLane')
|
||||||
|
? "iris-lanes/${gradle.extensions.extraProperties.get('irisBuildLane')}/" : ''
|
||||||
TaskProvider<JavaExec> resolvedRevisionVerification = tasks.register('verifyResolvedGenerationKernelDependencies', JavaExec) {
|
TaskProvider<JavaExec> resolvedRevisionVerification = tasks.register('verifyResolvedGenerationKernelDependencies', JavaExec) {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Checks the actual bundled generation library contents against the current build revision.'
|
description = 'Checks the actual bundled generation library contents against the current build revision.'
|
||||||
dependsOn(buildRevisionVerification)
|
dependsOn(buildRevisionVerification)
|
||||||
classpath = files('../../buildSrc/build/libs/buildSrc.jar')
|
classpath = files("../../buildSrc/build/${buildLogicVariant}libs/buildSrc.jar")
|
||||||
mainClass.set('art.arcane.iris.buildtools.GenerationBuildRevision')
|
mainClass.set('art.arcane.iris.buildtools.GenerationBuildRevision')
|
||||||
doFirst {
|
doFirst {
|
||||||
String runtimeVariant = gradle.extensions.extraProperties.get('irisRuntimeOutputVariant').toString()
|
String runtimeVariant = gradle.extensions.extraProperties.get('irisRuntimeOutputVariant').toString()
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ import groovy.json.JsonSlurper
|
|||||||
|
|
||||||
List<String> sourceOverlays = (List<String>) irisModTarget.overlays
|
List<String> sourceOverlays = (List<String>) irisModTarget.overlays
|
||||||
List<File> adapterSourceRoots = [projectDir, file('../modded-common'), file('../client-common')]
|
List<File> adapterSourceRoots = [projectDir, file('../modded-common'), file('../client-common')]
|
||||||
|
int testForks = Math.max(1, Integer.parseInt(providers.gradleProperty('testForks').getOrElse('2')))
|
||||||
|
|
||||||
tasks.named('compileTestJava', JavaCompile).configure {
|
tasks.named('compileTestJava', JavaCompile).configure {
|
||||||
options.release.set((int) irisModTarget.java)
|
options.release.set((int) irisModTarget.java)
|
||||||
}
|
}
|
||||||
tasks.withType(Test).configureEach {
|
tasks.withType(Test).configureEach {
|
||||||
|
maxParallelForks = Math.min(2, Math.min(Runtime.runtime.availableProcessors(),
|
||||||
|
Math.min(gradle.startParameter.maxWorkerCount, testForks)))
|
||||||
|
if (gradle.extensions.extraProperties.has('irisBuildLane')) {
|
||||||
|
jvmArgs('-XX:ParallelGCThreads=2', '-XX:ConcGCThreads=1', '-XX:CICompilerCount=2')
|
||||||
|
}
|
||||||
javaLauncher.set(javaToolchains.launcherFor {
|
javaLauncher.set(javaToolchains.launcherFor {
|
||||||
languageVersion = JavaLanguageVersion.of((int) irisModTarget.java)
|
languageVersion = JavaLanguageVersion.of((int) irisModTarget.java)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
Gradle rootBuild = gradle
|
||||||
|
while (rootBuild.parent != null) {
|
||||||
|
rootBuild = rootBuild.parent
|
||||||
|
}
|
||||||
|
String buildLane = rootBuild.startParameter.projectProperties['irisBuildLane']
|
||||||
|
if (buildLane == null) {
|
||||||
|
throw new GradleException('The nested build requires -PirisBuildLane.')
|
||||||
|
}
|
||||||
|
if (!(buildLane ==~ /[a-zA-Z0-9][a-zA-Z0-9._-]*/)) {
|
||||||
|
throw new GradleException('irisBuildLane must contain only letters, numbers, dots, underscores and hyphens.')
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.extensions.extraProperties.set('irisBuildLane', buildLane)
|
||||||
|
gradle.startParameter.projectCacheDir = new File(gradle.startParameter.currentDir, ".gradle/iris-lanes/${buildLane}")
|
||||||
|
gradle.beforeSettings { Settings settings ->
|
||||||
|
settings.gradle.startParameter.projectCacheDir = new File(settings.settingsDir, ".gradle/iris-lanes/${buildLane}")
|
||||||
|
}
|
||||||
|
gradle.beforeProject { Project candidate ->
|
||||||
|
boolean sharedRuntime = candidate.rootProject.name == 'Iris'
|
||||||
|
&& candidate.path in [':core', ':core:agent', ':spi']
|
||||||
|
if (!candidate.rootProject.name.startsWith('iris-') && !sharedRuntime) {
|
||||||
|
candidate.layout.buildDirectory.set(candidate.layout.projectDirectory.dir("build/iris-lanes/${buildLane}"))
|
||||||
|
}
|
||||||
|
candidate.plugins.withId('io.papermc.paperweight.userdev') {
|
||||||
|
candidate.tasks.named('paperweightUserdevSetup').configure { Task setup ->
|
||||||
|
Directory taskCache = candidate.layout.projectDirectory.dir(".gradle/iris-lanes/${buildLane}/caches/paperweight/taskCache")
|
||||||
|
setup.mappedServerJar.set(taskCache.file('mappedServerJar.jar'))
|
||||||
|
setup.reobfMappings.set(taskCache.file('reobfMappings.tiny'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ if (modTarget == null) {
|
|||||||
}
|
}
|
||||||
String runtimeJava = providers.gradleProperty('irisRuntimeJava').getOrElse(modTarget.java.toString())
|
String runtimeJava = providers.gradleProperty('irisRuntimeJava').getOrElse(modTarget.java.toString())
|
||||||
String variant = adapter ? "minecraft${minecraftVersion}-java${runtimeJava}" : "java${runtimeJava}"
|
String variant = adapter ? "minecraft${minecraftVersion}-java${runtimeJava}" : "java${runtimeJava}"
|
||||||
File projectCacheRoot = gradle.startParameter.projectCacheDir ?: new File(settingsDir, '.gradle')
|
if (!gradle.extensions.extraProperties.has('irisBuildLane')) {
|
||||||
gradle.startParameter.projectCacheDir = new File(projectCacheRoot, variant)
|
File projectCacheRoot = gradle.startParameter.projectCacheDir ?: new File(settingsDir, '.gradle')
|
||||||
|
gradle.startParameter.projectCacheDir = new File(projectCacheRoot, variant)
|
||||||
|
}
|
||||||
|
|
||||||
String runtimeOutputVariant = runtimeJava == '21' ? 'java21/' : ''
|
String runtimeOutputVariant = runtimeJava == '21' ? 'java21/' : ''
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
|
|||||||
Reference in new issue
Block a user