From db65759c88288a756f4a488373047a0c490cd8c4 Mon Sep 17 00:00:00 2001 From: modmuss Date: Thu, 9 May 2024 09:10:05 +0100 Subject: [PATCH] Experimental Sandbox support (#1107) --- .../net/fabricmc/loom/LoomGradlePlugin.java | 4 +- .../loom/configuration/ide/RunConfig.java | 22 +-- .../configuration/ide/RunConfigSettings.java | 16 ++ .../sandbox/SandboxConfiguration.java | 98 ++++++++++ .../sandbox/SandboxMetadata.java | 170 ++++++++++++++++++ .../net/fabricmc/loom/util/Constants.java | 3 + .../loom/util/fmj/FabricModJsonUtils.java | 14 +- .../loom/test/integration/SandboxTest.groovy | 112 ++++++++++++ .../loom/test/unit/SandboxMetadataTest.groovy | 74 ++++++++ .../loom/test/util/ZipTestUtils.groovy | 8 +- .../test/unit/sandbox/SandboxEntrypoint.java | 40 +++++ 11 files changed, 541 insertions(+), 20 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxMetadata.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/SandboxTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/SandboxMetadataTest.groovy create mode 100644 src/test/java/net/fabricmc/loom/test/unit/sandbox/SandboxEntrypoint.java diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 71248d2d..dabc1c77 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -41,6 +41,7 @@ import net.fabricmc.loom.configuration.LoomConfigurations; import net.fabricmc.loom.configuration.MavenPublication; import net.fabricmc.loom.configuration.ide.IdeConfiguration; import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration; +import net.fabricmc.loom.configuration.sandbox.SandboxConfiguration; import net.fabricmc.loom.decompilers.DecompilerConfiguration; import net.fabricmc.loom.extension.LoomFiles; import net.fabricmc.loom.extension.LoomGradleExtensionImpl; @@ -63,7 +64,8 @@ public class LoomGradlePlugin implements BootstrappedPlugin { LoomTasks.class, DecompilerConfiguration.class, IdeaConfiguration.class, - IdeConfiguration.class + IdeConfiguration.class, + SandboxConfiguration.class ); @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index af1ffd3a..d6e1cd10 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -113,18 +113,6 @@ public class RunConfig { return e; } - private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment, boolean appendProjectPath) { - if (appendProjectPath && !extension.isRootProject()) { - runConfig.configName += " (" + project.getPath() + ")"; - } - - runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); - - runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main"; - runConfig.vmArgs.add("-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath())); - runConfig.vmArgs.add("-Dfabric.dli.env=" + environment.toLowerCase()); - } - // Turns camelCase/PascalCase into Capital Case // caseConversionExample -> Case Conversion Example private static String capitalizeCamelCaseName(String name) { @@ -181,7 +169,15 @@ public class RunConfig { boolean appendProjectPath = settings.getAppendProjectPathToConfigName().get(); RunConfig runConfig = new RunConfig(); runConfig.configName = configName; - populate(project, extension, runConfig, environment, appendProjectPath); + + if (appendProjectPath && !extension.isRootProject()) { + runConfig.configName += " (" + project.getPath() + ")"; + } + + runConfig.mainClass = settings.devLaunchMainClass().get(); + runConfig.vmArgs.add("-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath())); + runConfig.vmArgs.add("-Dfabric.dli.env=" + environment.toLowerCase()); + runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); runConfig.ideaModuleName = IdeaUtils.getIdeaModuleName(new SourceSetReference(sourceSet, project)); runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir; runConfig.runDir = runDir; diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java index 6cf05fbd..b5547e81 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -40,6 +40,7 @@ import org.gradle.api.Named; import org.gradle.api.Project; import org.gradle.api.provider.Property; import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; @@ -97,6 +98,14 @@ public class RunConfigSettings implements Named { */ private final Property mainClass; + /** + * The true entrypoint, this is usually dev launch injector. + * This should not be changed unless you know what you are doing. + */ + @ApiStatus.Internal + @ApiStatus.Experimental + private final Property devLaunchMainClass; + /** * The source set getter, which obtains the source set from the given project. */ @@ -136,6 +145,7 @@ public class RunConfigSettings implements Named { Objects.requireNonNull(defaultMainClass, "Run config " + name + " must specify default main class"); return RunConfig.getMainClass(environment, extension, defaultMainClass); })); + this.devLaunchMainClass = project.getObjects().property(String.class).convention("net.fabricmc.devlaunchinjector.Main"); setSource(p -> { final String sourceSetName = MinecraftSourceSets.get(p).getSourceSetForEnv(getEnvironment()); @@ -369,4 +379,10 @@ public class RunConfigSettings implements Named { public void setIdeConfigGenerated(boolean ideConfigGenerated) { this.ideConfigGenerated = ideConfigGenerated; } + + @ApiStatus.Internal + @ApiStatus.Experimental + public Property devLaunchMainClass() { + return devLaunchMainClass; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java new file mode 100644 index 00000000..a07ee658 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java @@ -0,0 +1,98 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.sandbox; + +import java.nio.file.Path; +import java.util.Objects; + +import javax.inject.Inject; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.dsl.DependencyFactory; +import org.gradle.api.plugins.JavaPlugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.Platform; +import net.fabricmc.loom.util.gradle.GradleUtils; + +/** + * Allows the user to specify a sandbox maven artifact as a gradle property. + * The sandbox jar is read to figure out if it's supported on the current platform. + * If it is, its added to the runtime classpath and a new client run config is created + */ +public abstract class SandboxConfiguration implements Runnable { + private static final Logger LOGGER = LoggerFactory.getLogger(SandboxConfiguration.class); + + @Inject + protected abstract Project getProject(); + + @Inject + public abstract DependencyFactory getDependencyFactory(); + + @Override + public void run() { + if (getProject().findProperty(Constants.Properties.SANDBOX) == null) { + LOGGER.debug("No fabric sandbox property set"); + return; + } + + GradleUtils.afterSuccessfulEvaluation(getProject(), this::evaluate); + } + + private void evaluate() { + final String sandboxNotation = (String) Objects.requireNonNull(getProject().findProperty(Constants.Properties.SANDBOX)); + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final ExternalModuleDependency dependency = getDependencyFactory().create(sandboxNotation); + final Configuration configuration = getProject().getConfigurations().detachedConfiguration(dependency); + final Path sandboxJar = configuration.getSingleFile().toPath(); + final SandboxMetadata metadata = SandboxMetadata.readFromJar(sandboxJar); + + if (!metadata.supportsPlatform(Platform.CURRENT)) { + LOGGER.info("Sandbox does not support the current platform"); + return; + } + + getProject().getDependencies().add(JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME, dependency); + + extension.getRuns().create("clientSandbox", settings -> { + RunConfigSettings clientRun = extension.getRuns().getByName("client"); + + settings.inherit(clientRun); + + settings.name("Client Sandbox"); + + // The sandbox also acts as DLI + // Set the sandbox as the true main class + settings.devLaunchMainClass().set(metadata.mainClass()); + settings.property("fabric.sandbox.realMain", clientRun.getMainClass().get()); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxMetadata.java b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxMetadata.java new file mode 100644 index 00000000..2c97e8f2 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxMetadata.java @@ -0,0 +1,170 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.sandbox; + +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.ParseException; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.getJsonObject; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readInt; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.fabricmc.loom.util.Platform; +import net.fabricmc.loom.util.ZipUtils; + +public sealed interface SandboxMetadata permits SandboxMetadata.V1 { + String SANDBOX_METADATA_FILENAME = "fabric-sandbox.json"; + + static SandboxMetadata readFromJar(Path path) { + try { + JsonObject jsonObject = ZipUtils.unpackGson(path, SANDBOX_METADATA_FILENAME, JsonObject.class); + int version = readInt(jsonObject, "version"); + return switch (version) { + case 1 -> SandboxMetadata.V1.parseV1(jsonObject); + default -> throw new UnsupportedOperationException("Unsupported sandbox metadata version: " + version); + }; + } catch (IOException e) { + throw new UncheckedIOException("Failed to read: " + SANDBOX_METADATA_FILENAME, e); + } + } + + /** + * @return The main class of the sandbox. + */ + String mainClass(); + + /** + * @param platform The platform to check. + * @return True if the sandbox supports the platform, false otherwise. + */ + boolean supportsPlatform(Platform platform); + + record V1(String mainClass, Map> supportedPlatforms) implements SandboxMetadata { + static V1 parseV1(JsonObject jsonObject) { + String mainClass = readString(jsonObject, "mainClass"); + JsonObject platforms = getJsonObject(jsonObject, "platforms"); + + Map> supportedPlatforms = new HashMap<>(); + + for (Map.Entry entry : platforms.entrySet()) { + if (!entry.getValue().isJsonArray()) { + throw new ParseException("Unexpected json array type for key (%s)", entry.getKey()); + } + + List architectures = new ArrayList<>(); + + for (JsonElement element : entry.getValue().getAsJsonArray()) { + if (!(element.isJsonPrimitive() && element.getAsJsonPrimitive().isString())) { + throw new ParseException("Unexpected json primitive type for key (%s)", entry.getKey()); + } + + architectures.add(parseArchitecture(element.getAsString())); + } + + supportedPlatforms.put(parseOperatingSystem(entry.getKey()), Collections.unmodifiableList(architectures)); + } + + return new V1(mainClass, Collections.unmodifiableMap(supportedPlatforms)); + } + + @Override + public boolean supportsPlatform(Platform platform) { + for (Map.Entry> entry : supportedPlatforms.entrySet()) { + if (!entry.getKey().compatibleWith(platform)) { + continue; + } + + for (Architecture architecture : entry.getValue()) { + if (architecture.compatibleWith(platform)) { + return true; + } + } + } + + return false; + } + } + + enum OperatingSystem { + WINDOWS, + MAC_OS, + LINUX; + + public boolean compatibleWith(Platform platform) { + final Platform.OperatingSystem operatingSystem = platform.getOperatingSystem(); + + return switch (this) { + case WINDOWS -> operatingSystem.isWindows(); + case MAC_OS -> operatingSystem.isMacOS(); + case LINUX -> operatingSystem.isLinux(); + }; + } + } + + enum Architecture { + X86_64, + ARM64; + + public boolean compatibleWith(Platform platform) { + final Platform.Architecture architecture = platform.getArchitecture(); + + if (!architecture.is64Bit()) { + return false; + } + + return switch (this) { + case X86_64 -> !architecture.isArm(); + case ARM64 -> architecture.isArm(); + }; + } + } + + private static OperatingSystem parseOperatingSystem(String os) { + return switch (os) { + case "windows" -> OperatingSystem.WINDOWS; + case "macos" -> OperatingSystem.MAC_OS; + case "linux" -> OperatingSystem.LINUX; + default -> throw new ParseException("Unsupported sandbox operating system: %s", os); + }; + } + + private static Architecture parseArchitecture(String arch) { + return switch (arch) { + case "x86_64" -> Architecture.X86_64; + case "arm64" -> Architecture.ARM64; + default -> throw new ParseException("Unsupported sandbox architecture: %s", arch); + }; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index ff94b011..05798ebe 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -24,6 +24,7 @@ package net.fabricmc.loom.util; +import org.jetbrains.annotations.ApiStatus; import org.objectweb.asm.Opcodes; public class Constants { @@ -132,6 +133,8 @@ public class Constants { public static final String DISABLE_REMAPPED_VARIANTS = "fabric.loom.disableRemappedVariants"; public static final String DISABLE_PROJECT_DEPENDENT_MODS = "fabric.loom.disableProjectDependentMods"; public static final String LIBRARY_PROCESSORS = "fabric.loom.libraryProcessors"; + @ApiStatus.Experimental + public static final String SANDBOX = "fabric.loom.experimental.sandbox"; } public static final class Manifest { diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java index 5559551b..d1c972ab 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java @@ -50,6 +50,16 @@ public final class FabricModJsonUtils { return element.getAsInt(); } + public static JsonObject getJsonObject(JsonObject jsonObject, String key) { + final JsonElement element = getElement(jsonObject, key); + + if (!element.isJsonObject()) { + throw new ParseException("Unexpected json object type for key (%s)", key); + } + + return element.getAsJsonObject(); + } + // Ensure that the schemaVersion json entry, is first in the json file // This exercises an optimisation here: https://github.com/FabricMC/fabric-loader/blob/d69cb72d26497e3f387cf46f9b24340b402a4644/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java#L62 public static JsonObject optimizeFmj(JsonObject json) { @@ -90,8 +100,8 @@ public final class FabricModJsonUtils { } } - static class ParseException extends RuntimeException { - ParseException(String message, Object... args) { + public static class ParseException extends RuntimeException { + public ParseException(String message, Object... args) { super(String.format(Locale.ROOT, message, args)); } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/SandboxTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/SandboxTest.groovy new file mode 100644 index 00000000..b7b4ea9f --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/SandboxTest.groovy @@ -0,0 +1,112 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.integration + +import java.nio.file.Path + +import org.intellij.lang.annotations.Language +import spock.lang.Specification +import spock.lang.Unroll + +import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper +import net.fabricmc.loom.test.unit.sandbox.SandboxEntrypoint +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.test.util.ZipTestUtils +import net.fabricmc.loom.util.Constants +import net.fabricmc.loom.util.ZipUtils + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class SandboxTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "sandbox (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << ''' + repositories { + mavenLocal() + } + + dependencies { + minecraft "com.mojang:minecraft:1.20.4" + mappings "net.fabricmc:yarn:1.20.4+build.3:v2" + modImplementation "net.fabricmc:fabric-loader:0.15.10" + } + ''' + new File(gradle.getProjectDir(), "gradle.properties").text = "${Constants.Properties.SANDBOX}=net.fabricmc.loom.test:sandbox:1.0.0" + + def mavenHelper = new LocalMavenHelper("net.fabricmc.loom.test", "sandbox", "1.0.0", null, gradle.getMavenLocalDir().toPath()) + mavenHelper.copyToMaven(createDummySandboxJar(), null) + mavenHelper.savePom() + + when: + def result = gradle.run(task: "runClientSandbox") + + then: + result.task(":runClientSandbox").outcome == SUCCESS + result.output.contains("Running real main: net.fabricmc.loader.impl.launch.knot.KnotClient") + // Ensure that we weren't launched via DLI + !result.output.contains("at net.fabricmc.devlaunchinjector.Main") + + where: + version << STANDARD_TEST_VERSIONS + } + + static Path createDummySandboxJar() { + def zip = ZipTestUtils.createZip(["fabric-sandbox.json": METADATA_JSON], ".jar") + ZipUtils.add(zip, "net/fabricmc/loom/test/unit/sandbox/SandboxEntrypoint.class", getClassBytes(SandboxEntrypoint.class)) + return zip + } + + static byte[] getClassBytes(Class clazz) { + return clazz.classLoader.getResourceAsStream(clazz.name.replace('.', '/') + ".class").withCloseable { + it.bytes + } + } + + // Ensure that all platforms that the test may run on are listed here + @Language("json") + private static String METADATA_JSON = """ + { + "version": 1, + "mainClass": "net.fabricmc.loom.test.unit.sandbox.SandboxEntrypoint", + "platforms": { + "windows": [ + "arm64", + "x86_64" + ], + "macos": [ + "arm64", + "x86_64" + ], + "linux": [ + "arm64", + "x86_64" + ] + } + } + """ +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/SandboxMetadataTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/SandboxMetadataTest.groovy new file mode 100644 index 00000000..2752e6ca --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/SandboxMetadataTest.groovy @@ -0,0 +1,74 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit + +import java.nio.file.Path + +import org.intellij.lang.annotations.Language +import spock.lang.Specification + +import net.fabricmc.loom.configuration.sandbox.SandboxMetadata +import net.fabricmc.loom.test.util.PlatformTestUtils +import net.fabricmc.loom.test.util.ZipTestUtils + +class SandboxMetadataTest extends Specification { + def "test sandbox metadata"() { + given: + def sandboxJar = createSandboxJar(""" + { + "version": 1, + "mainClass": "net.fabricmc.loom.test.Main", + "platforms": { + "windows": [ + "arm64", + "x86_64" + ], + "macos": [ + "arm64" + ] + } + } + """) + + when: + def metadata = SandboxMetadata.readFromJar(sandboxJar) + + then: + metadata.mainClass() == "net.fabricmc.loom.test.Main" + + metadata.supportsPlatform(PlatformTestUtils.WINDOWS_X64) + metadata.supportsPlatform(PlatformTestUtils.WINDOWS_ARM64) + + !metadata.supportsPlatform(PlatformTestUtils.LINUX_X64) + !metadata.supportsPlatform(PlatformTestUtils.LINUX_ARM64) + + !metadata.supportsPlatform(PlatformTestUtils.MAC_OS_X64) + metadata.supportsPlatform(PlatformTestUtils.MAC_OS_ARM64) + } + + private static Path createSandboxJar(@Language("json") String json) { + return ZipTestUtils.createZip(["fabric-sandbox.json": json]) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy index 75add96d..be637b4c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy @@ -33,17 +33,17 @@ import java.util.jar.Manifest import net.fabricmc.loom.util.FileSystemUtil class ZipTestUtils { - static Path createZip(Map entries) { + static Path createZip(Map entries, String suffix = ".zip") { return createZipFromBytes(entries.collectEntries { k, v -> [ k, v.getBytes(StandardCharsets.UTF_8) ] - }) + }, suffix) } - static Path createZipFromBytes(Map entries) { - def file = Files.createTempFile("loom-test", ".zip") + static Path createZipFromBytes(Map entries, String suffix = ".zip") { + def file = Files.createTempFile("loom-test", suffix) Files.delete(file) FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip -> diff --git a/src/test/java/net/fabricmc/loom/test/unit/sandbox/SandboxEntrypoint.java b/src/test/java/net/fabricmc/loom/test/unit/sandbox/SandboxEntrypoint.java new file mode 100644 index 00000000..c4b061c8 --- /dev/null +++ b/src/test/java/net/fabricmc/loom/test/unit/sandbox/SandboxEntrypoint.java @@ -0,0 +1,40 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.sandbox; + +public class SandboxEntrypoint { + public static void main(String[] args) { + String realMain = System.getProperty("fabric.sandbox.realMain"); + + if (realMain == null) { + throw new IllegalStateException("Unable to find real main"); + } + + // Print the curren stacktrace, we can use this to ensure that we haven't been launched via DLI + Thread.dumpStack(); + + System.out.println("Running real main: " + realMain); + } +}