Skip to content

Commit

Permalink
Startup Experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
shartte committed Dec 16, 2024
1 parent b4a1040 commit 2b646ec
Show file tree
Hide file tree
Showing 97 changed files with 4,586 additions and 957 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ gradleutils.version {

println "Version: ${project.version = gradleutils.version}"

tasks.named('updateDaemonJvm') {
jvmVersion = JavaVersion.VERSION_21
}

allprojects {
apply plugin: 'java-library'
apply plugin: 'jacoco'
Expand Down Expand Up @@ -56,6 +60,8 @@ allprojects {
subprojects { subProject ->
subProject.version = rootProject.version

apply plugin : fmlbuild.InDevModulePlugin

jar {
manifest.attributes(
'Git-Commit' : gradleutils.gitInfo.abbreviatedId,
Expand Down
1 change: 1 addition & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ java {
}

dependencies {
implementation "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:1.1.8"
implementation "com.google.code.gson:gson:2.10.1"
}

Expand Down
21 changes: 21 additions & 0 deletions buildSrc/src/main/java/fmlbuild/InDevModulePlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fmlbuild;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;

public class InDevModulePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
var sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
var mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
var generatedVersionDir = project.getLayout().getBuildDirectory().dir("generated/version");
var createVersionProperties = project.getTasks().register("createVersionProperties", WriteVersionPropertiesTask.class, task -> {
task.setDescription("Generates a Module version properties file for use during development and containing more information for production as well.");
task.getOutputDirectory().set(generatedVersionDir);
task.getProjectVersion().set(project.provider(() -> project.getVersion().toString()));
});
mainSourceSet.getResources().srcDir(createVersionProperties.flatMap(WriteVersionPropertiesTask::getOutputDirectory));
}
}
68 changes: 52 additions & 16 deletions buildSrc/src/main/java/fmlbuild/InstallProductionClientTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
Expand Down Expand Up @@ -54,12 +56,6 @@ public abstract class InstallProductionClientTask extends DefaultTask {
@InputFiles
public abstract ConfigurableFileCollection getNfrt();

/**
* The Minecraft version matching the NeoForge version to install.
*/
@Input
public abstract Property<String> getMinecraftVersion();

/**
* The NeoForge version, used for placeholders when launching the game.
* It needs to match the installer used.
Expand Down Expand Up @@ -132,6 +128,20 @@ public abstract class InstallProductionClientTask extends DefaultTask {
@OutputFile
public abstract RegularFileProperty getNeoForgeProgramArgFile();

/**
* Allows the main class from the version profile to be overridden to be something else.
*/
@Input
@Optional
public abstract Property<String> getMainClass();

/**
* Allows the game directory written to the argument to be overridden.
*/
@Input
@Optional
public abstract Property<String> getGameDir();

@Inject
public InstallProductionClientTask(ExecOperations execOperations) {
this.execOperations = execOperations;
Expand All @@ -157,8 +167,11 @@ public void install() throws Exception {
}
});

var minecraftVersion = getMinecraftVersion().get();
var neoForgeVersion = getNeoForgeVersion().get();
var manifests = loadVersionManifests(installDir, "neoforge-" + neoForgeVersion);
var minecraftManifest = manifests.getFirst();
var neoForgeManifest = manifests.getLast();
var minecraftVersion = minecraftManifest.getAsJsonPrimitive("id").getAsString();

// Download Minecraft Assets and read the asset index id and root for the program arguments
var assetPropertiesFile = new File(getTemporaryDir(), "asset.properties");
Expand All @@ -183,11 +196,13 @@ public void install() throws Exception {
var nativesDir = installDir.resolve("natives");
Files.createDirectories(nativesDir);

var gameDir = getGameDir().orElse(getInstallDir().getAsFile().map(File::getAbsolutePath));

// Set up the placeholders generally used by Vanilla profiles in their argument definitions.
var placeholders = new HashMap<String, String>();
placeholders.put("auth_player_name", "FMLDev");
placeholders.put("version_name", minecraftVersion);
placeholders.put("game_directory", getInstallDir().getAsFile().get().getAbsolutePath());
placeholders.put("game_directory", gameDir.get());
placeholders.put("auth_uuid", "00000000-0000-4000-8000-000000000000");
placeholders.put("auth_access_token", "0");
placeholders.put("clientid", "0");
Expand All @@ -203,21 +218,20 @@ public void install() throws Exception {
placeholders.put("library_directory", getLibrariesDir().get().getAsFile().getAbsolutePath());
placeholders.put("classpath_separator", File.pathSeparator);

writeArgFiles(installDir, minecraftVersion, placeholders, getVanillaJvmArgFile(), getVanillaMainClassArgFile(), getVanillaProgramArgFile());
writeArgFiles(installDir, "neoforge-" + neoForgeVersion, placeholders, getNeoForgeJvmArgFile(), getNeoForgeMainClassArgFile(), getNeoForgeProgramArgFile());
writeArgFiles(minecraftManifest, placeholders, getVanillaJvmArgFile(), getVanillaMainClassArgFile(), getVanillaProgramArgFile());
writeArgFiles(neoForgeManifest, placeholders, getNeoForgeJvmArgFile(), getNeoForgeMainClassArgFile(), getNeoForgeProgramArgFile());
}

private void writeArgFiles(Path installDir,
String profileName,
private void writeArgFiles(JsonObject manifest,
HashMap<String, String> placeholders,
RegularFileProperty jvmArgFileDestination,
RegularFileProperty mainClassArgFileDestination,
RegularFileProperty programArgFileDestination) throws IOException {
// Read back the version manifest and get the startup arguments
var manifestPath = installDir.resolve("versions").resolve(profileName).resolve(profileName + ".json");
var manifest = readJson(manifestPath);

var mainClass = manifest.getAsJsonPrimitive("mainClass").getAsString();
var mainClass = Objects.requireNonNullElse(
getMainClass().getOrNull(),
manifest.getAsJsonPrimitive("mainClass").getAsString()
);

// Vanilla Arguments
var programArgs = getArguments(manifest, "game");
Expand Down Expand Up @@ -315,6 +329,28 @@ private static boolean isCurrentOsArch(String arch) {
};
}

// Returns the inherited manifests first
private static List<JsonObject> loadVersionManifests(Path installDir, String versionId) {
// Read back the version manifest and get the startup arguments
var manifestPath = installDir.resolve("versions").resolve(versionId).resolve(versionId + ".json");
JsonObject manifest;
try {
manifest = readJson(manifestPath);
} catch (IOException e) {
throw new GradleException("Failed to read launcher profile " + manifestPath, e);
}

var result = new ArrayList<JsonObject>();
var inheritsFrom = manifest.getAsJsonPrimitive("inheritsFrom");
if (inheritsFrom != null) {
result.addAll(loadVersionManifests(installDir, inheritsFrom.getAsString()));
}

result.add(manifest);

return result;
}

private static JsonObject readJson(Path path) throws IOException {
try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return new Gson().fromJson(reader, JsonObject.class);
Expand Down
16 changes: 14 additions & 2 deletions buildSrc/src/main/java/fmlbuild/InstallProductionServerTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
Expand Down Expand Up @@ -70,6 +71,13 @@ public abstract class InstallProductionServerTask extends DefaultTask {
@OutputFile
public abstract RegularFileProperty getNeoForgeMainClassArgFile();

/**
* Allows the main class from the version profile to be overridden to be something else.
*/
@Input
@Optional
public abstract Property<String> getMainClass();

@Inject
public InstallProductionServerTask(ExecOperations execOperations) {
this.execOperations = execOperations;
Expand All @@ -92,7 +100,7 @@ public void install() throws Exception {
});

// We need to know the name of the main class to split the arg-file into JVM and program arguments
var mainClass = getMainClass();
var mainClass = getEffectiveMainClass();
// The difference here is only really in path separators...
var argFileName = File.pathSeparatorChar == ':' ? "unix_args.txt" : "win_args.txt";
var argFilePath = installDir.resolve("libraries/net/neoforged/neoforge/" + getNeoForgeVersion().get() + "/" + argFileName);
Expand Down Expand Up @@ -122,7 +130,11 @@ public void install() throws Exception {
Files.writeString(getNeoForgeProgramArgFile().getAsFile().get().toPath(), programArgs, StandardCharsets.UTF_8);
}

private String getMainClass() throws IOException {
private String getEffectiveMainClass() throws IOException {
if (getMainClass().isPresent()) {
return getMainClass().get();
}

String versionContent;
try (var zf = new ZipFile(getInstaller().getSingleFile())) {
var entry = zf.getEntry("version.json");
Expand Down
10 changes: 10 additions & 0 deletions buildSrc/src/main/java/fmlbuild/NeoForgeInstallation.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,14 @@ public String getName() {
* Where the installation should be made.
*/
public abstract DirectoryProperty getDirectory();

/**
* The main class to use. If not set, the main class from the version profile is used.
*/
public abstract Property<String> getMainClass();

/**
* Allows the game directory to be overriden. Can be set to the directory of a modpack, for example.
*/
public abstract Property<String> getGameDir();
}
18 changes: 9 additions & 9 deletions buildSrc/src/main/java/fmlbuild/NeoForgeInstallationsPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ private void addClientInstallation(Project project, Configuration nfrtCliConfig,
task.setGroup("fml/installations");
task.getInstaller().from(installerConfig);
task.getNfrt().from(nfrtCliConfig);
task.getMinecraftVersion().set(installation.getMinecraftVersion());
task.getNeoForgeVersion().set(installation.getVersion());
task.getInstallDir().set(installation.getDirectory());
task.getAssetsDir().set(task.getInstallDir().dir("assets"));
task.getLibrariesDir().set(task.getInstallDir().dir("libraries"));
task.getMainClass().set(installation.getMainClass());
task.getGameDir().set(installation.getGameDir());

// Write the JVM args to files
task.getVanillaJvmArgFile().set(installation.getVanillaJvmArgFile());
Expand All @@ -102,21 +103,20 @@ private void addServerInstallation(Project project, NeoForgeServerInstallation i
config.setCanBeResolved(true);
config.setCanBeConsumed(false);
config.setTransitive(false);
config.withDependencies(dependencies -> {
dependencies.addLater(installation
.getVersion()
.map(v -> depFactory
.create("net.neoforged:neoforge:" + v).capabilities(caps -> {
caps.requireCapability("net.neoforged:neoforge-installer");
})));
});
config.getDependencies().addLater(installation
.getVersion()
.map(v -> depFactory
.create("net.neoforged:neoforge:" + v).capabilities(caps -> {
caps.requireCapability("net.neoforged:neoforge-installer");
})));
});

project.getTasks().register("installNeoForge" + capitalizedName, InstallProductionServerTask.class, task -> {
task.setGroup("fml/installations");
task.getInstaller().from(installerConfig);
task.getInstallDir().set(installation.getDirectory());
task.getNeoForgeVersion().set(installation.getVersion());
task.getMainClass().set(installation.getMainClass());

// Write the JVM args to files
task.getNeoForgeJvmArgFile().set(installation.getNeoForgeJvmArgFile());
Expand Down
6 changes: 6 additions & 0 deletions buildSrc/src/main/java/fmlbuild/RunConfigurationSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public abstract class RunConfigurationSettings implements Named {
@Inject
public RunConfigurationSettings(Project project, String name) {
this.name = name;
getIdeName().convention(name);
getWorkingDirectory().convention(project.getLayout().getProjectDirectory());
}

Expand All @@ -39,6 +40,11 @@ public String getName() {
*/
public abstract Property<String> getTaskGroup();

/**
* Name for the run configuration in the IDE.
*/
public abstract Property<String> getIdeName();

/**
* The main class to launch.
*/
Expand Down
59 changes: 59 additions & 0 deletions buildSrc/src/main/java/fmlbuild/RunConfigurationsPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@
import org.gradle.api.Task;
import org.gradle.api.file.Directory;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.plugins.ExtensionAware;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.jvm.toolchain.JavaToolchainService;

import javax.inject.Inject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.gradle.plugins.ide.idea.model.*;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.gradle.ext.*;

public abstract class RunConfigurationsPlugin implements Plugin<Project> {
@Inject
Expand Down Expand Up @@ -91,6 +96,42 @@ public void apply(Project project) {
runConfigurations.whenObjectRemoved(installation -> {
throw new GradleException("Cannot remove installations once they have been registered");
});

project.afterEvaluate(ignored -> {
var ijRunConfigs = getIntelliJRunConfigurations(project);
if (ijRunConfigs == null) {
return;
}

for (var settings : runConfigurations) {
var sourceSet = sourceSets.getByName(settings.getName());

var runtimeModulesConfig = project.getConfigurations().getByName(getRuntimeModuleConfigName(settings));

var app = new Application(settings.getIdeName().get(), project);
app.setModuleRef(new ModuleRef(project, sourceSet)); // TODO: Use MDG utility since idea-ext is just wrong
app.setMainClass(settings.getMainClass().get());
var effectiveJvmArgs = new ArrayList<>(settings.getJvmArguments().get());
for (var entry : settings.getSystemProperties().get().entrySet()) {
effectiveJvmArgs.add("-D" + entry.getKey() + "=" + entry.getValue());
}

if (!runtimeModulesConfig.isEmpty()) {
effectiveJvmArgs.add("-p");
var modulePath = runtimeModulesConfig.getFiles().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator));
effectiveJvmArgs.add(modulePath);
}

app.setJvmArgs(
effectiveJvmArgs.stream().map(RunUtils::escapeJvmArg).collect(Collectors.joining(" "))
);
app.setProgramParameters(
settings.getProgramArguments().get().stream().map(RunUtils::escapeJvmArg).collect(Collectors.joining(" "))
);
app.setWorkingDirectory(settings.getWorkingDirectory().getAsFile().get().getAbsolutePath());
ijRunConfigs.add(app);
}
});
}

private static String getRuntimeModuleConfigName(RunConfigurationSettings runConfig) {
Expand All @@ -103,4 +144,22 @@ private static void configureJavaExec(Task task) {
javaExec.workingDir(inputProps.get("runWorkingDirectory"));
javaExec.args((List<?>) inputProps.get("runProgramArgs"));
}

private static @Nullable RunConfigurationContainer getIntelliJRunConfigurations(Project project) {
var rootProject = project.getRootProject();

var ideaModel = (IdeaModel) rootProject.getExtensions().findByName("idea");
if (ideaModel == null) {
return null;
}
var ideaProject = ideaModel.getProject();
if (ideaProject == null) {
return null;
}
var projectSettings = ((ExtensionAware) ideaProject).getExtensions().findByType(ProjectSettings.class);
if (projectSettings == null) {
return null;
}
return (RunConfigurationContainer) ((ExtensionAware) projectSettings).getExtensions().findByName("runConfigurations");
}
}
Loading

0 comments on commit 2b646ec

Please sign in to comment.