diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index b5dff55ce596..478bd0a51bad 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -37,6 +37,7 @@ import java.util.Properties; import java.util.function.Consumer; import java.util.function.UnaryOperator; +import java.util.logging.Handler; import org.apache.maven.api.Constants; import org.apache.maven.api.ProtoSession; @@ -124,6 +125,8 @@ public final int invoke(InvokerRequest invokerRequest) { Properties oldProps = new Properties(); oldProps.putAll(System.getProperties()); ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); + // activateLogging() replaces them with SLF4JBridgeHandler, whose classes live in Maven's realm + Handler[] oldRootLoggerHandlers = java.util.logging.Logger.getLogger("").getHandlers(); try (C context = createContext(invokerRequest)) { if (contextConsumer != null) { contextConsumer.accept(context); @@ -147,6 +150,22 @@ public final int invoke(InvokerRequest invokerRequest) { } finally { Thread.currentThread().setContextClassLoader(oldCL); System.setProperties(oldProps); + restoreRootLoggerHandlers(oldRootLoggerHandlers); + } + } + + /** + * Puts back the handlers the {@code java.util.logging} root logger had before the invocation. When Maven runs inside + * another JVM, the root logger belongs to that JVM, and a {@code SLF4JBridgeHandler} left on it would route the + * host's logging to Maven and fail once Maven's class realm is closed. + */ + private static void restoreRootLoggerHandlers(Handler[] handlers) { + java.util.logging.Logger root = java.util.logging.Logger.getLogger(""); + for (Handler handler : root.getHandlers()) { + root.removeHandler(handler); + } + for (Handler handler : handlers) { + root.addHandler(handler); } } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index b1115a4dce4f..18c6f828506f 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -23,6 +23,8 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; @@ -37,6 +39,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -66,6 +69,37 @@ void defaultFs( invoke(cwd, userHome, List.of("verify"), List.of()); } + /** + * The invocation replaces the handlers of the {@code java.util.logging} root logger with {@code SLF4JBridgeHandler}; + * they must be back afterwards, as the root logger belongs to the JVM Maven runs in (GH-13362). + */ + @Test + void restoresJavaUtilLoggingRootHandlers( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) + throws Exception { + java.util.logging.Logger root = java.util.logging.Logger.getLogger(""); + Handler[] original = root.getHandlers(); + Handler marker = new ConsoleHandler(); + for (Handler handler : original) { + root.removeHandler(handler); + } + root.addHandler(marker); + try { + invoke(cwd, userHome, List.of("validate"), List.of()); + + assertArrayEquals(new Handler[] {marker}, root.getHandlers()); + } finally { + root.removeHandler(marker); + for (Handler handler : root.getHandlers()) { + root.removeHandler(handler); + } + for (Handler handler : original) { + root.addHandler(handler); + } + } + } + @Disabled("Enable it when fully moved to NIO2 with Path/Filesystem (ie MavenExecutionRequest)") @Test void jimFs() throws Exception {