diff --git a/src/main/java/dev/architectury/loom/forge/tool/ForgeToolExecutor.java b/src/main/java/dev/architectury/loom/forge/tool/ForgeToolExecutor.java new file mode 100644 index 000000000..ce3a58c0a --- /dev/null +++ b/src/main/java/dev/architectury/loom/forge/tool/ForgeToolExecutor.java @@ -0,0 +1,141 @@ +package dev.architectury.loom.forge.tool; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.configuration.ShowStacktrace; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; +import org.jetbrains.annotations.Nullable; + +/** + * Contains helpers for executing Forge's command line tools + * with suppressed output streams to prevent annoying log spam. + */ +public abstract class ForgeToolExecutor { + @Inject + protected abstract ExecOperations getExecOperations(); + + public static boolean shouldShowVerboseStdout(Project project) { + // if running with INFO or DEBUG logging + return project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0; + } + + public static boolean shouldShowVerboseStderr(Project project) { + // if stdout is shown or stacktraces are visible so that errors printed to stderr show up + return shouldShowVerboseStdout(project) || project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS; + } + + public static Settings getDefaultSettings(Project project) { + final Settings settings = project.getObjects().newInstance(Settings.class); + settings.getExecutable().set(JavaExecutableFetcher.getJavaToolchainExecutable(project)); + settings.getShowVerboseStdout().set(shouldShowVerboseStdout(project)); + settings.getShowVerboseStderr().set(shouldShowVerboseStderr(project)); + return settings; + } + + /** + * Executes an external Java process. + * + *

This method cannot be used during configuration. + * Use {@link ForgeToolValueSource#exec(Project, Action)} for those cases. + * + * @param project the project + * @param configurator the {@code javaexec} configuration action + * @return the execution result + */ + public static ExecResult exec(Project project, Action configurator) { + final Settings settings = getDefaultSettings(project); + configurator.execute(settings); + return project.getObjects().newInstance(ForgeToolExecutor.class).exec(settings); + } + + private ExecResult exec(Settings settings) { + return exec(getExecOperations(), settings); + } + + public static ExecResult exec(ExecOperations execOperations, Settings settings) { + return execOperations.javaexec(spec -> { + final @Nullable String executable = settings.getExecutable().getOrNull(); + if (executable != null) spec.setExecutable(executable); + spec.getMainClass().set(settings.getMainClass()); + spec.setArgs(settings.getProgramArgs().get()); + spec.setJvmArgs(settings.getJvmArgs().get()); + spec.setClasspath(settings.getExecClasspath()); + + if (settings.getShowVerboseStdout().get()) { + spec.setStandardOutput(System.out); + } else { + spec.setStandardOutput(NullOutputStream.INSTANCE); + } + + if (settings.getShowVerboseStderr().get()) { + spec.setErrorOutput(System.err); + } else { + spec.setErrorOutput(NullOutputStream.INSTANCE); + } + }); + } + + public interface Settings { + @Input + Property getExecutable(); + + @Input + ListProperty getProgramArgs(); + + @Input + ListProperty getJvmArgs(); + + @Input + Property getMainClass(); + + @Classpath + ConfigurableFileCollection getExecClasspath(); + + @Input + Property getShowVerboseStdout(); + + @Input + Property getShowVerboseStderr(); + + default void classpath(Object... paths) { + getExecClasspath().from(paths); + } + + default void setClasspath(Object... paths) { + getExecClasspath().setFrom(paths); + } + + default void args(String... args) { + getProgramArgs().addAll(args); + } + + default void args(Collection args) { + getProgramArgs().addAll(args); + } + + default void setArgs(List args) { + getProgramArgs().set(args); + } + + default void jvmArgs(String... args) { + getJvmArgs().addAll(args); + } + + default void jvmArgs(Collection args) { + getJvmArgs().addAll(args); + } + } +} diff --git a/src/main/java/dev/architectury/loom/forge/tool/ForgeToolValueSource.java b/src/main/java/dev/architectury/loom/forge/tool/ForgeToolValueSource.java new file mode 100644 index 000000000..15c715f05 --- /dev/null +++ b/src/main/java/dev/architectury/loom/forge/tool/ForgeToolValueSource.java @@ -0,0 +1,46 @@ +package dev.architectury.loom.forge.tool; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ValueSource; +import org.gradle.api.provider.ValueSourceParameters; +import org.gradle.api.tasks.Nested; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; + +public abstract class ForgeToolValueSource implements ValueSource { + @Inject + protected abstract ExecOperations getExecOperations(); + + public static Provider create(Project project, Action configurator) { + return project.getProviders().of(ForgeToolValueSource.class, spec -> { + ForgeToolExecutor.Settings settings = ForgeToolExecutor.getDefaultSettings(project); + configurator.execute(settings); + spec.getParameters().getSettings().set(settings); + }); + } + + /** + * Executes an external Java process during project configuration. + * + * @param project the project + * @param configurator an action that configures the exec settings + */ + public static void exec(Project project, Action configurator) { + create(project, configurator).get().rethrowFailure().assertNormalExitValue(); + } + + @Override + public ExecResult obtain() { + return ForgeToolExecutor.exec(getExecOperations(), getParameters().getSettings().get()); + } + + public interface Parameters extends ValueSourceParameters { + @Nested + Property getSettings(); + } +} diff --git a/src/main/java/dev/architectury/loom/forge/ForgeTools.java b/src/main/java/dev/architectury/loom/forge/tool/ForgeTools.java similarity index 74% rename from src/main/java/dev/architectury/loom/forge/ForgeTools.java rename to src/main/java/dev/architectury/loom/forge/tool/ForgeTools.java index 5a9430295..07b1a4d0c 100644 --- a/src/main/java/dev/architectury/loom/forge/ForgeTools.java +++ b/src/main/java/dev/architectury/loom/forge/tool/ForgeTools.java @@ -1,4 +1,4 @@ -package dev.architectury.loom.forge; +package dev.architectury.loom.forge.tool; public final class ForgeTools { public static final String SIDE_STRIPPER = "net.minecraftforge:mergetool:1.1.6:fatjar"; diff --git a/src/main/java/dev/architectury/loom/forge/tool/JavaExecutableFetcher.java b/src/main/java/dev/architectury/loom/forge/tool/JavaExecutableFetcher.java new file mode 100644 index 000000000..1c7d5a723 --- /dev/null +++ b/src/main/java/dev/architectury/loom/forge/tool/JavaExecutableFetcher.java @@ -0,0 +1,31 @@ +package dev.architectury.loom.forge.tool; + +import javax.inject.Inject; + +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.jvm.toolchain.JavaToolchainSpec; + +public abstract class JavaExecutableFetcher { + @Inject + protected abstract JavaToolchainService getToolchainService(); + + public static Provider getJavaToolchainExecutable(Project project) { + return project.provider(() -> { + final JavaExecutableFetcher fetcher = project.getObjects().newInstance(JavaExecutableFetcher.class); + final JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); + final JavaToolchainSpec toolchain = java.getToolchain(); + + if (!toolchain.getLanguageVersion().isPresent()) { + // Toolchain not configured, we'll use the runtime Java version. + return null; + } + + final JavaLauncher launcher = fetcher.getToolchainService().launcherFor(toolchain).get(); + return launcher.getExecutablePath().getAsFile().getAbsolutePath(); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java index 1f8f99929..223210340 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java @@ -42,6 +42,7 @@ import com.google.common.io.MoreFiles; import dev.architectury.at.AccessTransformSet; import dev.architectury.at.io.AccessTransformFormats; +import dev.architectury.loom.forge.tool.ForgeToolValueSource; import dev.architectury.loom.util.TempFiles; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; @@ -59,7 +60,6 @@ import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.ExceptionUtil; -import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.LoomVersions; import net.fabricmc.loom.util.fmj.FabricModJson; @@ -174,11 +174,11 @@ public static void executeAt(Project project, Path input, Path output, AccessTra configuration.apply(args); - ForgeToolExecutor.exec(project, spec -> { + ForgeToolValueSource.exec(project, spec -> { spec.getMainClass().set(mainClass); spec.setArgs(args); spec.setClasspath(classpath); - }).rethrowFailure().assertNormalExitValue(); + }); } private static LoomVersions chooseAccessTransformer(Project project) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index b4c347683..db2ea3abd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -53,6 +53,7 @@ import com.google.common.base.Stopwatch; import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer; import dev.architectury.loom.forge.UserdevConfig; +import dev.architectury.loom.forge.tool.ForgeToolValueSource; import dev.architectury.loom.util.MappingOption; import dev.architectury.loom.util.TempFiles; import org.gradle.api.Project; @@ -78,7 +79,6 @@ import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.MappingsProviderVerbose; import net.fabricmc.loom.util.ThreadingUtils; import net.fabricmc.loom.util.TinyRemapperHelper; @@ -459,7 +459,7 @@ private void patchJars() throws Exception { } private void patchJars(Path clean, Path output, Path patches) { - ForgeToolExecutor.exec(project, spec -> { + ForgeToolValueSource.exec(project, spec -> { UserdevConfig.BinaryPatcherConfig config = getExtension().getForgeUserdevProvider().getConfig().binpatcher(); final FileCollection download = DependencyDownloader.download(project, config.dependency()); spec.classpath(download); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java index ca50f3a83..6e0ffa4d6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java @@ -44,13 +44,14 @@ import com.google.common.hash.Hashing; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import dev.architectury.loom.forge.tool.ForgeToolExecutor; +import dev.architectury.loom.forge.tool.ForgeToolValueSource; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.Logger; -import org.gradle.process.JavaExecSpec; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; @@ -67,7 +68,6 @@ import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StripLogic; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.download.DownloadBuilder; import net.fabricmc.loom.util.function.CollectionUtil; import net.fabricmc.loom.util.gradle.GradleUtils; @@ -368,8 +368,8 @@ private static void redirectAwareDownload(String urlString, Path path) throws IO } @Override - public void javaexec(Action configurator) { - ForgeToolExecutor.exec(project, configurator).rethrowFailure().assertNormalExitValue(); + public void javaexec(Action configurator) { + ForgeToolValueSource.exec(project, configurator); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/steplogic/StepLogic.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/steplogic/StepLogic.java index 590b81cc0..0b7908ca2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/steplogic/StepLogic.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/steplogic/StepLogic.java @@ -31,9 +31,9 @@ import java.util.Optional; import java.util.Set; +import dev.architectury.loom.forge.tool.ForgeToolExecutor; import org.gradle.api.Action; import org.gradle.api.logging.Logger; -import org.gradle.process.JavaExecSpec; import net.fabricmc.loom.configuration.providers.forge.ConfigValue; import net.fabricmc.loom.util.download.DownloadBuilder; @@ -64,7 +64,7 @@ interface ExecutionContext { Path downloadFile(String url) throws IOException; Path downloadDependency(String notation); DownloadBuilder downloadBuilder(String url); - void javaexec(Action configurator); + void javaexec(Action configurator); Set getMinecraftLibraries(); default List resolve(List configValues) { diff --git a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java index bae894730..7d53c02e8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java @@ -41,6 +41,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import dev.architectury.loom.forge.tool.ForgeToolExecutor; import dev.architectury.loom.util.MappingOption; import org.apache.commons.io.output.NullOutputStream; import org.cadixdev.lorenz.MappingSet; @@ -57,7 +58,6 @@ import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.LoomVersions; import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.ThreadingUtils; diff --git a/src/main/java/net/fabricmc/loom/task/GenerateForgePatchedSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateForgePatchedSourcesTask.java index 94de74c0a..38c9fbad0 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateForgePatchedSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateForgePatchedSourcesTask.java @@ -37,7 +37,8 @@ import codechicken.diffpatch.util.LoggingOutputStream; import codechicken.diffpatch.util.PatchMode; import com.google.common.base.Stopwatch; -import dev.architectury.loom.forge.ForgeTools; +import dev.architectury.loom.forge.tool.ForgeToolValueSource; +import dev.architectury.loom.forge.tool.ForgeTools; import dev.architectury.loom.util.TempFiles; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; @@ -55,7 +56,6 @@ import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.service.ScopedServiceFactory; import net.fabricmc.loom.util.service.ServiceFactory; @@ -191,7 +191,7 @@ private void stripSideAnnotations(Path input, Path output) throws IOException { final FileCollection classpath = DependencyDownloader.download(getProject(), ForgeTools.SIDE_STRIPPER, false, true); - ForgeToolExecutor.exec(getProject(), spec -> { + ForgeToolValueSource.exec(getProject(), spec -> { spec.setClasspath(classpath); spec.args( "--strip", diff --git a/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java b/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java deleted file mode 100644 index 3e7ad6ea0..000000000 --- a/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2022-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.util; - -import javax.inject.Inject; - -import org.apache.commons.io.output.NullOutputStream; -import org.gradle.api.Action; -import org.gradle.api.Project; -import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.configuration.ShowStacktrace; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.jvm.toolchain.JavaLauncher; -import org.gradle.jvm.toolchain.JavaToolchainService; -import org.gradle.jvm.toolchain.JavaToolchainSpec; -import org.gradle.process.ExecOperations; -import org.gradle.process.ExecResult; -import org.gradle.process.JavaExecSpec; -import org.jetbrains.annotations.Nullable; - -/** - * Contains helpers for executing Forge's command line tools - * with suppressed output streams to prevent annoying log spam. - */ -public abstract class ForgeToolExecutor { - @Inject - protected abstract JavaToolchainService getToolchainService(); - - @Inject - protected abstract ExecOperations getExecOperations(); - - @Inject - protected abstract Project getProject(); - - public static boolean shouldShowVerboseStdout(Project project) { - // if running with INFO or DEBUG logging - return project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0; - } - - public static boolean shouldShowVerboseStderr(Project project) { - // if stdout is shown or stacktraces are visible so that errors printed to stderr show up - return shouldShowVerboseStdout(project) || project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS; - } - - /** - * Executes a {@link ExecOperations#javaexec(Action) javaexec} action with suppressed output. - * - * @param project the project - * @param configurator the {@code javaexec} configuration action - * @return the execution result - */ - public static ExecResult exec(Project project, Action configurator) { - return project.getObjects().newInstance(ForgeToolExecutor.class) - .exec(configurator); - } - - private ExecResult exec(Action configurator) { - final Project project = getProject(); - return getExecOperations().javaexec(spec -> { - configurator.execute(spec); - - if (shouldShowVerboseStdout(project)) { - spec.setStandardOutput(System.out); - } else { - spec.setStandardOutput(NullOutputStream.INSTANCE); - } - - if (shouldShowVerboseStderr(project)) { - spec.setErrorOutput(System.err); - } else { - spec.setErrorOutput(NullOutputStream.INSTANCE); - } - - // Use project toolchain for executing if possible. - // Note: This feature cannot be tested using the test kit since - // - Gradle disables native services in test kit environments. - // - The only resolver plugin I could find, foojay-resolver, - // requires the services for finding the OS architecture. - final @Nullable String executable = findJavaToolchainExecutable(project); - - if (executable != null) { - spec.setExecutable(executable); - } - }); - } - - private @Nullable String findJavaToolchainExecutable(Project project) { - final JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); - final JavaToolchainSpec toolchain = java.getToolchain(); - - if (!toolchain.getLanguageVersion().isPresent()) { - // Toolchain not configured, we'll use the runtime Java version. - return null; - } - - final JavaLauncher launcher = getToolchainService().launcherFor(toolchain).get(); - return launcher.getExecutablePath().getAsFile().getAbsolutePath(); - } -}