From 237e85107c94a036009880b8a5125963a05c2f5b Mon Sep 17 00:00:00 2001 From: Stefan Oehme Date: Mon, 30 Jan 2023 19:34:17 +0100 Subject: [PATCH] Make Classworld setup more alike to vanilla Maven Instead of providing a custom URLClassLoader, create the ClassWorld using the plexus configurator and then pass it down the call chain. This improves compatbility with any extensions or plugins that assume that their ClassLoader is a ClassRealm. --- .../mvnd/client/DaemonConnector.java | 35 ++++++--- common/pom.xml | 5 ++ .../mvndaemon/mvnd/common/MavenDaemon.java | 72 ++++++------------- .../org/apache/maven/cli/DaemonMavenCli.java | 6 +- .../org/mvndaemon/mvnd/daemon/Server.java | 5 +- pom.xml | 5 ++ 6 files changed, 63 insertions(+), 65 deletions(-) diff --git a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java index eeb3c7d1d..88e689ce9 100644 --- a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java +++ b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java @@ -339,8 +339,7 @@ private Process startDaemonProcess(String daemonId, ClientOutput output) { final Path mvndHome = parameters.mvndHome(); final Path workingDir = parameters.userDir(); String command = ""; - try (DirectoryStream jarPaths = - Files.newDirectoryStream(mvndHome.resolve("lib").resolve("ext"))) { + try { List args = new ArrayList<>(); // executable final String java = Os.current().isUnixLike() ? "bin/java" : "bin\\java.exe"; @@ -348,13 +347,26 @@ private Process startDaemonProcess(String daemonId, ClientOutput output) { // classpath String mvndCommonPath = null; String mvndAgentPath = null; - for (Path jar : jarPaths) { - String s = jar.getFileName().toString(); - if (s.endsWith(".jar")) { - if (s.startsWith("mvnd-common-")) { - mvndCommonPath = jar.toString(); - } else if (s.startsWith("mvnd-agent-")) { - mvndAgentPath = jar.toString(); + String plexusClassworldsPath = null; + try (DirectoryStream jarPaths = Files.newDirectoryStream(mvndHome.resolve("lib/ext"))) { + for (Path jar : jarPaths) { + String s = jar.getFileName().toString(); + if (s.endsWith(".jar")) { + if (s.startsWith("mvnd-common-")) { + mvndCommonPath = jar.toString(); + } else if (s.startsWith("mvnd-agent-")) { + mvndAgentPath = jar.toString(); + } + } + } + } + try (DirectoryStream jarPaths = Files.newDirectoryStream(mvndHome.resolve("boot"))) { + for (Path jar : jarPaths) { + String s = jar.getFileName().toString(); + if (s.endsWith(".jar")) { + if (s.startsWith("plexus-classworlds-")) { + plexusClassworldsPath = jar.toString(); + } } } } @@ -364,8 +376,11 @@ private Process startDaemonProcess(String daemonId, ClientOutput output) { if (mvndAgentPath == null) { throw new IllegalStateException("Could not find mvnd-agent jar in lib/"); } + if (plexusClassworldsPath == null) { + throw new IllegalStateException("Could not find plexus-classworlds jar in boot/"); + } args.add("-classpath"); - args.add(mvndCommonPath + File.pathSeparator + mvndAgentPath); + args.add(mvndCommonPath + File.pathSeparator + plexusClassworldsPath + File.pathSeparator + mvndAgentPath); args.add("-javaagent:" + mvndAgentPath); // debug options if (parameters.property(Environment.MVND_DEBUG).asBoolean()) { diff --git a/common/pom.xml b/common/pom.xml index a66534ceb..1346e2b24 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -40,6 +40,11 @@ org.jline jline-terminal-jansi + + org.codehaus.plexus + plexus-classworlds + provided + org.slf4j diff --git a/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java b/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java index 0ebbfee55..c9cc644a6 100644 --- a/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java +++ b/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java @@ -18,67 +18,39 @@ */ package org.mvndaemon.mvnd.common; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; +import java.io.InputStream; +import java.lang.reflect.Constructor; import java.nio.file.Files; import java.nio.file.Path; -import java.util.stream.Stream; + +import org.codehaus.plexus.classworlds.ClassWorld; +import org.codehaus.plexus.classworlds.launcher.Launcher; +import org.codehaus.plexus.classworlds.realm.ClassRealm; public class MavenDaemon { public static void main(String[] args) throws Exception { - final Path mvndHome = Environment.MVND_HOME.asPath(); - URL[] classpath = Stream.concat( - /* jars */ - Stream.of("lib/ext", "lib", "boot") - .map(mvndHome::resolve) - .flatMap((Path p) -> { - try { - return Files.list(p); - } catch (java.io.IOException e) { - throw new RuntimeException("Could not list " + p, e); - } - }) - .filter(p -> { - final String fileName = p.getFileName().toString(); - return fileName.endsWith(".jar") && !fileName.startsWith("mvnd-client-"); - }) - .filter(Files::isRegularFile), - /* resources */ - Stream.of(mvndHome.resolve("conf"), mvndHome.resolve("conf/logging"))) - .map(Path::normalize) - .map(Path::toUri) - .map(uri -> { - try { - return uri.toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }) - .toArray(URL[]::new); - ClassLoader loader = new URLClassLoader(classpath, null) { + Path mvndHome = Environment.MVND_HOME.asPath(); + Launcher launcher = new Launcher(); + launcher.setSystemClassLoader(new ClassLoader(ClassLoader.getPlatformClassLoader()) { + @Override protected Class findClass(String name) throws ClassNotFoundException { - try { - return super.findClass(name); - } catch (ClassNotFoundException e) { + if (name.startsWith("org.codehaus.plexus.classworlds.") + || name.startsWith("org.codehaus.classworlds.")) { return MavenDaemon.class.getClassLoader().loadClass(name); } + throw new ClassNotFoundException(name); } - - @Override - public URL getResource(String name) { - URL url = super.getResource(name); - if (url == null) { - url = MavenDaemon.class.getClassLoader().getResource(name); - } - return url; - } - }; - Thread.currentThread().setContextClassLoader(loader); - Class clazz = loader.loadClass("org.mvndaemon.mvnd.daemon.Server"); - try (AutoCloseable server = (AutoCloseable) clazz.getConstructor().newInstance()) { + }); + try (InputStream in = Files.newInputStream(mvndHome.resolve("bin/m2.conf"))) { + launcher.configure(in); + } + ClassWorld world = launcher.getWorld(); + ClassRealm coreRealm = launcher.getMainRealm(); + Class serverClass = coreRealm.loadClass("org.mvndaemon.mvnd.daemon.Server"); + Constructor serverConstructor = serverClass.getConstructor(ClassWorld.class); + try (AutoCloseable server = (AutoCloseable) serverConstructor.newInstance(world)) { ((Runnable) server).run(); } } diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java index 765430a7f..a30d0994d 100644 --- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java +++ b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java @@ -174,13 +174,12 @@ public class DaemonMavenCli { */ private BuildEventListener buildEventListener = BuildEventListener.dummy(); - public DaemonMavenCli() throws Exception { + public DaemonMavenCli(ClassWorld classWorld) throws Exception { slf4jLoggerFactory = LoggerFactory.getILoggerFactory(); slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName()); plexusLoggerManager = new Slf4jLoggerManager(); - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - classWorld = new ClassWorld("plexus.core", cl); + this.classWorld = classWorld; container = container(); @@ -473,6 +472,7 @@ DefaultPlexusContainer container() throws Exception { List extClassPath = Stream.of( Environment.MVND_EXT_CLASSPATH.asString().split(",")) + .filter(s -> s != null && !s.isEmpty()) .map(File::new) .collect(Collectors.toList()); diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java b/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java index 115417cd8..51c514efc 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java @@ -51,6 +51,7 @@ import java.util.stream.Collectors; import org.apache.maven.cli.DaemonMavenCli; +import org.codehaus.plexus.classworlds.ClassWorld; import org.mvndaemon.mvnd.builder.SmartBuilder; import org.mvndaemon.mvnd.common.DaemonConnection; import org.mvndaemon.mvnd.common.DaemonException; @@ -99,7 +100,7 @@ public class Server implements AutoCloseable, Runnable { private final DaemonMemoryStatus memoryStatus; private final long keepAliveMs; - public Server() throws IOException { + public Server(ClassWorld world) throws IOException { // When spawning a new process, the child process is create within // the same process group. This means that a few signals are sent // to the whole group. This is the case for SIGINT (Ctrl-C) and @@ -123,7 +124,7 @@ public Server() throws IOException { .orElse(SocketFamily.inet); try { - cli = new DaemonMavenCli(); + cli = new DaemonMavenCli(world); registry = new DaemonRegistry(Environment.MVND_REGISTRY.asPath()); socket = socketFamily.openServerSocket(); executor = Executors.newScheduledThreadPool(1); diff --git a/pom.xml b/pom.xml index 27fcaf6a9..c0ca1a50b 100644 --- a/pom.xml +++ b/pom.xml @@ -320,6 +320,11 @@ + + org.codehaus.plexus + plexus-classworlds + 2.6.0 +