From 8014d2c18b7f1ca2cda140fd617c6f4bd5db013d Mon Sep 17 00:00:00 2001 From: modmuss Date: Sat, 26 Apr 2025 15:37:46 +0100 Subject: [PATCH] RenderDoc tasks (#1291) * First pass on renderdoc support * Fixes and improvements * Fix debugging/cleaner code. * Download from fabric maven * Fix build * Revert changes to AbstractRunTask --- .github/workflows/publish-exp.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test-push.yml | 4 +- gradle/runtime.libs.versions.toml | 8 +- .../net/fabricmc/loom/task/LoomTasks.java | 73 ++++++++++++++- .../fabricmc/loom/task/RenderDocRunTask.java | 92 +++++++++++++++++++ .../loom/task/RenderDocRunUITask.java | 51 ++++++++++ .../java/net/fabricmc/loom/util/Platform.java | 4 + 8 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java diff --git a/.github/workflows/publish-exp.yml b/.github/workflows/publish-exp.yml index 4a523661..b1cb9942 100644 --- a/.github/workflows/publish-exp.yml +++ b/.github/workflows/publish-exp.yml @@ -17,7 +17,7 @@ jobs: java-version: 21 distribution: 'temurin' - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/wrapper-validation@v4 # Generate the build number based on tags to allow per branch build numbers, not something github provides by default. - name: Generate build number diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1c22e7d0..3b87825c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: java-version: 21 distribution: 'temurin' - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/wrapper-validation@v4 # Generate the build number based on tags to allow per branch build numbers, not something github provides by default. - name: Generate build number diff --git a/.github/workflows/test-push.yml b/.github/workflows/test-push.yml index 636bfd7e..decbdd08 100644 --- a/.github/workflows/test-push.yml +++ b/.github/workflows/test-push.yml @@ -27,7 +27,7 @@ jobs: java-version: 21 distribution: 'temurin' - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/wrapper-validation@v4 - run: ./gradlew build check -x test --stacktrace --warning-mode fail build_windows: @@ -41,7 +41,7 @@ jobs: with: java-version: 21 distribution: 'temurin' - - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/actions/wrapper-validation@v4 - run: ./gradlew build check -x test --stacktrace --warning-mode fail # This job is used to feed the test matrix of next job to allow the tests to run in parallel diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index ed108f5b..f39fe4b0 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -12,6 +12,9 @@ jetbrains-annotations = "26.0.2" native-support = "1.0.1" fabric-installer = "1.0.3" +# Debug tools +renderdoc = "1.37" + [libraries] # Decompilers fernflower = { module = "net.fabricmc:fabric-fernflower", version.ref = "fernflower" } @@ -24,4 +27,7 @@ dev-launch-injector = { module = "net.fabricmc:dev-launch-injector", version.ref terminal-console-appender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminal-console-appender" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" } -fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" } \ No newline at end of file +fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" } + +# Debug tools +renderdoc = { module = "org.renderdoc:renderdoc", version.ref = "renderdoc" } # Not a maven dependency \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 97d4df71..8fd96545 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -24,13 +24,18 @@ package net.fabricmc.loom.task; +import java.io.File; + import javax.inject.Inject; import com.google.common.base.Preconditions; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Sync; import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskOutputs; import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.LoomGradleExtension; @@ -41,6 +46,8 @@ import net.fabricmc.loom.task.launch.GenerateDLIConfigTask; import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask; import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.LoomVersions; +import net.fabricmc.loom.util.Platform; import net.fabricmc.loom.util.gradle.GradleUtils; public abstract class LoomTasks implements Runnable { @@ -132,17 +139,28 @@ public abstract class LoomTasks implements Runnable { private void registerRunTasks() { LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final boolean renderDocSupported = RenderDocRunTask.isSupported(Platform.CURRENT); Preconditions.checkArgument(extension.getRunConfigs().size() == 0, "Run configurations must not be registered before loom"); extension.getRunConfigs().whenObjectAdded(config -> { - getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config).configure(t -> { + var runTask = getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config); + + runTask.configure(t -> { t.setDescription("Starts the '" + config.getConfigName() + "' run configuration"); t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch"); }); + + if (config.getName().equals("client") && renderDocSupported) { + getTasks().register("runClientRenderDoc", RenderDocRunTask.class, config); + } }); + if (renderDocSupported) { + configureRenderDocTasks(); + } + extension.getRunConfigs().whenObjectRemoved(runConfigSettings -> { getTasks().named(getRunConfigTaskName(runConfigSettings), task -> { // Disable the task so it can't be run @@ -170,7 +188,58 @@ public abstract class LoomTasks implements Runnable { return; } - extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName)); + extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName) + || settings.getName().equals(taskName + "RenderDoc")); + }); + } + + private void configureRenderDocTasks() { + final Platform.OperatingSystem operatingSystem = Platform.CURRENT.getOperatingSystem(); + final String renderDocVersion = LoomVersions.RENDERDOC.version(); + final String renderDocBaseName = operatingSystem.isWindows() + ? "RenderDoc_%s_64".formatted(renderDocVersion) + : "renderdoc_%s".formatted(renderDocVersion); + final String renderDocFilename = operatingSystem.isWindows() + ? "%s.zip".formatted(renderDocBaseName) + : "%s.tar.gz".formatted(renderDocBaseName); + final String renderDocUrl = "https://maven.fabricmc.net/org/renderdoc/%s".formatted(renderDocFilename); + final String executableExt = operatingSystem.isWindows() ? ".exe" : ""; + + var downloadRenderDoc = getTasks().register("downloadRenderDoc", DownloadTask.class, task -> { + task.setGroup(Constants.TaskGroup.FABRIC); + + task.getUrl().set(renderDocUrl); + task.getOutput().set(getProject().getLayout().getBuildDirectory().file(renderDocFilename)); + }); + + var extractRenderDoc = getTasks().register("extractRenderDoc", Sync.class, task -> { + task.setGroup(Constants.TaskGroup.FABRIC); + + if (operatingSystem.isWindows()) { + task.from(getProject().zipTree(downloadRenderDoc.map(DownloadTask::getOutput))); + } else { + task.from(getProject().tarTree(downloadRenderDoc.map(DownloadTask::getOutput))); + } + + task.into(getProject().getLayout().getBuildDirectory().dir("renderdoc")); + }); + + Provider renderDocDir = extractRenderDoc.map(Sync::getOutputs) + .map(TaskOutputs::getFiles) + .map(FileCollection::getSingleFile) + .map(dir -> new File(dir, renderDocBaseName)); + + if (operatingSystem.isLinux()) { + renderDocDir = renderDocDir.map(dir -> new File(dir, "bin")); + } + + Provider renderDocCMD = renderDocDir.map(dir -> new File(dir, "renderdoccmd" + executableExt)); + Provider renderDocUI = renderDocDir.map(dir -> new File(dir, "qrenderdoc" + executableExt)); + + getTasks().register("startRenderDocUI", RenderDocRunUITask.class, task -> task.getRenderDocExecutable().fileProvider(renderDocUI)); + + getTasks().withType(RenderDocRunTask.class).configureEach(task -> { + task.getRenderDocExecutable().fileProvider(renderDocCMD); }); } diff --git a/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java new file mode 100644 index 00000000..3035af36 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java @@ -0,0 +1,92 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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 java.io.File; + +import javax.inject.Inject; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.process.CommandLineArgumentProvider; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.Platform; + +public abstract class RenderDocRunTask extends RunGameTask { + private static final Logger LOGGER = LoggerFactory.getLogger(RenderDocRunTask.class); + + @InputFile + public abstract RegularFileProperty getRenderDocExecutable(); + + @Input + public abstract ListProperty getRenderDocArgs(); + + @Inject + protected abstract ExecOperations getExecOperations(); + + @Inject + public RenderDocRunTask(RunConfigSettings settings) { + super(settings); + setGroup(Constants.TaskGroup.FABRIC); + dependsOn("configureClientLaunch"); + getRenderDocArgs().addAll("capture", "--wait-for-exit", "--working-dir", getWorkingDir().getAbsolutePath()); + } + + @Override + public void exec() { + ExecResult result = getExecOperations().exec(exec -> { + exec.workingDir(new File(getProjectDir().get(), getInternalRunDir().get())); + exec.environment(getInternalEnvironmentVars().get()); + + exec.commandLine(getRenderDocExecutable().get().getAsFile()); + exec.args(getRenderDocArgs().get()); + exec.args(getJavaLauncher().get().getExecutablePath()); + exec.args(getJvmArgs()); + exec.args(getMainClass().get()); + + for (CommandLineArgumentProvider provider : getArgumentProviders()) { + exec.args(provider.asArguments()); + } + + LOGGER.info("Running command: {}", exec.getCommandLine()); + }); + result.assertNormalExitValue(); + } + + public static boolean isSupported(Platform platform) { + final Platform.OperatingSystem os = platform.getOperatingSystem(); + final Platform.Architecture arch = platform.getArchitecture(); + // RenderDoc does support 32-bit Windows, but I cannot be bothered to test/maintain it + return (os.isLinux() || os.isWindows()) && arch.isX64(); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java b/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java new file mode 100644 index 00000000..7716d5c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java @@ -0,0 +1,51 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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 java.io.IOException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.util.Constants; + +public abstract class RenderDocRunUITask extends DefaultTask { + @InputFile + public abstract RegularFileProperty getRenderDocExecutable(); + + public RenderDocRunUITask() { + setGroup(Constants.TaskGroup.FABRIC); + } + + @TaskAction + public void run() throws IOException { + ProcessBuilder builder = new ProcessBuilder() + .command(getRenderDocExecutable().getAsFile().get().getAbsolutePath()); + builder.start(); + // Allow to run in the background. + } +} diff --git a/src/main/java/net/fabricmc/loom/util/Platform.java b/src/main/java/net/fabricmc/loom/util/Platform.java index 9cb99f89..c7487549 100644 --- a/src/main/java/net/fabricmc/loom/util/Platform.java +++ b/src/main/java/net/fabricmc/loom/util/Platform.java @@ -53,6 +53,10 @@ public interface Platform { boolean isArm(); boolean isRiscV(); + + default boolean isX64() { + return is64Bit() && !isArm() && !isRiscV(); + } } Architecture getArchitecture();