diff --git a/build.gradle b/build.gradle index 452575f0..339d8dd1 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,7 @@ dependencies { implementation ('org.ow2.asm:asm-commons:9.0') implementation ('org.ow2.asm:asm-tree:9.0') implementation ('org.ow2.asm:asm-util:9.0') + implementation ('me.tongfei:progressbar:0.9.0') // game handling utils implementation ('net.fabricmc:stitch:0.5.1+build.77') { @@ -154,7 +155,7 @@ license { checkstyle { configFile = file('checkstyle.xml') - toolVersion = '8.25' + toolVersion = '8.39' } checkstyleMain { diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index f26decb3..9b38b1b3 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -44,17 +44,20 @@ import com.google.gson.JsonObject; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.plugins.BasePluginConvention; +import org.jetbrains.annotations.ApiStatus; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.configuration.LoomDependencyManager; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.MinecraftProvider; @@ -72,7 +75,6 @@ import net.fabricmc.loom.util.function.LazyBool; public class LoomGradleExtension { private static final String FORGE_PROPERTY = "loom.forge"; - public String runDir = "run"; public String refmapName; public String loaderLaunchMethod; public boolean remapMod = true; @@ -111,6 +113,8 @@ public class LoomGradleExtension { } }))); + private NamedDomainObjectContainer runs; + /** * Loom will generate a new genSources task (with a new name, based off of {@link LoomDecompiler#name()}) * that uses the specified decompiler instead. @@ -177,6 +181,10 @@ public class LoomGradleExtension { public class DataGenConsumer { public void mod(String... modIds) { dataGenMods.addAll(Arrays.asList(modIds)); + + if (modIds.length > 0 && getRuns().findByName("data") == null) { + getRuns().create("data", RunConfigSettings::data); + } } } @@ -209,6 +217,8 @@ public class LoomGradleExtension { this.autoGenIDERuns = isRootProject(); this.unmappedMods = project.files(); this.forge = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(FORGE_PROPERTY)))); + this.runs = project.container(RunConfigSettings.class, + baseName -> new RunConfigSettings(project, baseName)); } /** @@ -384,7 +394,7 @@ public class LoomGradleExtension { @Nullable private Dependency getMixinDependency() { - return recurseProjects((p) -> { + return recurseProjects(p -> { Set configs = new LinkedHashSet<>(); // check compile classpath first Configuration possibleCompileClasspath = p.getConfigurations().findByName("compileClasspath"); @@ -401,11 +411,7 @@ public class LoomGradleExtension { return true; } - if (name.equalsIgnoreCase("sponge-mixin") && group.equalsIgnoreCase("net.fabricmc")) { - return true; - } - - return false; + return name.equalsIgnoreCase("sponge-mixin") && group.equalsIgnoreCase("net.fabricmc"); }); }); } @@ -561,4 +567,14 @@ public class LoomGradleExtension { public List getDecompilers() { return decompilers; } + + @ApiStatus.Experimental + public void runs(Action> action) { + action.execute(runs); + } + + @ApiStatus.Experimental + public NamedDomainObjectContainer getRuns() { + return runs; + } } diff --git a/src/main/java/net/fabricmc/loom/build/NestedJars.java b/src/main/java/net/fabricmc/loom/build/NestedJars.java index 172efca9..c88c4487 100644 --- a/src/main/java/net/fabricmc/loom/build/NestedJars.java +++ b/src/main/java/net/fabricmc/loom/build/NestedJars.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -118,9 +119,19 @@ public class NestedJars { for (Task task : remapJarTasks.isEmpty() ? jarTasks : remapJarTasks) { if (task instanceof RemapJarTask) { - fileList.add(((RemapJarTask) task).getArchivePath()); + fileList.addAll(prepareForNesting( + Collections.singleton(((RemapJarTask) task).getArchivePath()), + projectDependency, + new ProjectDependencyMetaExtractor(), + project + )); } else if (task instanceof AbstractArchiveTask) { - fileList.add(((AbstractArchiveTask) task).getArchivePath()); + fileList.addAll(prepareForNesting( + Collections.singleton(((AbstractArchiveTask) task).getArchivePath()), + projectDependency, + new ProjectDependencyMetaExtractor(), + project + )); } } } @@ -136,8 +147,10 @@ public class NestedJars { .stream() .map(ResolvedArtifact::getFile) .collect(Collectors.toSet()), - dependency, project) - ); + dependency, + new ResolvedDependencyMetaExtractor(), + project + )); } } @@ -178,7 +191,7 @@ public class NestedJars { } //This is a good place to do pre-nesting operations, such as adding a fabric.mod.json to a library - private static List prepareForNesting(Set files, ResolvedDependency dependency, Project project) { + private static List prepareForNesting(Set files, D dependency, DependencyMetaExtractor metaExtractor, Project project) { List fileList = new ArrayList<>(); for (File file : files) { @@ -203,7 +216,7 @@ public class NestedJars { throw new RuntimeException("Failed to copy file", e); } - ZipUtil.addEntry(tempFile, "fabric.mod.json", getMod(dependency).getBytes()); + ZipUtil.addEntry(tempFile, "fabric.mod.json", getMod(dependency, metaExtractor).getBytes()); fileList.add(tempFile); } else { // Default copy the jar right in @@ -215,12 +228,12 @@ public class NestedJars { } // Generates a barebones mod for a dependency - private static String getMod(ResolvedDependency dependency) { + private static String getMod(D dependency, DependencyMetaExtractor metaExtractor) { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("schemaVersion", 1); - jsonObject.addProperty("id", (dependency.getModuleGroup() + "_" + dependency.getModuleName()).replaceAll("\\.", "_").toLowerCase(Locale.ENGLISH)); - jsonObject.addProperty("version", dependency.getModuleVersion()); - jsonObject.addProperty("name", dependency.getModuleName()); + jsonObject.addProperty("id", (metaExtractor.group(dependency) + "_" + metaExtractor.name(dependency)).replaceAll("\\.", "_").toLowerCase(Locale.ENGLISH)); + jsonObject.addProperty("version", metaExtractor.version(dependency)); + jsonObject.addProperty("name", metaExtractor.name(dependency)); JsonObject custom = new JsonObject(); custom.addProperty("fabric-loom:generated", true); @@ -232,4 +245,46 @@ public class NestedJars { private static ZipEntryTransformerEntry[] single(ZipEntryTransformerEntry element) { return new ZipEntryTransformerEntry[]{element}; } + + private interface DependencyMetaExtractor { + String group(D dependency); + + String version(D dependency); + + String name(D dependency); + } + + private static final class ProjectDependencyMetaExtractor implements DependencyMetaExtractor { + @Override + public String group(ProjectDependency dependency) { + return dependency.getGroup(); + } + + @Override + public String version(ProjectDependency dependency) { + return dependency.getVersion(); + } + + @Override + public String name(ProjectDependency dependency) { + return dependency.getName(); + } + } + + private static final class ResolvedDependencyMetaExtractor implements DependencyMetaExtractor { + @Override + public String group(ResolvedDependency dependency) { + return dependency.getModuleGroup(); + } + + @Override + public String version(ResolvedDependency dependency) { + return dependency.getModuleVersion(); + } + + @Override + public String name(ResolvedDependency dependency) { + return dependency.getModuleName(); + } + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index cb7be779..d315f75c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -74,21 +74,6 @@ public final class CompileConfiguration { } public static void setupConfigurations(Project project) { - // Force add Mojang and Forge repositories - addMavenRepo(project, "Mojang", "https://libraries.minecraft.net/"); - addMavenRepo(project, "Forge", "https://files.minecraftforge.net/maven/", repo -> { - repo.metadataSources(sources -> { - sources.mavenPom(); - - try { - MavenArtifactRepository.MetadataSources.class.getDeclaredMethod("ignoreGradleMetadataRedirection") - .invoke(sources); - } catch (Throwable ignored) { - // Method not available - } - }); - }); - Configuration modCompileClasspathConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MOD_COMPILE_CLASSPATH); modCompileClasspathConfig.setTransitive(true); Configuration modCompileClasspathMappedConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED); @@ -219,6 +204,17 @@ public final class CompileConfiguration { project1.getRepositories().maven(mavenArtifactRepository -> { mavenArtifactRepository.setName("Forge"); mavenArtifactRepository.setUrl("https://files.minecraftforge.net/maven/"); + + mavenArtifactRepository.metadataSources(sources -> { + sources.mavenPom(); + + try { + MavenArtifactRepository.MetadataSources.class.getDeclaredMethod("ignoreGradleMetadataRedirection") + .invoke(sources); + } catch (Throwable ignored) { + // Method not available + } + }); }); project1.getRepositories().mavenCentral(); diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 91c4ee9d..ae73c145 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -32,6 +32,7 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; @@ -88,6 +89,12 @@ public class AccessWidenerJarProcessor implements JarProcessor { //Remap accessWidener if its not named, allows for AE's to be written in intermediary if (!accessWidener.getNamespace().equals("named")) { try { + List validNamespaces = loomGradleExtension.getMappingsProvider().getMappings().getMetadata().getNamespaces(); + + if (!validNamespaces.contains(accessWidener.getNamespace())) { + throw new UnsupportedOperationException(String.format("Access Widener namespace '%s' is not a valid namespace, it must be one of: '%s'", accessWidener.getNamespace(), String.join(", ", validNamespaces))); + } + TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper(null, "official", "named"); loomGradleExtension.getMinecraftMappedProvider(); tinyRemapper.readClassPath(MinecraftMappedProvider.getRemapClasspath(project)); 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 1e75b56b..c2a0b68c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -31,8 +31,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -44,7 +44,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; @@ -63,6 +62,7 @@ public class RunConfig { public String ideaModuleName; public String vscodeProjectName; public String mainClass; + public String runDirIdeaUrl; public String runDir; public String vmArgs; public String programArgs; @@ -75,7 +75,7 @@ public class RunConfig { this.addXml(root, "module", ImmutableMap.of("name", ideaModuleName)); this.addXml(root, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", mainClass)); - this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDir)); + this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDirIdeaUrl)); if (!Strings.isNullOrEmpty(vmArgs)) { this.addXml(root, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", vmArgs)); @@ -113,8 +113,8 @@ public class RunConfig { return e; } - private static String getIdeaModuleName(Project project) { - String module = project.getName() + ".main"; + private static String getIdeaModuleName(Project project, SourceSet srcs) { + String module = project.getName() + "." + srcs.getName(); while ((project = project.getParent()) != null) { module = project.getName() + "." + module; @@ -123,20 +123,19 @@ public class RunConfig { return module; } - private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String mode) { + private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment) { runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")"; runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); - runConfig.ideaModuleName = getIdeaModuleName(project); runConfig.vscodeProjectName = extension.isRootProject() ? "" : project.getPath(); - runConfig.runDir = "file://$PROJECT_DIR$/" + extension.runDir; runConfig.vmArgs = ""; + runConfig.programArgs = ""; if ("launchwrapper".equals(extension.getLoaderLaunchMethod())) { - runConfig.mainClass = "net.minecraft.launchwrapper.Launch"; - runConfig.programArgs += "--tweakClass " + ("client".equals(mode) ? Constants.LaunchWrapper.DEFAULT_FABRIC_CLIENT_TWEAKER : Constants.LaunchWrapper.DEFAULT_FABRIC_SERVER_TWEAKER); + runConfig.mainClass = "net.minecraft.launchwrapper.Launch"; // TODO What about custom tweakers for run configs? + runConfig.programArgs += "--tweakClass " + ("client".equals(environment) ? Constants.LaunchWrapper.DEFAULT_FABRIC_CLIENT_TWEAKER : Constants.LaunchWrapper.DEFAULT_FABRIC_SERVER_TWEAKER); } else { runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main"; - runConfig.vmArgs = "-Dfabric.dli.config=" + encodeEscaped(extension.getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + mode.toLowerCase(); + runConfig.vmArgs = "-Dfabric.dli.config=" + encodeEscaped(extension.getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + environment.toLowerCase(); } if (extension.isForge()) { @@ -161,7 +160,7 @@ public class RunConfig { JsonObject installerJson = extension.getInstallerJson(); if (installerJson != null) { - List sideKeys = ImmutableList.of(mode, "common"); + List sideKeys = ImmutableList.of(environment, "common"); // copy launchwrapper tweakers if (installerJson.has("launchwrapper")) { @@ -186,50 +185,72 @@ public class RunConfig { } } - public static RunConfig clientRunConfig(Project project) { - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + // Turns camelCase/PascalCase into Capital Case + // caseConversionExample -> Case Conversion Example + private static String capitalizeCamelCaseName(String name) { + if (name.length() == 0) { + return ""; + } - RunConfig ideaClient = new RunConfig(); - ideaClient.configName = "Minecraft Client"; - ideaClient.programArgs = ""; - populate(project, extension, ideaClient, "client"); - ideaClient.vmArgs += getOSClientJVMArgs(); - ideaClient.vmArgs += " -Dfabric.dli.main=" + getMainClass("client", extension); - ideaClient.vscodeBeforeRun = new ArrayList<>(extension.getTasksBeforeRun()); - - return ideaClient; + return name.substring(0, 1).toUpperCase() + name.substring(1).replaceAll("([^A-Z])([A-Z])", "$1 $2"); } - public static RunConfig serverRunConfig(Project project) { + public static RunConfig runConfig(Project project, RunConfigSettings settings) { LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + String name = settings.getName(); - RunConfig ideaServer = new RunConfig(); - ideaServer.configName = "Minecraft Server"; - ideaServer.programArgs = "nogui "; - populate(project, extension, ideaServer, "server"); - ideaServer.vmArgs += " -Dfabric.dli.main=" + getMainClass("server", extension); - ideaServer.vscodeBeforeRun = new ArrayList<>(extension.getTasksBeforeRun()); + String configName = settings.getConfigName(); + String environment = settings.getEnvironment(); + SourceSet sourceSet = settings.getSource(project); - return ideaServer; - } + String defaultMain = settings.getDefaultMainClass(); - public static RunConfig dataRunConfig(Project project) { - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + if (defaultMain == null) { + throw new IllegalArgumentException("Run configuration '" + name + "' must specify 'defaultMainClass'"); + } - RunConfig ideaServer = new RunConfig(); - ideaServer.configName = "Generate Data"; - ideaServer.programArgs = ""; - populate(project, extension, ideaServer, "data"); - ideaServer.vmArgs += " -Dfabric.dli.main=" + getMainClass("data", extension); - ideaServer.vscodeBeforeRun = new ArrayList<>(extension.getTasksBeforeRun()); + if (configName == null) { + configName = ""; + String srcName = sourceSet.getName(); - return ideaServer; - } + if (!srcName.equals(SourceSet.MAIN_SOURCE_SET_NAME)) { + configName += capitalizeCamelCaseName(srcName) + " "; + } - // This can be removed at somepoint, its not ideal but its the best solution I could thing of - public static boolean needsUpgrade(File file) throws IOException { - String contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); - return !(contents.contains("net.fabricmc.devlaunchinjector.Main")); + configName += "Minecraft " + capitalizeCamelCaseName(name); + } + + Objects.requireNonNull(environment, "No environment set for run config"); + + String runDir = settings.getRunDir(); + + if (runDir == null) { + runDir = "run"; + } + + RunConfig runConfig = new RunConfig(); + runConfig.configName = configName; + populate(project, extension, runConfig, environment); + runConfig.ideaModuleName = getIdeaModuleName(project, sourceSet); + runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir; + runConfig.runDir = runDir; + + // Custom parameters + for (String progArg : settings.getProgramArgs()) { + runConfig.programArgs += " " + progArg; + } + + for (String vmArg : settings.getVmArgs()) { + runConfig.vmArgs += " " + vmArg; + } + + runConfig.vmArgs += " -Dfabric.dli.main=" + getMainClass(environment, extension, defaultMain); + + // Remove unnecessary leading/trailing whitespaces we might have generated + runConfig.programArgs = runConfig.programArgs.trim(); + runConfig.vmArgs = runConfig.vmArgs.trim(); + + return runConfig; } public String fromDummy(String dummy) throws IOException { @@ -243,6 +264,7 @@ public class RunConfig { dummyConfig = dummyConfig.replace("%MAIN_CLASS%", mainClass); dummyConfig = dummyConfig.replace("%ECLIPSE_PROJECT%", eclipseProjectName); dummyConfig = dummyConfig.replace("%IDEA_MODULE%", ideaModuleName); + dummyConfig = dummyConfig.replace("%RUN_DIRECTORY%", runDir); dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", programArgs.replaceAll("\"", """)); dummyConfig = dummyConfig.replace("%VM_ARGS%", vmArgs.replaceAll("\"", """)); @@ -276,11 +298,7 @@ public class RunConfig { return ""; } - private static String getMainClass(String side, LoomGradleExtension extension) { - if (extension.isForge()) { - return "net.minecraftforge.userdev.LaunchTesting"; - } - + private static String getMainClass(String side, LoomGradleExtension extension, String defaultMainClass) { JsonObject installerJson = extension.getInstallerJson(); if (installerJson != null && installerJson.has("mainClass")) { @@ -306,7 +324,7 @@ public class RunConfig { return "net.minecraft.launchwrapper.Launch"; } - return "net.fabricmc.loader.launch.knot.Knot" + side.substring(0, 1).toUpperCase(Locale.ROOT) + side.substring(1).toLowerCase(Locale.ROOT); + return defaultMainClass; } private static String encodeEscaped(String s) { diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java new file mode 100644 index 00000000..41cda1b1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -0,0 +1,298 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.ide; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.gradle.api.Named; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.ApiStatus; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.OperatingSystem; + +/** + * Experimental for now, please make sure to direct any suggests towards the github. + */ +@ApiStatus.Experimental +public final class RunConfigSettings implements Named { + /** + * Arguments for the JVM, such as system properties. + */ + private final List vmArgs = new ArrayList<>(); + + /** + * Arguments for the program's main class. + */ + private final List programArgs = new ArrayList<>(); + + /** + * The environment (or side) to run, usually client or server. + */ + private String environment; + + /** + * The full name of the run configuration, i.e. 'Minecraft Client'. + * + *

By default this is determined from the base name. + */ + private String name; + + /** + * The default main class of the run configuration. + * + *

This can be overwritten in {@code fabric_installer.[method].json}. Note that this doesn't take + * priority over the main class specified in the Fabric installer configuration. + */ + private String defaultMainClass; + + /** + * The source set getter, which obtains the source set from the given project. + */ + private Function source; + + /** + * The run directory for this configuration, relative to the root project directory. + */ + private String runDir; + + /** + * The base name of the run configuration, which is the name it is created with, i.e. 'client' + */ + private final String baseName; + + private final Project project; + private final LoomGradleExtension extension; + + public RunConfigSettings(Project project, String baseName) { + this.baseName = baseName; + this.project = project; + this.extension = project.getExtensions().getByType(LoomGradleExtension.class); + + source("main"); + runDir("run"); + } + + public Project getProject() { + return project; + } + + public LoomGradleExtension getExtension() { + return extension; + } + + @Override + public String getName() { + return baseName; + } + + public List getVmArgs() { + return vmArgs; + } + + public List getProgramArgs() { + return programArgs; + } + + public String getEnvironment() { + return environment; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public String getConfigName() { + return name; + } + + public void setConfigName(String name) { + this.name = name; + } + + public String getDefaultMainClass() { + return defaultMainClass; + } + + public void setDefaultMainClass(String defaultMainClass) { + this.defaultMainClass = defaultMainClass; + } + + public String getRunDir() { + return runDir; + } + + public void setRunDir(String runDir) { + this.runDir = runDir; + } + + public SourceSet getSource(Project proj) { + return source.apply(proj); + } + + public void setSource(SourceSet source) { + this.source = proj -> source; + } + + public void setSource(Function sourceFn) { + this.source = sourceFn; + } + + public void environment(String environment) { + setEnvironment(environment); + } + + public void name(String name) { + setConfigName(name); + } + + public void defaultMainClass(String cls) { + setDefaultMainClass(cls); + } + + public void runDir(String dir) { + setRunDir(dir); + } + + public void vmArg(String arg) { + vmArgs.add(arg); + } + + public void vmArgs(String... args) { + vmArgs.addAll(Arrays.asList(args)); + } + + public void vmArgs(Collection args) { + vmArgs.addAll(args); + } + + public void property(String name, String value) { + vmArg("-D" + name + "=" + value); + } + + public void property(String name) { + vmArg("-D" + name); + } + + public void properties(Map props) { + props.forEach(this::property); + } + + public void programArg(String arg) { + programArgs.add(arg); + } + + public void programArgs(String... args) { + programArgs.addAll(Arrays.asList(args)); + } + + public void programArgs(Collection args) { + programArgs.addAll(args); + } + + public void source(SourceSet source) { + setSource(source); + } + + public void source(String source) { + setSource(proj -> { + JavaPluginConvention conv = proj.getConvention().getPlugin(JavaPluginConvention.class); + return conv.getSourceSets().getByName(source); + }); + } + + /** + * Add the {@code -XstartOnFirstThread} JVM argument when on OSX. + */ + public void startFirstThread() { + if (OperatingSystem.getOS().equalsIgnoreCase("osx")) { + vmArg("-XstartOnFirstThread"); + } + } + + /** + * Removes the {@code nogui} argument for the server configuration. By default {@code nogui} is specified, this is + * a convenient way to remove it if wanted. + */ + public void serverWithGui() { + programArgs.removeIf("nogui"::equals); + } + + /** + * Configure run config with the default client options. + */ + public void client() { + startFirstThread(); + environment("client"); + defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT); + } + + /** + * Configure run config with the default server options. + */ + public void server() { + programArg("nogui"); + environment("server"); + defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER); + } + + /** + * Configure run config with the default server options. + */ + public void data() { + environment("data"); + defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER); + } + + /** + * Copies settings from another run configuration. + */ + public void inherit(RunConfigSettings parent) { + vmArgs.addAll(0, parent.vmArgs); + programArgs.addAll(0, parent.programArgs); + + environment = parent.environment; + name = parent.name; + defaultMainClass = parent.defaultMainClass; + source = parent.source; + } + + public void makeRunDir() { + File file = new File(getProject().getRootDir(), runDir); + + if (!file.exists()) { + file.mkdir(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java index 5cf04f9b..c531cee8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java @@ -50,12 +50,6 @@ public class SetupIntelijRunConfigs { } catch (IOException e) { throw new RuntimeException("Failed to generate run configs", e); } - - File runDir = new File(project.getRootDir(), extension.runDir); - - if (!runDir.exists()) { - runDir.mkdirs(); - } } private static void generate(Project project) throws IOException { @@ -72,31 +66,23 @@ public class SetupIntelijRunConfigs { File projectDir = rootProject.file(".idea"); File runConfigsDir = new File(projectDir, "runConfigurations"); - File clientRunConfigs = new File(runConfigsDir, "Minecraft_Client" + projectPath + ".xml"); - File serverRunConfigs = new File(runConfigsDir, "Minecraft_Server" + projectPath + ".xml"); - File dataRunConfigs = new File(runConfigsDir, "Minecraft_Data" + projectPath + ".xml"); if (!runConfigsDir.exists()) { runConfigsDir.mkdirs(); } - String clientRunConfig = RunConfig.clientRunConfig(project).fromDummy("idea_run_config_template.xml"); - String serverRunConfig = RunConfig.serverRunConfig(project).fromDummy("idea_run_config_template.xml"); + for (RunConfigSettings settings : extension.getRuns()) { + RunConfig config = RunConfig.runConfig(project, settings); + String name = config.configName.replaceAll("[^a-zA-Z0-9$_]", "_"); - if (!clientRunConfigs.exists() || RunConfig.needsUpgrade(clientRunConfigs)) { - FileUtils.writeStringToFile(clientRunConfigs, clientRunConfig, StandardCharsets.UTF_8); - } + File runConfigs = new File(runConfigsDir, name + projectPath + ".xml"); + String runConfigXml = config.fromDummy("idea_run_config_template.xml"); - if (!serverRunConfigs.exists() || RunConfig.needsUpgrade(serverRunConfigs)) { - FileUtils.writeStringToFile(serverRunConfigs, serverRunConfig, StandardCharsets.UTF_8); - } - - if (extension.isDataGenEnabled()) { - String dataRunConfig = RunConfig.dataRunConfig(project).fromDummy("idea_run_config_template.xml"); - - if (!dataRunConfigs.exists() || RunConfig.needsUpgrade(dataRunConfigs)) { - FileUtils.writeStringToFile(dataRunConfigs, dataRunConfig, StandardCharsets.UTF_8); + if (!runConfigs.exists()) { + FileUtils.writeStringToFile(runConfigs, runConfigXml, StandardCharsets.UTF_8); } + + settings.makeRunDir(); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java index a36e7355..8d0a8d5e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java @@ -31,8 +31,6 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.zip.ZipError; import com.google.common.io.Files; @@ -178,13 +176,11 @@ public class MinecraftProvider extends DependencyProvider { } else { getProject().getLogger().debug("Downloading Minecraft {} manifest", minecraftVersion); - String url = optionalVersion.get().url; - // Find the sha1 of the json from the url, return true if it matches the local json - Pattern sha1Pattern = Pattern.compile("\\b[0-9a-f]{5,40}\\b"); - Matcher matcher = sha1Pattern.matcher(url); + ManifestVersion.Versions version = optionalVersion.get(); + String url = version.url; - if (matcher.find()) { - HashedDownloadUtil.downloadIfInvalid(new URL(url), minecraftJson, matcher.group(), getProject().getLogger(), true); + if (version.sha1 != null) { + HashedDownloadUtil.downloadIfInvalid(new URL(url), minecraftJson, version.sha1, getProject().getLogger(), true); } else { // Use the etag if no hash found from url DownloadUtil.downloadIfChanged(new URL(url), minecraftJson, getProject().getLogger()); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java index 4cc2e33e..8ab01007 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java @@ -31,6 +31,6 @@ public class ManifestVersion { public List versions = new ArrayList<>(); public static class Versions { - public String id, url; + public String id, url, sha1; } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java index ee4d9448..5709e38c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java @@ -29,30 +29,31 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.net.URL; -import java.util.Deque; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import au.com.bytecode.opencsv.CSVReader; -import au.com.bytecode.opencsv.CSVWriter; import com.google.common.base.Stopwatch; import com.google.common.hash.Hashing; import com.google.common.io.Files; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import me.tongfei.progressbar.DelegatingProgressBarConsumer; +import me.tongfei.progressbar.ProgressBar; +import me.tongfei.progressbar.ProgressBarBuilder; +import me.tongfei.progressbar.ProgressBarStyle; import org.gradle.api.GradleException; import org.gradle.api.Project; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.providers.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionInfo; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DownloadUtil; -import net.fabricmc.loom.util.gradle.ProgressLogger; public class MinecraftAssetsProvider { public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException { @@ -70,7 +71,7 @@ public class MinecraftAssetsProvider { } File assetsInfo = new File(assets, "indexes" + File.separator + assetIndex.getFabricId(minecraftProvider.getMinecraftVersion()) + ".json"); - File checksumInfo = new File(assets, "checksum" + File.separator + minecraftProvider.getMinecraftVersion() + ".csv"); + File checksumInfo = new File(assets, "checksum" + File.separator + minecraftProvider.getMinecraftVersion() + ".json"); if (!assetsInfo.exists() || !Checksum.equals(assetsInfo, assetIndex.sha1)) { project.getLogger().lifecycle(":downloading asset index"); @@ -88,137 +89,124 @@ public class MinecraftAssetsProvider { } } - Map checksumInfos = new ConcurrentHashMap<>(); + Gson gson = new Gson(); + Map checksumInfos = new HashMap<>(); if (checksumInfo.exists()) { - try (CSVReader reader = new CSVReader(new FileReader(checksumInfo))) { - String[] strings; - - while ((strings = reader.readNext()) != null) { - checksumInfos.put(strings[0], new AssetChecksumInfo(strings[1], Long.parseLong(strings[2]))); - } + try (FileReader reader = new FileReader(checksumInfo)) { + checksumInfos.putAll(gson.fromJson(reader, new TypeToken>() { + }.getType())); } } - project.getLogger().lifecycle(":downloading assets..."); - - Deque loggers = new ConcurrentLinkedDeque<>(); - ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1))); + ExecutorService executor = Executors.newFixedThreadPool(Math.min(16, Math.max(Runtime.getRuntime().availableProcessors() * 2, 1))); + int toDownload = 0; AssetIndex index; try (FileReader fileReader = new FileReader(assetsInfo)) { - index = new Gson().fromJson(fileReader, AssetIndex.class); + index = gson.fromJson(fileReader, AssetIndex.class); } Stopwatch stopwatch = Stopwatch.createStarted(); Map parent = index.getFileMap(); - parent.entrySet().parallelStream().forEach(entry -> { - AssetObject object = entry.getValue(); - String sha1 = object.getHash(); - String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1; - File file = new File(assets, filename); - long localFileLength = !file.exists() ? -1L : file.length(); - - AssetChecksumInfo localFileChecksum = localFileLength == -1L ? null : checksumInfos.computeIfAbsent(entry.getKey(), path -> { - try { - return new AssetChecksumInfo(Files.asByteSource(file).hash(Hashing.sha1()).toString(), localFileLength); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - }); - - if (localFileChecksum == null || localFileChecksum.length != localFileLength || !localFileChecksum.sha1.equals(sha1)) { - if (offline) { - if (file.exists()) { - project.getLogger().warn("Outdated asset " + entry.getKey()); - } else { - throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath()); - } - } else { - executor.execute(() -> { - ProgressLogger progressLogger; - - if (loggers.isEmpty()) { - //Create a new logger if we need one - progressLogger = ProgressLogger.getProgressFactory(project, MinecraftAssetsProvider.class.getName()); - progressLogger.start("Downloading assets...", "assets"); - } else { - // use a free logger if we can - progressLogger = loggers.pop(); - } - - String assetName = entry.getKey(); - int end = assetName.lastIndexOf("/") + 1; - - if (end > 0) { - assetName = assetName.substring(end); - } - - project.getLogger().debug(":downloading asset " + assetName); - progressLogger.progress(String.format("%-30.30s", assetName) + " - " + sha1); - - try { - DownloadUtil.downloadIfChanged(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file, project.getLogger(), true); - } catch (IOException e) { - throw new RuntimeException("Failed to download: " + assetName, e); - } - - try { - if (localFileChecksum == null) { - checksumInfos.put(entry.getKey(), new AssetChecksumInfo(Files.asByteSource(file).hash(Hashing.sha1()).toString(), file.length())); - } - } catch (IOException e) { - throw new RuntimeException("Failed to save checksum: " + assetName, e); - } - - //Give this logger back - loggers.add(progressLogger); - }); - } - } - }); - - project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index."); - - { - checksumInfo.getParentFile().mkdirs(); - - try (CSVWriter writer = new CSVWriter(new FileWriter(checksumInfo))) { - checksumInfos.forEach((path, info) -> { - writer.writeNext(new String[] { - path, - info.sha1, - String.valueOf(info.length) - }); - }); - } - } - - //Wait for the assets to all download - executor.shutdown(); + ProgressBar[] progressBar = {null}; try { - if (executor.awaitTermination(2, TimeUnit.HOURS)) { - executor.shutdownNow(); + for (Map.Entry entry : parent.entrySet()) { + AssetObject object = entry.getValue(); + String sha1 = object.getHash(); + String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1; + File file = new File(assets, filename); + + String localFileChecksum = !file.exists() ? null : checksumInfos.computeIfAbsent(entry.getKey(), path -> { + try { + return Files.asByteSource(file).hash(Hashing.sha1()).toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + }); + + if (LoomGradlePlugin.refreshDeps || localFileChecksum == null || !localFileChecksum.equals(sha1)) { + if (offline) { + if (file.exists()) { + project.getLogger().warn("Outdated asset " + entry.getKey()); + } else { + throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath()); + } + } else { + toDownload++; + + if (progressBar[0] == null) { + progressBar[0] = new ProgressBarBuilder() + .setConsumer(new DelegatingProgressBarConsumer(project.getLogger()::lifecycle)) + .setInitialMax(toDownload) + .setUpdateIntervalMillis(2000) + .setTaskName(":downloading assets") + .setStyle(ProgressBarStyle.ASCII) + .showSpeed() + .build(); + } + + progressBar[0].maxHint(toDownload); + + executor.execute(() -> { + String assetName = entry.getKey(); + int end = assetName.lastIndexOf("/") + 1; + + if (end > 0) { + assetName = assetName.substring(end); + } + + project.getLogger().debug(":downloading asset " + assetName); + + try { + DownloadUtil.downloadIfChanged(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file, project.getLogger(), true); + } catch (IOException e) { + throw new RuntimeException("Failed to download: " + assetName, e); + } + + if (localFileChecksum == null) { + checksumInfos.put(entry.getKey(), sha1); + } + + synchronized (progressBar[0]) { + progressBar[0].step(); + } + }); + } + } } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - loggers.forEach(ProgressLogger::completed); - } + project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index."); - private static class AssetChecksumInfo { - public final String sha1; - public long length; + if (toDownload > 0) { + project.getLogger().lifecycle(":downloading " + toDownload + " asset" + (toDownload == 1 ? "" : "s") + "..."); + } - AssetChecksumInfo(String sha1, long length) { - this.sha1 = sha1; - this.length = length; + checksumInfo.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter(checksumInfo)) { + gson.toJson(checksumInfos, writer); + } + + //Wait for the assets to all download + executor.shutdown(); + + try { + if (executor.awaitTermination(2, TimeUnit.HOURS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } finally { + if (progressBar[0] != null) { + progressBar[0].close(); + } } } } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 551bd58a..61c27abb 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.function.Function; import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.JavaExec; import net.fabricmc.loom.LoomGradleExtension; @@ -45,6 +46,9 @@ public abstract class AbstractRunTask extends JavaExec { super(); setGroup("fabric"); this.configProvider = config; + + setClasspath(getProject().getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + classpath(this.getProject().getExtensions().getByType(LoomGradleExtension.class).getUnmappedModCollection()); } @Override @@ -53,9 +57,6 @@ public abstract class AbstractRunTask extends JavaExec { config = configProvider.apply(getProject()); } - classpath(getProject().getConfigurations().getByName("runtimeClasspath")); - classpath(this.getProject().getExtensions().getByType(LoomGradleExtension.class).getUnmappedModCollection()); - List argsSplit = new ArrayList<>(); String[] args = config.programArgs.split(" "); int partPos = -1; @@ -85,8 +86,7 @@ public abstract class AbstractRunTask extends JavaExec { } args(argsSplit); - LoomGradleExtension extension = this.getProject().getExtensions().getByType(LoomGradleExtension.class); - setWorkingDir(new File(getProject().getRootDir(), extension.runDir)); + setWorkingDir(new File(getProject().getRootDir(), config.runDir)); environment(config.envVariables); super.exec(); diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java index 0896c4bf..1f93ef0b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java @@ -32,39 +32,29 @@ import org.apache.commons.io.FileUtils; import org.gradle.api.tasks.TaskAction; import org.gradle.plugins.ide.eclipse.model.EclipseModel; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; public class GenEclipseRunsTask extends AbstractLoomTask { @TaskAction public void genRuns() throws IOException { EclipseModel eclipseModel = getProject().getExtensions().getByType(EclipseModel.class); - File clientRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_client.launch"); - File serverRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_server.launch"); + LoomGradleExtension extension = getExtension(); File dataRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_data.launch"); - String clientRunConfig = RunConfig.clientRunConfig(getProject()).fromDummy("eclipse_run_config_template.xml"); - String serverRunConfig = RunConfig.serverRunConfig(getProject()).fromDummy("eclipse_run_config_template.xml"); + for (RunConfigSettings settings : extension.getRuns()) { + String name = settings.getName(); - if (!clientRunConfigs.exists() || RunConfig.needsUpgrade(clientRunConfigs)) { - FileUtils.writeStringToFile(clientRunConfigs, clientRunConfig, StandardCharsets.UTF_8); - } + File configs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_" + name + ".launch"); + RunConfig configInst = RunConfig.runConfig(getProject(), settings); + String config = configInst.fromDummy("eclipse_run_config_template.xml"); - if (!serverRunConfigs.exists() || RunConfig.needsUpgrade(serverRunConfigs)) { - FileUtils.writeStringToFile(serverRunConfigs, serverRunConfig, StandardCharsets.UTF_8); - } - - if (getExtension().isDataGenEnabled()) { - String dataRunConfig = RunConfig.dataRunConfig(getProject()).fromDummy("eclipse_run_config_template.xml"); - - if (!dataRunConfigs.exists() || RunConfig.needsUpgrade(dataRunConfigs)) { - FileUtils.writeStringToFile(dataRunConfigs, dataRunConfig, StandardCharsets.UTF_8); + if (!configs.exists()) { + FileUtils.writeStringToFile(configs, config, StandardCharsets.UTF_8); } - } - File runDir = new File(getProject().getRootDir(), getExtension().runDir); - - if (!runDir.exists()) { - runDir.mkdirs(); + settings.makeRunDir(); } } } diff --git a/src/main/java/net/fabricmc/loom/task/GenIdeaProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenIdeaProjectTask.java index cd5fbe52..8ad3586b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenIdeaProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenIdeaProjectTask.java @@ -46,6 +46,7 @@ import org.xml.sax.SAXException; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; public class GenIdeaProjectTask extends AbstractLoomTask { @TaskAction @@ -82,11 +83,9 @@ public class GenIdeaProjectTask extends AbstractLoomTask { throw new RuntimeException("Failed to generate IntelliJ run configurations (runManager was not found)"); } - runManager.appendChild(RunConfig.clientRunConfig(project).genRuns(runManager)); - runManager.appendChild(RunConfig.serverRunConfig(project).genRuns(runManager)); - - if (extension.isDataGenEnabled()) { - runManager.appendChild(RunConfig.dataRunConfig(project).genRuns(runManager)); + for (RunConfigSettings settings : getExtension().getRuns()) { + runManager.appendChild(RunConfig.runConfig(project, settings).genRuns(runManager)); + settings.makeRunDir(); } TransformerFactory transformerFactory = TransformerFactory.newInstance(); @@ -96,11 +95,5 @@ public class GenIdeaProjectTask extends AbstractLoomTask { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(source, result); - - File runDir = new File(getProject().getRootDir(), extension.runDir); - - if (!runDir.exists()) { - runDir.mkdirs(); - } } } diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index d1fe52b9..e6ea643f 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -43,6 +43,7 @@ import org.gradle.api.tasks.TaskAction; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; // Recommended vscode plugins: // https://marketplace.visualstudio.com/items?itemName=redhat.java @@ -94,11 +95,9 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { launch = new VsCodeLaunch(); } - launch.add(RunConfig.clientRunConfig(project)); - launch.add(RunConfig.serverRunConfig(project)); - - if (extension.isDataGenEnabled()) { - launch.add(RunConfig.dataRunConfig(project)); + for (RunConfigSettings settings : extension.getRuns()) { + launch.add(RunConfig.runConfig(project, settings)); + settings.makeRunDir(); } String json = gson.toJson(launch); @@ -109,12 +108,6 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { throw new RuntimeException("Failed to write launch.json", e); } - File runDir = new File(project.getRootDir(), extension.runDir); - - if (!runDir.exists()) { - runDir.mkdirs(); - } - VsCodeTasks tasks; if (tasksJson.exists()) { @@ -181,7 +174,7 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { public String type = "java"; public String name; public String request = "launch"; - public String cwd = "${workspaceFolder}/run"; + public String cwd; public String console = "internalConsole"; public boolean stopOnEntry = false; public String mainClass; @@ -197,6 +190,7 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { this.mainClass = runConfig.mainClass; this.vmArgs = runConfig.vmArgs; this.args = runConfig.programArgs; + this.cwd = "${workspaceFolder}/" + runConfig.runDir; this.projectName = runConfig.vscodeProjectName; this.env.putAll(runConfig.envVariables); this.tasksBeforeRun.addAll(runConfig.vscodeBeforeRun); diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index e17ad8e6..a3aca532 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -24,11 +24,13 @@ package net.fabricmc.loom.task; +import com.google.common.base.Preconditions; import org.gradle.api.Project; import org.gradle.api.tasks.TaskContainer; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler; public final class LoomTasks { @@ -40,7 +42,7 @@ public final class LoomTasks { tasks.register("migrateMappings", MigrateMappingsTask.class, t -> { t.setDescription("Migrates mappings to a new version."); - t.getOutputs().upToDateWhen((o) -> false); + t.getOutputs().upToDateWhen(o -> false); }); tasks.register("remapJar", RemapJarTask.class, t -> { @@ -84,33 +86,32 @@ public final class LoomTasks { private static void registerRunTasks(TaskContainer tasks, Project project) { LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - tasks.register("runClient", RunClientTask.class, t -> { - t.setDescription("Starts a development version of the Minecraft client."); - t.dependsOn("downloadAssets"); - t.setGroup("fabric"); + Preconditions.checkArgument(extension.getRuns().size() == 0, "Run configurations must not be registered before loom"); + + extension.getRuns().whenObjectAdded(config -> { + String configName = config.getName(); + String taskName = "run" + configName.substring(0, 1).toUpperCase() + configName.substring(1); + + tasks.register(taskName, RunGameTask.class, config).configure(t -> { + t.setDescription("Starts the '" + config.getConfigName() + "' run configuration"); + t.setGroup("fabric"); + + if (config.getEnvironment().equals("client")) { + t.dependsOn("downloadAssets"); + } + }); }); - tasks.register("runServer", RunServerTask.class, t -> { - t.setDescription("Starts a development version of the Minecraft server."); - t.setGroup("fabric"); - }); - - project.afterEvaluate(p -> { - if (extension.isDataGenEnabled()) { - tasks.register("runData", RunDataTask.class, t -> { - t.setDescription("Starts a development version of the Minecraft data."); - t.setGroup("fabric"); - }); - } - }); + extension.getRuns().create("client", RunConfigSettings::client); + extension.getRuns().create("server", RunConfigSettings::server); } private static void registerDecompileTasks(TaskContainer tasks, Project project) { LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - project.afterEvaluate((p) -> { + project.afterEvaluate(p -> { for (LoomDecompiler decompiler : extension.getDecompilers()) { - String taskName = (decompiler instanceof FabricFernFlowerDecompiler) ? "genSources" : "genSourcesWith" + decompiler.name(); + String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name(); // decompiler will be passed to the constructor of GenerateSourcesTask tasks.register(taskName, GenerateSourcesTask.class, decompiler); } diff --git a/src/main/java/net/fabricmc/loom/task/RunClientTask.java b/src/main/java/net/fabricmc/loom/task/RunClientTask.java index de0cc377..0bfaca0c 100644 --- a/src/main/java/net/fabricmc/loom/task/RunClientTask.java +++ b/src/main/java/net/fabricmc/loom/task/RunClientTask.java @@ -24,10 +24,15 @@ package net.fabricmc.loom.task; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +@Deprecated // Replaced by RunGameTask public class RunClientTask extends AbstractRunTask { public RunClientTask() { - super(RunConfig::clientRunConfig); + super(project -> { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + return RunConfig.runConfig(project, extension.getRuns().getByName("client")); + }); } } diff --git a/src/main/java/net/fabricmc/loom/task/RunDataTask.java b/src/main/java/net/fabricmc/loom/task/RunDataTask.java index 01f62884..fbb80951 100644 --- a/src/main/java/net/fabricmc/loom/task/RunDataTask.java +++ b/src/main/java/net/fabricmc/loom/task/RunDataTask.java @@ -24,10 +24,15 @@ package net.fabricmc.loom.task; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +@Deprecated // Replaced by RunGameTask public class RunDataTask extends AbstractRunTask { public RunDataTask() { - super(RunConfig::dataRunConfig); + super(project -> { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + return RunConfig.runConfig(project, extension.getRuns().getByName("data")); + }); } } diff --git a/src/main/java/net/fabricmc/loom/task/RunGameTask.java b/src/main/java/net/fabricmc/loom/task/RunGameTask.java new file mode 100644 index 00000000..f82352a0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RunGameTask.java @@ -0,0 +1,37 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.task; + +import javax.inject.Inject; + +import net.fabricmc.loom.configuration.ide.RunConfig; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; + +public class RunGameTask extends AbstractRunTask { + @Inject + public RunGameTask(RunConfigSettings settings) { + super(proj -> RunConfig.runConfig(proj, settings)); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/RunServerTask.java b/src/main/java/net/fabricmc/loom/task/RunServerTask.java index 977b65c7..aa9fc94a 100644 --- a/src/main/java/net/fabricmc/loom/task/RunServerTask.java +++ b/src/main/java/net/fabricmc/loom/task/RunServerTask.java @@ -24,10 +24,15 @@ package net.fabricmc.loom.task; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; +@Deprecated // Replaced by RunGameTask public class RunServerTask extends AbstractRunTask { public RunServerTask() { - super(RunConfig::serverRunConfig); + super(project -> { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + return RunConfig.runConfig(project, extension.getRuns().getByName("client")); + }); } } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index eb37930e..0ff48728 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -136,4 +136,19 @@ public class Constants { private LaunchWrapper() { } } + + public static final class Knot { + public static final String KNOT_CLIENT = "net.fabricmc.loader.launch.knot.KnotClient"; + public static final String KNOT_SERVER = "net.fabricmc.loader.launch.knot.KnotServer"; + + private Knot() { + } + } + + public static final class ForgeUserDev { + public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting"; + + private ForgeUserDev() { + } + } } diff --git a/src/main/resources/eclipse_run_config_template.xml b/src/main/resources/eclipse_run_config_template.xml index c8697299..cff2835c 100644 --- a/src/main/resources/eclipse_run_config_template.xml +++ b/src/main/resources/eclipse_run_config_template.xml @@ -12,5 +12,5 @@ - + diff --git a/src/main/resources/idea_run_config_template.xml b/src/main/resources/idea_run_config_template.xml index f462051d..6b71bd71 100644 --- a/src/main/resources/idea_run_config_template.xml +++ b/src/main/resources/idea_run_config_template.xml @@ -5,7 +5,7 @@