From c60b456f7eeec57410aff67ce0f3daad8b4507f4 Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 11 Mar 2024 21:16:46 +0000 Subject: [PATCH] Print info about locked files during configuration or genSources (#1066) * Print info about locked files during configuration or genSources * Use release version * Output adjustments * Fix build * Add user back --- build.gradle | 3 +- gradle/libs.versions.toml | 2 + .../configuration/CompileConfiguration.java | 71 +----------- .../loom/task/GenerateSourcesTask.java | 3 +- .../net/fabricmc/loom/util/ExceptionUtil.java | 47 +++++++- .../net/fabricmc/loom/util/ProcessUtil.java | 109 ++++++++++++++++++ .../loom/test/unit/ProcessUtilTest.groovy | 41 +++++++ 7 files changed, 206 insertions(+), 70 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/util/ProcessUtil.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy diff --git a/build.gradle b/build.gradle index c15a009d..8c426029 100644 --- a/build.gradle +++ b/build.gradle @@ -131,11 +131,12 @@ dependencies { implementation libs.fabric.tiny.remapper implementation libs.fabric.access.widener implementation libs.fabric.mapping.io - implementation (libs.fabric.lorenz.tiny) { transitive = false } + implementation libs.fabric.loom.nativelib + // decompilers fernflowerCompileOnly runtimeLibs.fernflower fernflowerCompileOnly libs.fabric.mapping.io diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index acb5aa82..a4b1d3bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ mapping-io = "0.5.1" lorenz-tiny = "4.0.2" mercury = "0.4.1" kotlinx-metadata = "0.9.0" +loom-native = "0.1.0" # Plugins spotless = "6.25.0" @@ -38,6 +39,7 @@ fabric-access-widener = { module = "net.fabricmc:access-widener", version.ref = fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping-io" } fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" } fabric-mercury = { module = "net.fabricmc:mercury", version.ref = "mercury" } +fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.ref = "loom-native" } # Misc kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 7cf9c62d..1e59e9fc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -32,8 +32,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -41,7 +39,6 @@ import javax.inject.Inject; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.plugins.JavaPlugin; @@ -72,6 +69,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraft import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.ExceptionUtil; +import net.fabricmc.loom.util.ProcessUtil; import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; import net.fabricmc.loom.util.service.ScopedSharedServiceManager; @@ -110,6 +108,7 @@ public abstract class CompileConfiguration implements Runnable { try { setupMinecraft(configContext); } catch (Exception e) { + ExceptionUtil.printFileLocks(e, getProject()); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e); } @@ -315,7 +314,8 @@ public abstract class CompileConfiguration implements Runnable { Files.deleteIfExists(lockFile.file); abrupt = true; } else { - logger.lifecycle(printWithParents(handle.get())); + ProcessUtil processUtil = ProcessUtil.create(getProject()); + logger.lifecycle(processUtil.printWithParents(handle.get())); logger.lifecycle("Waiting for lock to be released..."); long sleptMs = 0; @@ -353,69 +353,6 @@ public abstract class CompileConfiguration implements Runnable { return abrupt ? LockResult.ACQUIRED_PREVIOUS_OWNER_MISSING : LockResult.ACQUIRED_CLEAN; } - private String printWithParents(ProcessHandle processHandle) { - var output = new StringBuilder(); - - List chain = getParentChain(null, processHandle); - - for (int i = 0; i < chain.size(); i++) { - ProcessHandle handle = chain.get(i); - - output.append("\t".repeat(i)); - - if (i != 0) { - output.append("└─ "); - } - - output.append(getInfoString(handle)); - - if (i < chain.size() - 1) { - output.append('\n'); - } - } - - return output.toString(); - } - - private String getInfoString(ProcessHandle handle) { - return "(%s) pid %s '%s%s'%s".formatted( - handle.info().user().orElse("unknown user"), - handle.pid(), - handle.info().command().orElse("unknown command"), - handle.info().arguments().map(arr -> { - if (getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.INFO - && getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.DEBUG) { - return " (run with --info or --debug to show arguments, may reveal sensitive info)"; - } - - String join = String.join(" ", arr); - - if (join.isBlank()) { - return ""; - } - - return " " + join; - }).orElse(" (unknown arguments)"), - handle.info().startInstant().map(instant -> " started at " + instant).orElse("") - ); - } - - private List getParentChain(List collectTo, ProcessHandle processHandle) { - if (collectTo == null) { - collectTo = new ArrayList<>(); - } - - Optional parent = processHandle.parent(); - - if (parent.isPresent()) { - getParentChain(collectTo, parent.get()); - } - - collectTo.add(processHandle); - - return collectTo; - } - private void releaseLock() { final Path lock = getLockFile().file; diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 51850533..0e235b0b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -198,7 +198,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(false, true, configContext); minecraftJars = getExtension().getNamedMinecraftProvider().provide(provideContext); } catch (Exception e) { - throw new RuntimeException("Failed to rebuild input jars", e); + ExceptionUtil.printFileLocks(e, getProject()); + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to rebuild input jars", e); } for (MinecraftJar minecraftJar : minecraftJars) { diff --git a/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java b/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java index 3683301e..8f942260 100644 --- a/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2022 FabricMC + * 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 @@ -24,8 +24,17 @@ package net.fabricmc.loom.util; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; import java.util.function.BiFunction; +import org.gradle.api.Project; + +import net.fabricmc.loom.nativeplatform.LoomNativePlatform; + public final class ExceptionUtil { /** * Creates a descriptive user-facing wrapper exception for an underlying cause. @@ -44,4 +53,40 @@ public final class ExceptionUtil { String descriptiveMessage = "%s, %s: %s".formatted(message, cause.getClass().getName(), cause.getMessage()); return constructor.apply(descriptiveMessage, cause); } + + public static void printFileLocks(Throwable e, Project project) { + Throwable cause = e; + + while (cause != null) { + if (cause instanceof FileSystemException fse) { + printFileLocks(fse.getFile(), project); + break; + } + + cause = cause.getCause(); + } + } + + private static void printFileLocks(String filename, Project project) { + final Path path = Paths.get(filename); + + if (!Files.exists(path)) { + return; + } + + final List processes = LoomNativePlatform.getProcessesWithLockOn(path); + + if (processes.isEmpty()) { + return; + } + + final ProcessUtil processUtil = ProcessUtil.create(project); + + final String noun = processes.size() == 1 ? "process has" : "processes have"; + project.getLogger().error("The following {} a lock on the file '{}':", noun, path); + + for (ProcessHandle process : processes) { + project.getLogger().error(processUtil.printWithParents(process)); + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/ProcessUtil.java b/src/main/java/net/fabricmc/loom/util/ProcessUtil.java new file mode 100644 index 00000000..93f8e1e3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ProcessUtil.java @@ -0,0 +1,109 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.StringJoiner; + +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; + +import net.fabricmc.loom.nativeplatform.LoomNativePlatform; + +public record ProcessUtil(LogLevel logLevel) { + private static final String EXPLORER_COMMAND = "C:\\Windows\\explorer.exe"; + + public static ProcessUtil create(Project project) { + return new ProcessUtil(project.getGradle().getStartParameter().getLogLevel()); + } + + public String printWithParents(ProcessHandle handle) { + String result = printWithParents(handle, 0).trim(); + + if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) { + return "Run with --info or --debug to show arguments, may reveal sensitive info\n" + result; + } + + return result; + } + + private String printWithParents(ProcessHandle handle, int depth) { + var lines = new ArrayList(); + getWindowTitles(handle).ifPresent(titles -> lines.add("title: " + titles)); + lines.add("pid: " + handle.pid()); + handle.info().command().ifPresent(command -> lines.add("command: " + command)); + getProcessArguments(handle).ifPresent(arguments -> lines.add("arguments: " + arguments)); + handle.info().startInstant().ifPresent(instant -> lines.add("started at: " + instant)); + handle.info().user().ifPresent(user -> lines.add("user: " + user)); + handle.parent().ifPresent(parent -> lines.add("parent:\n" + printWithParents(parent, depth + 1))); + + StringBuilder sj = new StringBuilder(); + + for (String line : lines) { + sj.append("\t".repeat(depth)).append("- ").append(line).append('\n'); + } + + return sj.toString(); + } + + private Optional getProcessArguments(ProcessHandle handle) { + if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) { + return Optional.empty(); + } + + return handle.info().arguments().map(arr -> { + String join = String.join(" ", arr); + + if (join.isBlank()) { + return ""; + } + + return " " + join; + }); + } + + private Optional getWindowTitles(ProcessHandle processHandle) { + if (processHandle.info().command().orElse("").equals(EXPLORER_COMMAND)) { + // Explorer is a single process, so the window titles are not useful + return Optional.empty(); + } + + List titles = LoomNativePlatform.getWindowTitlesForPid(processHandle.pid()); + + if (titles.isEmpty()) { + return Optional.empty(); + } + + final StringJoiner joiner = new StringJoiner(", "); + + for (String title : titles) { + joiner.add("'" + title + "'"); + } + + return Optional.of(joiner.toString()); + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy new file mode 100644 index 00000000..76a96ea3 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy @@ -0,0 +1,41 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 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.test.unit + +import org.gradle.api.logging.LogLevel +import spock.lang.Specification + +import net.fabricmc.loom.util.ProcessUtil + +class ProcessUtilTest extends Specification { + def "print process info"() { + when: + def output = new ProcessUtil(LogLevel.DEBUG).printWithParents(ProcessHandle.current()) + + then: + // Just a simple check to see if the output is not empty + !output.isEmpty() + } +}