mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Dicing things up a little
- Trying to slim this beauty down to a size 6. - Shes a size 7.1
This commit is contained in:
@@ -250,9 +250,11 @@ Per-platform tasks: `./gradlew buildBukkit`, `buildFabric`, `buildForge`, `build
|
||||
SPI jar (the pure-JVM adapter/platform contract, not the stable plugin API) is built to `spi/build/libs/` by
|
||||
`./gradlew :spi:jar`.
|
||||
|
||||
`./gradlew buildAll` is a different task: it builds every platform and copies the jars into a
|
||||
consumer dropin tree for a local test server. It defaults to `build/consumers/` inside the repo;
|
||||
override with `-Plocation=/path/to/consumers`.
|
||||
`./gradlew buildAll` also runs `buildAllToOut` and copies the jars into a consumer dropin tree
|
||||
for a local test server. The consumer directory defaults to `../../[Minecraft Server]/consumers/`
|
||||
when it exists, otherwise `build/consumers/` inside the repo. `-Plocation=/path/to/consumers`
|
||||
changes only the consumer directory. Both tasks copy all four verified platform jars into
|
||||
`../PluginOuts/`.
|
||||
|
||||
If you need help compiling as a developer or contributor, ask in the Discord.
|
||||
|
||||
|
||||
+2
-2
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.core.pregenerator.PregenTask;
|
||||
@@ -33,7 +34,6 @@ import org.bukkit.util.Vector;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.BukkitCommandMessagesExtended;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
@Director(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!", descriptionKey = "iris.director.commandpregen.director.pregenerate_your_iris_worlds")
|
||||
@@ -62,7 +62,7 @@ public class CommandPregen implements DirectorExecutor {
|
||||
|
||||
if (PregeneratorJob.getInstance() != null) {
|
||||
// Reject like the modded adapter does; never silently kill a running job.
|
||||
sender().sendMessage(IrisLanguage.text(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PREGENERATION_TASK_IS_ALREADY_RUNNING_STOP_IT_FIRST_WITH_IRIS));
|
||||
sender().sendMessage(IrisLanguage.text(IrisMessages.PREGEN_ALREADY_RUNNING));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package art.arcane.iris.client;
|
||||
|
||||
import art.arcane.iris.core.localization.ClientUiMessages;
|
||||
import art.arcane.iris.modded.localization.ClientUiMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package art.arcane.iris.client;
|
||||
|
||||
import art.arcane.iris.core.localization.ClientUiMessages;
|
||||
import art.arcane.iris.modded.localization.ClientUiMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package art.arcane.iris.client;
|
||||
|
||||
import art.arcane.iris.core.localization.ClientUiMessages;
|
||||
import art.arcane.iris.modded.localization.ClientUiMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package art.arcane.iris.client;
|
||||
|
||||
import art.arcane.iris.core.localization.ClientUiMessages;
|
||||
import art.arcane.iris.modded.localization.ClientUiMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.spi.protocol.IrisMessage;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package art.arcane.iris.client.mixin;
|
||||
|
||||
import art.arcane.iris.core.localization.ClientUiMessages;
|
||||
import art.arcane.iris.modded.localization.ClientUiMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.modded.ModdedMixinFlags;
|
||||
|
||||
@@ -20,12 +20,24 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri('https://jitpack.io') }
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT')
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.fabricmc.fabric-loom' version '1.17.14'
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
apply plugin: 'art.arcane.volmit-packaging'
|
||||
|
||||
apply from: file('../../gradle/modded-generation-build-revision.gradle')
|
||||
|
||||
Properties rootProperties = new Properties()
|
||||
@@ -320,3 +332,14 @@ tasks.matching { it.name == 'remapJar' }.configureEach {
|
||||
tasks.named('assemble').configure {
|
||||
dependsOn(tasks.named('shadowJar'))
|
||||
}
|
||||
|
||||
pluginPackaging {
|
||||
artifacts {
|
||||
create('distribution') {
|
||||
taskName = 'shadowJar'
|
||||
policyName = 'Iris Fabric'
|
||||
prunePrefixes = ['art/arcane/volmlib/']
|
||||
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,24 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri('https://jitpack.io') }
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT')
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.minecraftforge.gradle' version '7.0.31'
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
apply plugin: 'art.arcane.volmit-packaging'
|
||||
|
||||
apply from: file('../../gradle/modded-generation-build-revision.gradle')
|
||||
|
||||
Properties rootProperties = new Properties()
|
||||
@@ -294,3 +306,14 @@ tasks.named('jar').configure {
|
||||
tasks.named('assemble').configure {
|
||||
dependsOn(tasks.named('shadowJar'))
|
||||
}
|
||||
|
||||
pluginPackaging {
|
||||
artifacts {
|
||||
create('distribution') {
|
||||
taskName = 'shadowJar'
|
||||
policyName = 'Iris Forge'
|
||||
prunePrefixes = ['art/arcane/volmlib/']
|
||||
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.PackDownloadMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator;
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.core.localization.ModdedHelpMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedHelpMessages;
|
||||
import art.arcane.volmlib.util.director.help.DirectorHelpMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.localization.TextKey;
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedDatapackCommands {
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import java.util.Enumeration;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
final class ModdedDeveloperCommands {
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.core.gui.GuiHost;
|
||||
import art.arcane.iris.core.loader.IrisRegistrant;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeProgressMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedGoldenHash {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ import java.util.UUID;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedObjectCommands {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedPackCommands {
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
|
||||
+3
-2
@@ -18,8 +18,9 @@
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
@@ -59,7 +60,7 @@ final class ModdedPregenCommands {
|
||||
return 0;
|
||||
}
|
||||
if (!started) {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PREGENERATION_TASK_IS_ALREADY_RUNNING_STOP_IT_FIRST_WITH_IRIS));
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(IrisMessages.PREGEN_ALREADY_RUNNING));
|
||||
return 0;
|
||||
}
|
||||
ModdedPregenBossBar.begin(source.getPlayer());
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
public final class ModdedRegen {
|
||||
private static final int APPLY_AHEAD = 8;
|
||||
private static final long CHUNK_SLOT_TIMEOUT_MILLIS = 120000L;
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedStructureCommands {
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedStudioCommands {
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ package art.arcane.iris.modded.command;
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||
import art.arcane.iris.core.localization.RuntimeUiMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
public final class ModdedWorldCommands {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
package art.arcane.iris.modded.localization;
|
||||
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
import art.arcane.volmlib.util.localization.PluralKey;
|
||||
+1
-6
@@ -1,4 +1,4 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
package art.arcane.iris.modded.localization;
|
||||
|
||||
import art.arcane.iris.util.common.format.C;
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
@@ -127,10 +127,6 @@ public final class ModdedCommandMessages {
|
||||
"iris.modded.irismoddedcommands.current_dimension_is_not_generated_by_iris_name_one_explicitly",
|
||||
"The current dimension (" + "{value}" + ") is not generated by Iris. Name one explicitly: /iris pregen start " + "{radius}" + " <dimension>; see /iris info for loaded Iris dimensions."
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_PREGENERATION_TASK_IS_ALREADY_RUNNING_STOP_IT_FIRST_WITH_IRIS = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.pregeneration_task_is_already_running_stop_it_first_with_iris",
|
||||
"A pregeneration task is already running. Stop it first with /iris pregen stop."
|
||||
);
|
||||
public static final TextKey IRIS_MODDED_COMMANDS_PREGEN_STARTED_BY_BLOCKS_FROM_PROGRESS_LOGS_CONSOLE_SEE_IRIS = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.pregen_started_by_blocks_from_progress_logs_console_see_iris",
|
||||
"Pregen started in " + "{value}" + " of " + "{value2}" + " by " + "{value3}" + " blocks from " + "{centerX}" + "," + "{centerZ}" + "." + "{modeNote}" + " Progress logs to console; see /iris pregen status." + "{guiNote}"
|
||||
@@ -1284,7 +1280,6 @@ public final class ModdedCommandMessages {
|
||||
IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_5,
|
||||
IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS_SEE_IRIS_INFO_LOADED_IRIS,
|
||||
IRIS_MODDED_COMMANDS_CURRENT_DIMENSION_IS_NOT_GENERATED_BY_IRIS_NAME_ONE_EXPLICITLY,
|
||||
IRIS_MODDED_COMMANDS_PREGENERATION_TASK_IS_ALREADY_RUNNING_STOP_IT_FIRST_WITH_IRIS,
|
||||
IRIS_MODDED_COMMANDS_PREGEN_STARTED_BY_BLOCKS_FROM_PROGRESS_LOGS_CONSOLE_SEE_IRIS,
|
||||
IRIS_MODDED_COMMANDS_STOPPING_PREGENERATION_FINISHING_UP_CURRENT_REGION,
|
||||
IRIS_MODDED_COMMANDS_NO_ACTIVE_PREGENERATION_TASK_STOP,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
package art.arcane.iris.modded.localization;
|
||||
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
import art.arcane.volmlib.util.localization.TextKey;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package art.arcane.iris.modded.localization;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisMessageContributor;
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class ModdedMessageContributor implements IrisMessageContributor {
|
||||
@Override
|
||||
public List<MessageKey> keys() {
|
||||
List<MessageKey> keys = new ArrayList<>();
|
||||
keys.addAll(ModdedCommandMessages.keys());
|
||||
keys.addAll(ModdedHelpMessages.keys());
|
||||
keys.addAll(ClientUiMessages.keys());
|
||||
return List.copyOf(keys);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
art.arcane.iris.modded.localization.ModdedMessageContributor
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package art.arcane.iris.modded.localization;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
import art.arcane.volmlib.util.localization.VolmitLocales;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class ModdedMessageContributorTest {
|
||||
@Test
|
||||
public void loaderDiscoversModdedAndClientMessages() {
|
||||
for (MessageKey key : new ModdedMessageContributor().keys()) {
|
||||
assertNotNull(key.id(), IrisLanguage.catalog().key(key.id()));
|
||||
assertEquals(key.id(), key.englishValue(), IrisLanguage.catalog().require(key.id()).englishValue());
|
||||
}
|
||||
assertEquals("A pregeneration task is already running. Stop it first with /iris pregen stop.",
|
||||
IrisLanguage.plain(IrisMessages.PREGEN_ALREADY_RUNNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeLoaderCatalogStillMatchesEveryDownloadableLocale() throws Exception {
|
||||
Path sourceRoot = Path.of("../../core/src/main/resources/languages");
|
||||
for (String locale : VolmitLocales.nonEnglish()) {
|
||||
JsonObject document = JsonParser.parseString(Files.readString(sourceRoot.resolve(locale + ".json")))
|
||||
.getAsJsonObject();
|
||||
assertEquals(locale, IrisLanguage.catalog().ids(), document.getAsJsonObject("messages").keySet());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,24 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri('https://jitpack.io') }
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT')
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.neoforged.moddev' version '2.0.141'
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
apply plugin: 'art.arcane.volmit-packaging'
|
||||
|
||||
apply from: file('../../gradle/modded-generation-build-revision.gradle')
|
||||
|
||||
Properties rootProperties = new Properties()
|
||||
@@ -270,3 +282,14 @@ tasks.named('jar').configure {
|
||||
tasks.named('assemble').configure {
|
||||
dependsOn(tasks.named('shadowJar'))
|
||||
}
|
||||
|
||||
pluginPackaging {
|
||||
artifacts {
|
||||
create('distribution') {
|
||||
taskName = 'shadowJar'
|
||||
policyName = 'Iris NeoForge'
|
||||
prunePrefixes = ['art/arcane/volmlib/']
|
||||
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-4
@@ -33,11 +33,23 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri('https://jitpack.io') }
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT')
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
apply plugin: 'art.arcane.volmit-packaging'
|
||||
|
||||
group = 'art.arcane'
|
||||
version = providers.gradleProperty('irisVersion').getOrElse('4.1.0-26.2')
|
||||
|
||||
@@ -167,9 +179,6 @@ tasks.named('jar', Jar).configure {
|
||||
delete(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
|
||||
}
|
||||
archiveFileName.set(bukkitArtifactName)
|
||||
doLast {
|
||||
JarCompactor.compact(archiveFile.get().asFile)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('iris', Copy) {
|
||||
@@ -611,7 +620,7 @@ tasks.register('buildAllToOut') {
|
||||
tasks.register('buildAll') {
|
||||
group = 'iris'
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn('iris', 'verifyModdedArtifacts')
|
||||
dependsOn('iris', 'verifyModdedArtifacts', 'buildAllToOut')
|
||||
doLast {
|
||||
delete(
|
||||
file("${bukkitConsumerPath}/Iris-Fabric.jar"),
|
||||
@@ -746,3 +755,17 @@ if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
System.err.println()
|
||||
System.exit(69)
|
||||
}
|
||||
|
||||
pluginPackaging {
|
||||
artifacts {
|
||||
create('distribution') {
|
||||
taskName = 'jar'
|
||||
policyName = 'Iris Bukkit'
|
||||
stripDirectories = true
|
||||
stripLocalVariables = true
|
||||
releaseCompression = providers.gradleProperty('compactRelease').map { it.toBoolean() }.getOrElse(false)
|
||||
prunePrefixes = ['art/arcane/volmlib/']
|
||||
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -6,8 +13,8 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* Reads the {@code CONSTANT_Class} entries of a class file. That is exactly the set of types the
|
||||
* class links against - supertypes, field and method owners, casts, catches, constant class
|
||||
* literals. Annotation types live in the annotation attributes as plain UTF-8 descriptors and are
|
||||
* class names directly - supertypes, field and method owners, casts, catches, constant class
|
||||
* literals. Field and method descriptors are also traversed to include signature-only types. Annotation types live in the annotation attributes as plain UTF-8 descriptors and are
|
||||
* deliberately not reported: the JVM skips an annotation whose type is absent.
|
||||
*/
|
||||
public final class ClassReferences {
|
||||
@@ -42,6 +49,7 @@ public final class ClassReferences {
|
||||
int constantCount = readUnsignedShort(classFile, 8);
|
||||
String[] utf8 = new String[constantCount];
|
||||
List<Integer> classNameIndexes = new ArrayList<>();
|
||||
List<Integer> descriptorIndexes = new ArrayList<>();
|
||||
int offset = 10;
|
||||
int index = 1;
|
||||
while (index < constantCount) {
|
||||
@@ -58,10 +66,18 @@ public final class ClassReferences {
|
||||
classNameIndexes.add(readUnsignedShort(classFile, offset));
|
||||
offset += 2;
|
||||
}
|
||||
case CONSTANT_STRING, CONSTANT_METHOD_TYPE, CONSTANT_MODULE, CONSTANT_PACKAGE -> offset += 2;
|
||||
case CONSTANT_METHOD_TYPE -> {
|
||||
descriptorIndexes.add(readUnsignedShort(classFile, offset));
|
||||
offset += 2;
|
||||
}
|
||||
case CONSTANT_NAME_AND_TYPE -> {
|
||||
descriptorIndexes.add(readUnsignedShort(classFile, offset + 2));
|
||||
offset += 4;
|
||||
}
|
||||
case CONSTANT_STRING, CONSTANT_MODULE, CONSTANT_PACKAGE -> offset += 2;
|
||||
case CONSTANT_METHOD_HANDLE -> offset += 3;
|
||||
case CONSTANT_INTEGER, CONSTANT_FLOAT, CONSTANT_FIELDREF, CONSTANT_METHODREF,
|
||||
CONSTANT_INTERFACE_METHODREF, CONSTANT_NAME_AND_TYPE, CONSTANT_DYNAMIC,
|
||||
CONSTANT_INTERFACE_METHODREF, CONSTANT_DYNAMIC,
|
||||
CONSTANT_INVOKE_DYNAMIC -> offset += 4;
|
||||
case CONSTANT_LONG, CONSTANT_DOUBLE -> {
|
||||
offset += 8;
|
||||
@@ -83,9 +99,41 @@ public final class ClassReferences {
|
||||
references.add(name);
|
||||
}
|
||||
}
|
||||
for (int descriptorIndex : descriptorIndexes) {
|
||||
addType(Type.getType(utf8[descriptorIndex]), references);
|
||||
}
|
||||
new ClassReader(classFile).accept(new ClassVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
addType(Type.getType(descriptor), references);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
addType(Type.getMethodType(descriptor), references);
|
||||
return null;
|
||||
}
|
||||
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
return references;
|
||||
}
|
||||
|
||||
private static void addType(Type type, Set<String> references) {
|
||||
switch (type.getSort()) {
|
||||
case Type.ARRAY -> addType(type.getElementType(), references);
|
||||
case Type.OBJECT -> references.add(type.getInternalName());
|
||||
case Type.METHOD -> {
|
||||
addType(type.getReturnType(), references);
|
||||
for (Type argument : type.getArgumentTypes()) {
|
||||
addType(argument, references);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String rawName) {
|
||||
if (rawName == null || rawName.isEmpty()) {
|
||||
return null;
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import org.apache.commons.compress.archivers.zip.ExtraFieldUtils;
|
||||
import org.apache.commons.compress.archivers.zip.Zip64Mode;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.gradle.api.GradleException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class JarCompactor {
|
||||
private static final int BUFFER_BYTES = 64 * 1024;
|
||||
|
||||
private JarCompactor() {
|
||||
}
|
||||
|
||||
public static void compact(File artifact) {
|
||||
if (artifact == null || !artifact.isFile()) {
|
||||
throw new GradleException("Cannot compact missing jar artifact: " + artifact);
|
||||
}
|
||||
|
||||
Path source = artifact.toPath();
|
||||
Path temporary = null;
|
||||
try {
|
||||
temporary = Files.createTempFile(source.getParent(), artifact.getName(), ".compact");
|
||||
rewrite(source, temporary);
|
||||
replace(temporary, source);
|
||||
} catch (IOException exception) {
|
||||
if (temporary != null) {
|
||||
try {
|
||||
Files.deleteIfExists(temporary);
|
||||
} catch (IOException cleanupFailure) {
|
||||
exception.addSuppressed(cleanupFailure);
|
||||
}
|
||||
}
|
||||
throw new GradleException("Unable to compact jar artifact " + artifact.getAbsolutePath(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void rewrite(Path source, Path destination) throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_BYTES];
|
||||
try (ZipFile input = new ZipFile(source.toFile());
|
||||
ZipArchiveOutputStream output = new ZipArchiveOutputStream(destination)) {
|
||||
if (!output.isSeekable()) {
|
||||
throw new IOException("Jar compaction requires seekable output.");
|
||||
}
|
||||
output.setLevel(9);
|
||||
output.setUseZip64(Zip64Mode.Never);
|
||||
output.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER);
|
||||
Set<String> names = new HashSet<>();
|
||||
Enumeration<? extends ZipEntry> entries = input.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
if (!names.add(entry.getName())) {
|
||||
throw new IOException("Duplicate jar entry: " + entry.getName());
|
||||
}
|
||||
output.putArchiveEntry(copyMetadata(entry));
|
||||
copyEntry(input, output, entry, buffer);
|
||||
output.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyEntry(ZipFile input, ZipArchiveOutputStream output, ZipEntry entry, byte[] buffer)
|
||||
throws IOException {
|
||||
CRC32 crc = new CRC32();
|
||||
long size = 0L;
|
||||
try (InputStream content = input.getInputStream(entry)) {
|
||||
int read;
|
||||
while ((read = content.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
crc.update(buffer, 0, read);
|
||||
size += read;
|
||||
}
|
||||
}
|
||||
if (size != entry.getSize() || crc.getValue() != entry.getCrc()) {
|
||||
throw new IOException("Jar entry failed size or CRC verification: " + entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static ZipArchiveEntry copyMetadata(ZipEntry source) throws IOException {
|
||||
ZipArchiveEntry target = new ZipArchiveEntry(source.getName());
|
||||
target.setMethod(ZipEntry.DEFLATED);
|
||||
if (source.getTime() >= 0L) {
|
||||
target.setTime(source.getTime());
|
||||
}
|
||||
if (source.getComment() != null) {
|
||||
target.setComment(source.getComment());
|
||||
}
|
||||
if (source.getExtra() != null) {
|
||||
target.setExtraFields(ExtraFieldUtils.parse(source.getExtra(), true,
|
||||
ZipArchiveEntry.ExtraFieldParsingMode.BEST_EFFORT));
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private static void replace(Path temporary, Path source) throws IOException {
|
||||
try {
|
||||
Files.move(
|
||||
temporary,
|
||||
source,
|
||||
StandardCopyOption.ATOMIC_MOVE,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (AtomicMoveNotSupportedException exception) {
|
||||
Files.move(temporary, source, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import org.junit.Test;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ClassReferencesTest {
|
||||
@Test
|
||||
public void includesDeclarationOnlyFieldAndMethodTypes() {
|
||||
ClassWriter writer = createClass();
|
||||
writer.visitField(Opcodes.ACC_PUBLIC, "field", "[[Lexample/FieldType;", null, null).visitEnd();
|
||||
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "method",
|
||||
"(Lexample/Argument;[I)[Lexample/Result;", null, null).visitEnd();
|
||||
writer.visitEnd();
|
||||
|
||||
Set<String> references = ClassReferences.read(writer.toByteArray());
|
||||
assertTrue(references.containsAll(Set.of("example/FieldType", "example/Argument", "example/Result")));
|
||||
assertFalse(references.contains("I"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void includesMemberReferenceAndMethodTypeDescriptors() {
|
||||
ClassWriter writer = createClass();
|
||||
writer.newMethod("example/Owner", "method", "(Lexample/Argument;)Lexample/Result;", false);
|
||||
writer.newConst(Type.getMethodType("(Lexample/HandleArgument;)Lexample/HandleResult;"));
|
||||
writer.visitEnd();
|
||||
|
||||
Set<String> references = ClassReferences.read(writer.toByteArray());
|
||||
assertTrue(references.containsAll(Set.of("example/Owner", "example/Argument", "example/Result",
|
||||
"example/HandleArgument", "example/HandleResult")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotTreatStringsOrAnnotationOnlyTypesAsLinkage() {
|
||||
ClassWriter writer = createClass();
|
||||
writer.newConst("Lexample/StringValue;");
|
||||
writer.visitAnnotation("Lexample/Annotation;", true).visitEnd();
|
||||
writer.visitEnd();
|
||||
|
||||
Set<String> references = ClassReferences.read(writer.toByteArray());
|
||||
assertFalse(references.contains("example/StringValue"));
|
||||
assertFalse(references.contains("example/Annotation"));
|
||||
}
|
||||
|
||||
private static ClassWriter createClass() {
|
||||
ClassWriter writer = new ClassWriter(0);
|
||||
writer.visit(Opcodes.V25, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT,
|
||||
"example/Consumer", null, "java/lang/Object", null);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JarCompactorTest {
|
||||
@Rule
|
||||
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void preservesFilesAndOmitsDirectoryEntries() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("artifact.jar");
|
||||
byte[] content = "Iris artifact content".getBytes(StandardCharsets.UTF_8);
|
||||
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact))) {
|
||||
output.putNextEntry(new JarEntry("example/"));
|
||||
output.closeEntry();
|
||||
output.putNextEntry(new JarEntry("example/value.txt"));
|
||||
output.write(content);
|
||||
output.closeEntry();
|
||||
}
|
||||
|
||||
JarCompactor.compact(artifact);
|
||||
|
||||
try (JarFile jar = new JarFile(artifact)) {
|
||||
assertNull(jar.getJarEntry("example/"));
|
||||
assertArrayEquals(content, jar.getInputStream(
|
||||
jar.getJarEntry("example/value.txt")).readAllBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preservesJarIdentityAndProducesDeterministicSeekableOutput() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("identity.jar");
|
||||
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact));
|
||||
InputStream classResource = getClass().getResourceAsStream("/JarCompactorTest.class")) {
|
||||
output.setLevel(9);
|
||||
writeEntry(output, "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\r\nMulti-Release: true\r\n\r\n"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
writeEntry(output, "JarCompactorTest.class", classResource.readAllBytes());
|
||||
writeEntry(output, "META-INF/iris/generation-kernels/abi-1.revision", "registry fixture".getBytes(StandardCharsets.UTF_8));
|
||||
writeEntry(output, "example/caf\u00e9.txt", "unicode name".getBytes(StandardCharsets.UTF_8));
|
||||
for (int index = 0; index < 100; index++) {
|
||||
writeEntry(output, "example/item-" + index + ".txt", ("value-" + index).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
Map<String, EntryIdentity> original = identity(artifact);
|
||||
long originalBytes = artifact.length();
|
||||
|
||||
JarCompactor.compact(artifact);
|
||||
|
||||
assertEquals(original, identity(artifact));
|
||||
assertTrue(artifact.length() < originalBytes);
|
||||
try (JarFile jar = new JarFile(artifact)) {
|
||||
assertEquals("true", jar.getManifest().getMainAttributes().getValue("Multi-Release"));
|
||||
assertEquals(0xCA, jar.getInputStream(jar.getJarEntry("JarCompactorTest.class")).read());
|
||||
}
|
||||
try (ZipFile zip = ZipFile.builder().setFile(artifact).get()) {
|
||||
Enumeration<ZipArchiveEntry> entries = zip.getEntries();
|
||||
while (entries.hasMoreElements()) {
|
||||
assertFalse(entries.nextElement().getGeneralPurposeBit().usesDataDescriptor());
|
||||
}
|
||||
}
|
||||
byte[] first = Files.readAllBytes(artifact.toPath());
|
||||
JarCompactor.compact(artifact);
|
||||
assertArrayEquals(first, Files.readAllBytes(artifact.toPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMalformedArchivesWithoutReplacingTheSource() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("invalid.jar");
|
||||
byte[] original = "not a zip archive".getBytes(StandardCharsets.UTF_8);
|
||||
Files.write(artifact.toPath(), original);
|
||||
|
||||
assertThrows(GradleException.class, () -> JarCompactor.compact(artifact));
|
||||
|
||||
assertArrayEquals(original, Files.readAllBytes(artifact.toPath()));
|
||||
assertEquals(1, temporaryFolder.getRoot().listFiles().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsCorruptPayloadWithoutReplacingTheSource() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("corrupt.jar");
|
||||
byte[] payload = "checked-payload".getBytes(StandardCharsets.UTF_8);
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(payload);
|
||||
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact))) {
|
||||
JarEntry entry = new JarEntry("value.txt");
|
||||
entry.setMethod(ZipEntry.STORED);
|
||||
entry.setSize(payload.length);
|
||||
entry.setCrc(crc.getValue());
|
||||
output.putNextEntry(entry);
|
||||
output.write(payload);
|
||||
output.closeEntry();
|
||||
}
|
||||
byte[] corrupt = Files.readAllBytes(artifact.toPath());
|
||||
int payloadOffset = new String(corrupt, StandardCharsets.ISO_8859_1).indexOf("checked-payload");
|
||||
assertTrue(payloadOffset > 0);
|
||||
corrupt[payloadOffset] ^= 1;
|
||||
Files.write(artifact.toPath(), corrupt);
|
||||
|
||||
assertThrows(GradleException.class, () -> JarCompactor.compact(artifact));
|
||||
|
||||
assertArrayEquals(corrupt, Files.readAllBytes(artifact.toPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsDuplicateEntriesWithoutReplacingTheSource() throws Exception {
|
||||
File artifact = temporaryFolder.newFile("duplicate.jar");
|
||||
try (ZipArchiveOutputStream output = new ZipArchiveOutputStream(artifact)) {
|
||||
for (int index = 0; index < 2; index++) {
|
||||
output.putArchiveEntry(new ZipArchiveEntry("duplicate.txt"));
|
||||
output.write(index);
|
||||
output.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
byte[] original = Files.readAllBytes(artifact.toPath());
|
||||
|
||||
assertThrows(GradleException.class, () -> JarCompactor.compact(artifact));
|
||||
|
||||
assertArrayEquals(original, Files.readAllBytes(artifact.toPath()));
|
||||
}
|
||||
|
||||
private static void writeEntry(JarOutputStream output, String name, byte[] content) throws Exception {
|
||||
JarEntry entry = new JarEntry(name);
|
||||
entry.setTime(315_532_800_000L);
|
||||
entry.setComment("preserved entry comment");
|
||||
entry.setExtra(new byte[]{(byte) 0xFE, (byte) 0xCA, 1, 0, 7});
|
||||
output.putNextEntry(entry);
|
||||
output.write(content);
|
||||
output.closeEntry();
|
||||
}
|
||||
|
||||
private static Map<String, EntryIdentity> identity(File artifact) throws Exception {
|
||||
Map<String, EntryIdentity> entries = new TreeMap<>();
|
||||
try (JarFile jar = new JarFile(artifact)) {
|
||||
Enumeration<JarEntry> contents = jar.entries();
|
||||
while (contents.hasMoreElements()) {
|
||||
JarEntry entry = contents.nextElement();
|
||||
byte[] bytes;
|
||||
try (InputStream input = jar.getInputStream(entry)) {
|
||||
bytes = input.readAllBytes();
|
||||
}
|
||||
entries.put(entry.getName(), new EntryIdentity(
|
||||
HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)),
|
||||
entry.getSize(), entry.getCrc(), entry.getTime(), entry.getComment(),
|
||||
entry.getExtra() == null ? "" : HexFormat.of().formatHex(entry.getExtra())));
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private record EntryIdentity(String sha256, long size, long crc, long time, String comment, String extra) {
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -415,10 +415,10 @@ List<String> unusedVolmLibClasses = [
|
||||
'art/arcane/volmlib/util/collection/StateList',
|
||||
'art/arcane/iris/core/pack/IrisPack',
|
||||
'art/arcane/iris/core/pack/IrisPackRepository',
|
||||
'art/arcane/iris/core/jobs/DownloadJob',
|
||||
'art/arcane/iris/core/jobs/JobCollection',
|
||||
'art/arcane/iris/core/jobs/ParallelQueueJob',
|
||||
'art/arcane/iris/core/jobs/QueueJob',
|
||||
'art/arcane/iris/util/common/scheduling/jobs/DownloadJob',
|
||||
'art/arcane/iris/util/common/scheduling/jobs/JobCollection',
|
||||
'art/arcane/iris/util/common/scheduling/jobs/ParallelQueueJob',
|
||||
'art/arcane/iris/util/common/scheduling/jobs/QueueJob',
|
||||
'art/arcane/iris/engine/framework/placer/WorldObjectPlacer',
|
||||
'art/arcane/iris/engine/object/IPostBlockAccess',
|
||||
'art/arcane/iris/engine/object/IrisPotionEffect',
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class CapabilitySnapshot {
|
||||
private final Method removeLevelMethod;
|
||||
private final String paperLikeResolution;
|
||||
|
||||
private CapabilitySnapshot(
|
||||
CapabilitySnapshot(
|
||||
ServerFamily serverFamily,
|
||||
boolean regionizedRuntime,
|
||||
Object worldsProvider,
|
||||
@@ -259,94 +259,6 @@ public final class CapabilitySnapshot {
|
||||
);
|
||||
}
|
||||
|
||||
public static CapabilitySnapshot forTesting(ServerFamily serverFamily, boolean regionizedRuntime, boolean worldsProviderHealthy, boolean paperLikeRuntimeHealthy) {
|
||||
Object minecraftServer = paperLikeRuntimeHealthy ? new TestingPaperLikeServer("datapack-registry", "server-registry") : null;
|
||||
Method createLevelMethod = null;
|
||||
Field worldLoaderContextField = null;
|
||||
Method serverRegistryAccessMethod = null;
|
||||
try {
|
||||
createLevelMethod = paperLikeRuntimeHealthy
|
||||
? TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class)
|
||||
: null;
|
||||
worldLoaderContextField = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext")
|
||||
: null;
|
||||
serverRegistryAccessMethod = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class)
|
||||
: null;
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
worldsProviderHealthy ? new Object() : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? "test-provider" : "inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
paperLikeRuntimeHealthy ? PaperLikeFlavor.CURRENT_INFO_AND_DATA : PaperLikeFlavor.UNSUPPORTED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
serverRegistryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
paperLikeRuntimeHealthy ? "available(test)" : "unsupported(test)"
|
||||
);
|
||||
}
|
||||
|
||||
public static CapabilitySnapshot forTestingRuntimeRegistries(ServerFamily serverFamily, boolean regionizedRuntime, Object datapackDimensions, Object serverRegistryAccess) {
|
||||
TestingPaperLikeServer minecraftServer = new TestingPaperLikeServer(datapackDimensions, serverRegistryAccess);
|
||||
Method createLevelMethod;
|
||||
Field worldLoaderContextField;
|
||||
Method registryAccessMethod;
|
||||
try {
|
||||
createLevelMethod = TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class);
|
||||
worldLoaderContextField = CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext");
|
||||
registryAccessMethod = CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class);
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
PaperLikeFlavor.CURRENT_INFO_AND_DATA,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
registryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"available(test-runtime-registries)"
|
||||
);
|
||||
}
|
||||
|
||||
public ServerFamily serverFamily() {
|
||||
return serverFamily;
|
||||
}
|
||||
@@ -578,35 +490,4 @@ public final class CapabilitySnapshot {
|
||||
return new Object[]{null, null, null, "inactive(service scan found nothing)"};
|
||||
}
|
||||
|
||||
private static final class TestingPaperLikeServer {
|
||||
private final TestingWorldLoaderContext worldLoaderContext;
|
||||
private final Object registryAccess;
|
||||
|
||||
private TestingPaperLikeServer(Object datapackDimensions, Object registryAccess) {
|
||||
this.worldLoaderContext = new TestingWorldLoaderContext(datapackDimensions);
|
||||
this.registryAccess = registryAccess;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void createLevel(Object levelStem, Object worldLoadingInfoAndData, Object worldDataAndGenSettings) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object registryAccess() {
|
||||
return registryAccess;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestingWorldLoaderContext {
|
||||
private final Object datapackDimensions;
|
||||
|
||||
private TestingWorldLoaderContext(Object datapackDimensions) {
|
||||
this.datapackDimensions = datapackDimensions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object datapackDimensions() {
|
||||
return datapackDimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,15 +568,20 @@ public final class IrisLanguage {
|
||||
});
|
||||
}
|
||||
|
||||
private static void validateDownload(String locale, String raw) {
|
||||
LocalizationValidator.validate(CATALOG, List.of(parseOverlay("download:" + locale, locale, raw)))
|
||||
.throwIfInvalid();
|
||||
static void validateDownload(String locale, String raw) {
|
||||
LocaleOverlay overlay = parseDownloadedOverlay("download:" + locale, locale, raw);
|
||||
LocalizationValidator.validate(CATALOG, List.of(overlay)).throwIfInvalid();
|
||||
for (MessageKey key : CATALOG.keys()) {
|
||||
if (overlay.value(key.id()) == null) {
|
||||
throw new IllegalArgumentException("Downloaded Iris locale " + locale + " is incomplete: " + key.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LocaleOverlay loadDownloadedOverlay(File root, String locale) {
|
||||
RemoteLanguageCatalog.CacheResult cached = remote(root).read(locale, IrisLanguage::validateDownload);
|
||||
if (cached.state() == RemoteLanguageCatalog.CacheState.VALID) {
|
||||
return parseOverlay(cached.file().toString(), locale, cached.content());
|
||||
return parseDownloadedOverlay(cached.file().toString(), locale, cached.content());
|
||||
}
|
||||
if (cached.failure() != null) {
|
||||
IrisLogging.error("Ignoring invalid downloaded Iris locale " + locale + ".");
|
||||
@@ -585,6 +590,17 @@ public final class IrisLanguage {
|
||||
return null;
|
||||
}
|
||||
|
||||
static LocaleOverlay parseDownloadedOverlay(String source, String locale, String raw) {
|
||||
LocaleOverlay parsed = parseOverlay(source, locale, raw);
|
||||
LocaleOverlay.Builder builder = LocaleOverlay.builder(source, locale);
|
||||
for (Map.Entry<String, MessageValue> entry : parsed.values().entrySet()) {
|
||||
if (CATALOG.key(entry.getKey()) != null) {
|
||||
builder.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static LocaleOverlay parseOverlay(String source, String locale, String raw) {
|
||||
JsonElement parsed = JsonParser.parseString(raw == null || raw.isBlank() ? "{}" : raw);
|
||||
if (!parsed.isJsonObject()) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IrisMessageContributor {
|
||||
List<MessageKey> keys();
|
||||
}
|
||||
@@ -7,8 +7,14 @@ import art.arcane.volmlib.util.localization.TextKey;
|
||||
import art.arcane.volmlib.util.localization.VolmitLocales;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public final class IrisMessages {
|
||||
public static final TextKey PREGEN_ALREADY_RUNNING = TextKey.of(
|
||||
"iris.modded.irismoddedcommands.pregeneration_task_is_already_running_stop_it_first_with_iris",
|
||||
"A pregeneration task is already running. Stop it first with /iris pregen stop."
|
||||
);
|
||||
|
||||
public static final TextKey COMMAND_PERMISSION_DENIED = TextKey.of(
|
||||
"iris.command.permission_denied",
|
||||
"You lack the permission '{permission}'"
|
||||
@@ -67,6 +73,7 @@ public final class IrisMessages {
|
||||
);
|
||||
|
||||
private static final List<MessageKey> RUNTIME_KEYS = List.of(
|
||||
PREGEN_ALREADY_RUNNING,
|
||||
COMMAND_PERMISSION_DENIED,
|
||||
COMMAND_UNKNOWN,
|
||||
COMMAND_PLAYER_ONLY,
|
||||
@@ -101,16 +108,16 @@ public final class IrisMessages {
|
||||
builder.addAll(RUNTIME_KEYS);
|
||||
builder.addAll(BukkitCommandMessages.keys());
|
||||
builder.addAll(BukkitCommandMessagesExtended.keys());
|
||||
builder.addAll(ModdedCommandMessages.keys());
|
||||
builder.addAll(DirectorCommandMessages.keys());
|
||||
builder.addAll(ModdedHelpMessages.keys());
|
||||
builder.addAll(RuntimeUiMessages.keys());
|
||||
builder.addAll(BukkitRuntimeMessages.keys());
|
||||
builder.addAll(RuntimeProgressMessages.keys());
|
||||
builder.addAll(PackDownloadMessages.keys());
|
||||
builder.addAll(ClientUiMessages.keys());
|
||||
builder.addAll(BukkitUiMessages.keys());
|
||||
builder.addAll(DesktopUiMessages.keys());
|
||||
for (IrisMessageContributor contributor : ServiceLoader.load(IrisMessageContributor.class, IrisMessages.class.getClassLoader())) {
|
||||
builder.addAll(contributor.keys());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class BukkitPublicBackendTest {
|
||||
worldName, worldKey, World.Environment.NORMAL, generator, biomeProvider,
|
||||
WorldType.NORMAL, true, false, 123L, false, false, WorldLifecycleCaller.CREATE);
|
||||
BukkitPublicBackend backend = new BukkitPublicBackend(
|
||||
CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, false));
|
||||
CapabilitySnapshotFixtures.forTesting(ServerFamily.PAPER, false, false, false));
|
||||
|
||||
doAnswer(invocation -> {
|
||||
assertSame(generator, WorldLifecycleStaging.peekStemGenerator(worldName));
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.core.lifecycle.CapabilitySnapshot.PaperLikeFlavor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class CapabilitySnapshotFixtures {
|
||||
private CapabilitySnapshotFixtures() {
|
||||
}
|
||||
|
||||
static CapabilitySnapshot forTesting(ServerFamily serverFamily, boolean regionizedRuntime, boolean worldsProviderHealthy, boolean paperLikeRuntimeHealthy) {
|
||||
Object minecraftServer = paperLikeRuntimeHealthy ? new TestingPaperLikeServer("datapack-registry", "server-registry") : null;
|
||||
Method createLevelMethod = null;
|
||||
Field worldLoaderContextField = null;
|
||||
Method serverRegistryAccessMethod = null;
|
||||
try {
|
||||
createLevelMethod = paperLikeRuntimeHealthy
|
||||
? TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class)
|
||||
: null;
|
||||
worldLoaderContextField = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext")
|
||||
: null;
|
||||
serverRegistryAccessMethod = paperLikeRuntimeHealthy
|
||||
? CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class)
|
||||
: null;
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
worldsProviderHealthy ? new Object() : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? Object.class : null,
|
||||
worldsProviderHealthy ? "test-provider" : "inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
paperLikeRuntimeHealthy ? PaperLikeFlavor.CURRENT_INFO_AND_DATA : PaperLikeFlavor.UNSUPPORTED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
serverRegistryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
paperLikeRuntimeHealthy ? "available(test)" : "unsupported(test)"
|
||||
);
|
||||
}
|
||||
|
||||
static CapabilitySnapshot forTestingRuntimeRegistries(ServerFamily serverFamily, boolean regionizedRuntime, Object datapackDimensions, Object serverRegistryAccess) {
|
||||
TestingPaperLikeServer minecraftServer = new TestingPaperLikeServer(datapackDimensions, serverRegistryAccess);
|
||||
Method createLevelMethod;
|
||||
Field worldLoaderContextField;
|
||||
Method registryAccessMethod;
|
||||
try {
|
||||
createLevelMethod = TestingPaperLikeServer.class.getDeclaredMethod("createLevel", Object.class, Object.class, Object.class);
|
||||
worldLoaderContextField = CapabilityResolution.resolveField(TestingPaperLikeServer.class, "worldLoaderContext");
|
||||
registryAccessMethod = CapabilityResolution.resolveServerRegistryAccessMethod(TestingPaperLikeServer.class);
|
||||
} catch (NoSuchMethodException | NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return new CapabilitySnapshot(
|
||||
serverFamily,
|
||||
regionizedRuntime,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"inactive",
|
||||
null,
|
||||
minecraftServer,
|
||||
createLevelMethod,
|
||||
PaperLikeFlavor.CURRENT_INFO_AND_DATA,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
worldLoaderContextField,
|
||||
registryAccessMethod,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"available(test-runtime-registries)"
|
||||
);
|
||||
}
|
||||
|
||||
private static final class TestingPaperLikeServer {
|
||||
private final TestingWorldLoaderContext worldLoaderContext;
|
||||
private final Object registryAccess;
|
||||
|
||||
private TestingPaperLikeServer(Object datapackDimensions, Object registryAccess) {
|
||||
this.worldLoaderContext = new TestingWorldLoaderContext(datapackDimensions);
|
||||
this.registryAccess = registryAccess;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void createLevel(Object levelStem, Object worldLoadingInfoAndData, Object worldDataAndGenSettings) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object registryAccess() {
|
||||
return registryAccess;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestingWorldLoaderContext {
|
||||
private final Object datapackDimensions;
|
||||
|
||||
private TestingWorldLoaderContext(Object datapackDimensions) {
|
||||
this.datapackDimensions = datapackDimensions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object datapackDimensions() {
|
||||
return datapackDimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ import static org.junit.Assert.fail;
|
||||
public class WorldLifecycleDiagnosticsTest {
|
||||
@Test
|
||||
public void studioCreateSelectionFailurePrintsFullStacktrace() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, false));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.PAPER, false, false, false));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public class WorldLifecycleRuntimeLevelStemTest {
|
||||
public void runtimeStemUsesFullServerRegistryAccessForPlatformGenerators() throws Exception {
|
||||
Object datapackDimensions = new MissingDimensionTypeRegistry();
|
||||
Object serverRegistryAccess = new Object();
|
||||
CapabilitySnapshot capabilities = CapabilitySnapshot.forTestingRuntimeRegistries(ServerFamily.PURPUR, false, datapackDimensions, serverRegistryAccess);
|
||||
CapabilitySnapshot capabilities = CapabilitySnapshotFixtures.forTestingRuntimeRegistries(ServerFamily.PURPUR, false, datapackDimensions, serverRegistryAccess);
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest(
|
||||
"studio",
|
||||
NamespacedKey.minecraft("studio"),
|
||||
|
||||
+11
-11
@@ -21,7 +21,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnPaper() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.PAPER, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -29,7 +29,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnPurpur() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -37,7 +37,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnCanvas() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.CANVAS, true, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.CANVAS, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -45,7 +45,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnFolia() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, true, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.FOLIA, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -53,7 +53,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void studioSelectsBukkitBackendOnSpigot() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.SPIGOT, false, false, false));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.SPIGOT, false, false, false));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", NamespacedKey.minecraft("studio"), World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("bukkit_public", service.selectCreateBackend(request).backendName());
|
||||
@@ -61,7 +61,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void persistentCreatePrefersBukkitBackendOnPaperLikeServers() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", NamespacedKey.minecraft("persistent"), World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertEquals("bukkit_public", service.selectCreateBackend(request).backendName());
|
||||
@@ -69,7 +69,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void persistentCreateSelectsPaperLikeBackendOnFolia() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, true, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.FOLIA, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", NamespacedKey.minecraft("persistent"), World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -77,7 +77,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void persistentCreateSelectsPaperLikeBackendOnCanvas() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.CANVAS, true, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.CANVAS, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", NamespacedKey.minecraft("persistent"), World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
@@ -85,7 +85,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void persistentCreateFailsClosedOnFoliaWithoutRuntime() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, false, false, false));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.FOLIA, false, false, false));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", NamespacedKey.minecraft("persistent"), World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> service.selectCreateBackend(request));
|
||||
@@ -93,7 +93,7 @@ public class WorldLifecycleSelectionTest {
|
||||
|
||||
@Test
|
||||
public void unloadUsesRememberedBackendFamily() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshotFixtures.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
|
||||
service.rememberBackend(NamespacedKey.minecraft("studio"), "paper_like_runtime");
|
||||
assertEquals("paper_like_runtime", service.selectUnloadBackend("minecraft:studio").backendName());
|
||||
@@ -104,7 +104,7 @@ public class WorldLifecycleSelectionTest {
|
||||
CountingBackend selected = new CountingBackend("selected", true);
|
||||
CountingBackend inactive = new CountingBackend("inactive", false);
|
||||
WorldLifecycleService service = new WorldLifecycleService(
|
||||
CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, true),
|
||||
CapabilitySnapshotFixtures.forTesting(ServerFamily.PAPER, false, false, true),
|
||||
selected,
|
||||
inactive,
|
||||
inactive);
|
||||
|
||||
+1
-1
@@ -279,7 +279,7 @@ public class WorldLifecycleUnloadAsyncTest {
|
||||
) {
|
||||
ControlledBackend inactive = new ControlledBackend("inactive");
|
||||
return new WorldLifecycleService(
|
||||
CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, false),
|
||||
CapabilitySnapshotFixtures.forTesting(ServerFamily.PURPUR, false, false, false),
|
||||
inactive,
|
||||
remembered,
|
||||
fallback
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
|
||||
import art.arcane.volmlib.util.localization.LocaleOverlay;
|
||||
import art.arcane.volmlib.util.localization.LocalizationValidator;
|
||||
import art.arcane.volmlib.util.localization.RemoteLanguageCatalog;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisDownloadValidationTest {
|
||||
private static final String LOCALE = "de_DE";
|
||||
private static final String MISSING_KEY = "iris.director.commanddeveloper.director.stage_world_generation_update";
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void incompleteCacheDownloadsCompleteCatalogAndReusesIt() throws Exception {
|
||||
String complete = completeCatalog();
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
HttpServer server = server(complete, requests);
|
||||
try (URLClassLoader resources = resources();
|
||||
RemoteLanguageCatalog catalog = catalog(server, resources)) {
|
||||
Path cache = catalog.cacheFile(LOCALE);
|
||||
Files.createDirectories(cache.getParent());
|
||||
Files.writeString(cache, incompleteCatalog(complete));
|
||||
|
||||
RemoteLanguageCatalog.CacheResult initial = catalog.read(LOCALE, IrisLanguage::validateDownload);
|
||||
assertEquals(RemoteLanguageCatalog.CacheState.INVALID, initial.state());
|
||||
assertTrue(initial.failure().getMessage().contains(MISSING_KEY));
|
||||
assertEquals(0, requests.get());
|
||||
|
||||
assertEquals(complete, catalog.readOrDownload(LOCALE, IrisLanguage::validateDownload));
|
||||
assertEquals(complete, Files.readString(cache));
|
||||
assertEquals(RemoteLanguageCatalog.CacheState.VALID,
|
||||
catalog.read(LOCALE, IrisLanguage::validateDownload).state());
|
||||
assertEquals(1, requests.get());
|
||||
|
||||
assertEquals(complete, catalog.readOrDownload(LOCALE, IrisLanguage::validateDownload));
|
||||
assertEquals(1, requests.get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteUpstreamDoesNotReplaceAnInvalidCache() throws Exception {
|
||||
String incomplete = incompleteCatalog(completeCatalog());
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
HttpServer server = server(incomplete, requests);
|
||||
try (URLClassLoader resources = resources();
|
||||
RemoteLanguageCatalog catalog = catalog(server, resources)) {
|
||||
Path cache = catalog.cacheFile(LOCALE);
|
||||
Files.createDirectories(cache.getParent());
|
||||
String cached = "{\"locale\":\"de_DE\",\"messages\":{}}";
|
||||
Files.writeString(cache, cached);
|
||||
|
||||
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
|
||||
() -> catalog.readOrDownload(LOCALE, IrisLanguage::validateDownload));
|
||||
assertTrue(failure.getMessage().contains(MISSING_KEY));
|
||||
assertEquals(cached, Files.readString(cache));
|
||||
assertEquals(1, requests.get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteUpstreamDoesNotCreateACacheFile() throws Exception {
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
HttpServer server = server(incompleteCatalog(completeCatalog()), requests);
|
||||
try (URLClassLoader resources = resources();
|
||||
RemoteLanguageCatalog catalog = catalog(server, resources)) {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> catalog.readOrDownload(LOCALE, IrisLanguage::validateDownload));
|
||||
assertFalse(Files.exists(catalog.cacheFile(LOCALE)));
|
||||
assertEquals(1, requests.get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialOperatorOverridesRemainValid() {
|
||||
LocaleOverlay overlay = IrisLanguage.parseOverlay("override", LOCALE, """
|
||||
{"locale":"de_DE","messages":{"iris.command.unknown":"Unbekannter Iris-Befehl"}}
|
||||
""");
|
||||
|
||||
assertEquals(1, overlay.values().size());
|
||||
assertTrue(LocalizationValidator.validate(IrisLanguage.catalog(), List.of(overlay)).errors().isEmpty());
|
||||
}
|
||||
|
||||
private String completeCatalog() throws IOException {
|
||||
return Files.readString(Path.of("src/main/resources/languages", LOCALE + ".json"));
|
||||
}
|
||||
|
||||
private String incompleteCatalog(String complete) {
|
||||
JsonObject document = JsonParser.parseString(complete).getAsJsonObject();
|
||||
assertTrue(document.getAsJsonObject("messages").remove(MISSING_KEY) != null);
|
||||
return document.toString();
|
||||
}
|
||||
|
||||
private HttpServer server(String content, AtomicInteger requests) throws IOException {
|
||||
byte[] response = content.getBytes(StandardCharsets.UTF_8);
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
server.createContext("/master/languages/de_DE.json", exchange -> respond(exchange, response, requests));
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
private void respond(HttpExchange exchange, byte[] response, AtomicInteger requests) throws IOException {
|
||||
requests.incrementAndGet();
|
||||
try {
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
exchange.getResponseBody().write(response);
|
||||
} finally {
|
||||
exchange.close();
|
||||
}
|
||||
}
|
||||
|
||||
private URLClassLoader resources() throws IOException {
|
||||
Path resources = temporaryFolder.newFolder().toPath();
|
||||
Files.writeString(resources.resolve("source.properties"), "revision=master\nlocales=de_DE\n");
|
||||
return new URLClassLoader(new URL[]{resources.toUri().toURL()}, null);
|
||||
}
|
||||
|
||||
private RemoteLanguageCatalog catalog(HttpServer server, ClassLoader resources) throws IOException {
|
||||
return RemoteLanguageCatalog.load(new RemoteLanguageCatalog.Options(
|
||||
"Iris",
|
||||
URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/"),
|
||||
"languages",
|
||||
".json",
|
||||
"source.properties",
|
||||
temporaryFolder.newFolder().toPath(),
|
||||
resources
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,32 @@ public class IrisLanguageTest {
|
||||
|
||||
private LocaleOverlay loadSourceOverlay(String locale) throws Exception {
|
||||
Path source = Path.of("src/main/resources/languages", locale + ".json");
|
||||
return IrisLanguage.parseOverlay(source.toString(), locale, Files.readString(source));
|
||||
return IrisLanguage.parseDownloadedOverlay(source.toString(), locale, Files.readString(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downloadedCatalogSelectsBukkitKeysWithoutAcceptingUnknownOverrides() {
|
||||
String raw = """
|
||||
{"locale":"de_DE","messages":{
|
||||
"iris.command.unknown":"Unbekannter Iris-Befehl",
|
||||
"iris.modded.help.entry.command.version":"Version anzeigen"
|
||||
}}
|
||||
""";
|
||||
LocaleOverlay downloaded = IrisLanguage.parseDownloadedOverlay("download", "de_DE", raw);
|
||||
assertEquals(Set.of(IrisMessages.COMMAND_UNKNOWN.id()), downloaded.values().keySet());
|
||||
assertTrue(LocalizationValidator.validate(IrisLanguage.catalog(), List.of(downloaded)).errors().isEmpty());
|
||||
LocaleOverlay override = IrisLanguage.parseOverlay("override", "de_DE", raw);
|
||||
assertFalse(LocalizationValidator.validate(IrisLanguage.catalog(), List.of(override)).errors().isEmpty());
|
||||
assertEquals("A pregeneration task is already running. Stop it first with /iris pregen stop.",
|
||||
IrisLanguage.plain(IrisMessages.PREGEN_ALREADY_RUNNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downloadedCatalogStillRejectsInvalidActivePlaceholders() {
|
||||
LocaleOverlay downloaded = IrisLanguage.parseDownloadedOverlay("download", "de_DE", """
|
||||
{"locale":"de_DE","messages":{"iris.command.permission_denied":"Keine Erlaubnis"}}
|
||||
""");
|
||||
assertFalse(LocalizationValidator.validate(IrisLanguage.catalog(), List.of(downloaded)).errors().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -231,7 +256,8 @@ public class IrisLanguageTest {
|
||||
}
|
||||
assertValueIntegrity(locale, key.id(), key.englishValue(), overlay.value(key.id()));
|
||||
}
|
||||
assertTrue(locale + " contains too many English placeholder values", translated >= 1050);
|
||||
assertTrue(locale + " contains too many English placeholder values",
|
||||
translated * 10 >= IrisLanguage.catalog().keys().size() * 7);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ if (irisUseLocalVolmLib && irisLocalVolmLibDirectory != null) {
|
||||
dependencySubstitution {
|
||||
substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared'))
|
||||
substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared'))
|
||||
substitute(module('com.github.VolmitSoftware.VolmLib:packaging')).using(project(':packaging'))
|
||||
substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared'))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user