Skip to content

Commit

Permalink
Experimental Sandbox support (#1107)
Browse files Browse the repository at this point in the history
  • Loading branch information
modmuss50 authored May 9, 2024
1 parent e54d33a commit db65759
Show file tree
Hide file tree
Showing 11 changed files with 541 additions and 20 deletions.
4 changes: 3 additions & 1 deletion src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
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;
Expand All @@ -63,7 +64,8 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
LoomTasks.class,
DecompilerConfiguration.class,
IdeaConfiguration.class,
IdeConfiguration.class
IdeConfiguration.class,
SandboxConfiguration.class
);

@Override
Expand Down
22 changes: 9 additions & 13 deletions src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,6 @@ public Element addXml(Node parent, String name, Map<String, String> values) {
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) {
Expand Down Expand Up @@ -181,7 +169,15 @@ public static RunConfig runConfig(Project project, RunConfigSettings settings) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
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;
Expand Down Expand Up @@ -97,6 +98,14 @@ public class RunConfigSettings implements Named {
*/
private final Property<String> 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<String> devLaunchMainClass;

/**
* The source set getter, which obtains the source set from the given project.
*/
Expand Down Expand Up @@ -136,6 +145,7 @@ public RunConfigSettings(Project project, String name) {
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());
Expand Down Expand Up @@ -369,4 +379,10 @@ public boolean isIdeConfigGenerated() {
public void setIdeConfigGenerated(boolean ideConfigGenerated) {
this.ideConfigGenerated = ideConfigGenerated;
}

@ApiStatus.Internal
@ApiStatus.Experimental
public Property<String> devLaunchMainClass() {
return devLaunchMainClass;
}
}
Original file line number Diff line number Diff line change
@@ -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());
});
}
}
Original file line number Diff line number Diff line change
@@ -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<OperatingSystem, List<Architecture>> supportedPlatforms) implements SandboxMetadata {
static V1 parseV1(JsonObject jsonObject) {
String mainClass = readString(jsonObject, "mainClass");
JsonObject platforms = getJsonObject(jsonObject, "platforms");

Map<OperatingSystem, List<Architecture>> supportedPlatforms = new HashMap<>();

for (Map.Entry<String, JsonElement> entry : platforms.entrySet()) {
if (!entry.getValue().isJsonArray()) {
throw new ParseException("Unexpected json array type for key (%s)", entry.getKey());
}

List<Architecture> 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<OperatingSystem, List<Architecture>> 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);
};
}
}
3 changes: 3 additions & 0 deletions src/main/java/net/fabricmc/loom/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package net.fabricmc.loom.util;

import org.jetbrains.annotations.ApiStatus;
import org.objectweb.asm.Opcodes;

public class Constants {
Expand Down Expand Up @@ -132,6 +133,8 @@ public static final class Properties {
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 {
Expand Down
Loading

0 comments on commit db65759

Please sign in to comment.