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..01125914e 100644 --- a/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java +++ b/client/src/main/java/org/mvndaemon/mvnd/client/DaemonConnector.java @@ -18,7 +18,6 @@ */ package org.mvndaemon.mvnd.client; -import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -52,7 +51,6 @@ import org.mvndaemon.mvnd.common.DaemonState; import org.mvndaemon.mvnd.common.DaemonStopEvent; import org.mvndaemon.mvnd.common.Environment; -import org.mvndaemon.mvnd.common.MavenDaemon; import org.mvndaemon.mvnd.common.Message; import org.mvndaemon.mvnd.common.Os; import org.mvndaemon.mvnd.common.SocketFamily; @@ -339,33 +337,43 @@ 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"; args.add(parameters.javaHome().resolve(java).toString()); // 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").resolve("ext"))) { + for (Path jar : jarPaths) { + String s = jar.getFileName().toString(); + if (s.endsWith(".jar")) { + if (s.startsWith("mvnd-agent-")) { + mvndAgentPath = jar.toString(); + } } } } - if (mvndCommonPath == null) { - throw new IllegalStateException("Could not find mvnd-common jar in lib/"); + 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(); + } + } + } } if (mvndAgentPath == null) { - throw new IllegalStateException("Could not find mvnd-agent jar in lib/"); + throw new IllegalStateException("Could not find mvnd-agent jar in lib/ext/"); + } + 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(plexusClassworldsPath); args.add("-javaagent:" + mvndAgentPath); // debug options if (parameters.property(Environment.MVND_DEBUG).asBoolean()) { @@ -420,8 +428,7 @@ private Process startDaemonProcess(String daemonId, ClientOutput output) { } Environment.MVND_HOME.addSystemProperty(args, mvndHome.toString()); - args.add("-Dmaven.home=" + mvndHome); - args.add("-Dmaven.conf=" + mvndHome.resolve("conf")); + args.add("-Dclassworlds.conf=" + mvndHome.resolve("bin").resolve("mvnd-server.conf")); Environment.MVND_JAVA_HOME.addSystemProperty( args, parameters.javaHome().toString()); @@ -439,7 +446,7 @@ private Process startDaemonProcess(String daemonId, ClientOutput output) { .orElseGet(() -> getJavaVersion() >= 16.0f ? SocketFamily.unix : SocketFamily.inet) .toString()); parameters.discriminatingSystemProperties(args); - args.add(MavenDaemon.class.getName()); + args.add("org.codehaus.plexus.classworlds.launcher.Launcher"); command = String.join(" ", args); LOGGER.debug( diff --git a/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java b/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java deleted file mode 100644 index 0ebbfee55..000000000 --- a/common/src/main/java/org/mvndaemon/mvnd/common/MavenDaemon.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.mvndaemon.mvnd.common; - -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; - -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) { - @Override - protected Class findClass(String name) throws ClassNotFoundException { - try { - return super.findClass(name); - } catch (ClassNotFoundException e) { - return MavenDaemon.class.getClassLoader().loadClass(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()) { - ((Runnable) server).run(); - } - } -} diff --git a/daemon/src/main/java/org/apache/maven/classrealm/MvndClassRealmManager.java b/daemon/src/main/java/org/apache/maven/classrealm/MvndClassRealmManager.java deleted file mode 100644 index 67dea464d..000000000 --- a/daemon/src/main/java/org/apache/maven/classrealm/MvndClassRealmManager.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.maven.classrealm; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import java.io.File; -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.Set; -import java.util.TreeMap; - -import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.extension.internal.CoreExportsProvider; -import org.apache.maven.model.Model; -import org.apache.maven.model.Plugin; -import org.codehaus.plexus.MutablePlexusContainer; -import org.codehaus.plexus.PlexusContainer; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; -import org.codehaus.plexus.logging.Logger; -import org.codehaus.plexus.util.StringUtils; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.sisu.Priority; - -/** - * This class is a copy of DefaultClassRealmManager with one modification: - * the {@link #PARENT_CLASSLOADER} is set to null instead of classworld's classloader. - * The reason is that mvnd is booted by {@link org.mvndaemon.mvnd.common.MavenDaemon} - * instead of {@link org.codehaus.plexus.classworlds.launcher.Launcher} and classworld - * is contained by the maven classloader. This can cause problems with extensions - * as their parent contains the whole maven classloader. - * See mvnd#690 - */ -@Named -@Singleton -@Priority(10) -public class MvndClassRealmManager implements ClassRealmManager { - public static final String API_REALMID = "maven.api"; - - /** - * During normal command line build, ClassWorld is loaded by jvm system classloader, which only includes - * plexus-classworlds jar and possibly javaagent classes, see https://issues.apache.org/jira/browse/MNG-4747. - *

- * Using ClassWorld to determine plugin/extensions realm parent classloaders gives m2e and integration test harness - * flexibility to load multiple version of maven into dedicated classloaders without assuming state of jvm system - * classloader. - */ - private static final ClassLoader PARENT_CLASSLOADER = ClassLoader.getSystemClassLoader(); - - private final Logger logger; - - private final ClassWorld world; - - private final ClassRealm containerRealm; - - // this is a live injected collection - private final List delegates; - - private final ClassRealm mavenApiRealm; - - /** - * Patterns of artifacts provided by maven core and exported via maven api realm. These artifacts are filtered from - * plugin and build extensions realms to avoid presence of duplicate and possibly conflicting classes on classpath. - */ - private final Set providedArtifacts; - - @Inject - public MvndClassRealmManager( - Logger logger, - PlexusContainer container, - List delegates, - CoreExportsProvider exports) { - this.logger = logger; - this.world = ((MutablePlexusContainer) container).getClassWorld(); - this.containerRealm = container.getContainerRealm(); - this.delegates = delegates; - - Map foreignImports = exports.get().getExportedPackages(); - - this.mavenApiRealm = createRealm( - API_REALMID, - ClassRealmRequest.RealmType.Core, - null /* parent */, - null /* parentImports */, - foreignImports, - null /* artifacts */); - - this.providedArtifacts = exports.get().getExportedArtifacts(); - } - - private ClassRealm newRealm(String id) { - synchronized (world) { - String realmId = id; - - Random random = new Random(); - - while (true) { - try { - ClassRealm classRealm = world.newRealm(realmId, null); - - if (logger.isDebugEnabled()) { - logger.debug("Created new class realm " + realmId); - } - - return classRealm; - } catch (DuplicateRealmException e) { - realmId = id + '-' + random.nextInt(); - } - } - } - } - - public ClassRealm getMavenApiRealm() { - return mavenApiRealm; - } - - /** - * Creates a new class realm with the specified parent and imports. - * - * @param baseRealmId The base id to use for the new realm, must not be {@code null}. - * @param type The type of the class realm, must not be {@code null}. - * @param parent The parent realm for the new realm, may be {@code null}. - * @param parentImports The packages/types to import from the parent realm, may be {@code null}. - * @param foreignImports The packages/types to import from foreign realms, may be {@code null}. - * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a - * missing file) will automatically be excluded from the realm. - * @return The created class realm, never {@code null}. - */ - private ClassRealm createRealm( - String baseRealmId, - ClassRealmRequest.RealmType type, - ClassLoader parent, - List parentImports, - Map foreignImports, - List artifacts) { - Set artifactIds = new LinkedHashSet<>(); - - List constituents = new ArrayList<>(); - - if (artifacts != null) { - for (Artifact artifact : artifacts) { - if (!isProvidedArtifact(artifact)) { - artifactIds.add(getId(artifact)); - if (artifact.getFile() != null) { - constituents.add(new ArtifactClassRealmConstituent(artifact)); - } - } - } - } - - if (parentImports != null) { - parentImports = new ArrayList<>(parentImports); - } else { - parentImports = new ArrayList<>(); - } - - if (foreignImports != null) { - foreignImports = new TreeMap<>(foreignImports); - } else { - foreignImports = new TreeMap<>(); - } - - ClassRealm classRealm = newRealm(baseRealmId); - - if (parent != null) { - classRealm.setParentClassLoader(parent); - } - - callDelegates(classRealm, type, parent, parentImports, foreignImports, constituents); - - wireRealm(classRealm, parentImports, foreignImports); - - Set includedIds = populateRealm(classRealm, constituents); - - if (logger.isDebugEnabled()) { - artifactIds.removeAll(includedIds); - - for (String id : artifactIds) { - logger.debug(" Excluded: " + id); - } - } - - return classRealm; - } - - public ClassRealm getCoreRealm() { - return containerRealm; - } - - public ClassRealm createProjectRealm(Model model, List artifacts) { - Objects.requireNonNull(model, "model cannot be null"); - - ClassLoader parent = getMavenApiRealm(); - - return createRealm(getKey(model), ClassRealmRequest.RealmType.Project, parent, null, null, artifacts); - } - - private static String getKey(Model model) { - return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); - } - - public ClassRealm createExtensionRealm(Plugin plugin, List artifacts) { - Objects.requireNonNull(plugin, "plugin cannot be null"); - - ClassLoader parent = PARENT_CLASSLOADER; - - Map foreignImports = Collections.singletonMap("", getMavenApiRealm()); - - return createRealm( - getKey(plugin, true), ClassRealmRequest.RealmType.Extension, parent, null, foreignImports, artifacts); - } - - private boolean isProvidedArtifact(Artifact artifact) { - return providedArtifacts.contains(artifact.getGroupId() + ":" + artifact.getArtifactId()); - } - - public ClassRealm createPluginRealm( - Plugin plugin, - ClassLoader parent, - List parentImports, - Map foreignImports, - List artifacts) { - Objects.requireNonNull(plugin, "plugin cannot be null"); - - if (parent == null) { - parent = PARENT_CLASSLOADER; - } - - return createRealm( - getKey(plugin, false), - ClassRealmRequest.RealmType.Plugin, - parent, - parentImports, - foreignImports, - artifacts); - } - - private static String getKey(Plugin plugin, boolean extension) { - String version = ArtifactUtils.toSnapshotVersion(plugin.getVersion()); - return (extension ? "extension>" : "plugin>") + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" - + version; - } - - private static String getId(Artifact artifact) { - return getId( - artifact.getGroupId(), - artifact.getArtifactId(), - artifact.getExtension(), - artifact.getClassifier(), - artifact.getBaseVersion()); - } - - private static String getId(ClassRealmConstituent constituent) { - return getId( - constituent.getGroupId(), - constituent.getArtifactId(), - constituent.getType(), - constituent.getClassifier(), - constituent.getVersion()); - } - - private static String getId(String gid, String aid, String type, String cls, String ver) { - return gid + ':' + aid + ':' + type + (StringUtils.isNotEmpty(cls) ? ':' + cls : "") + ':' + ver; - } - - private void callDelegates( - ClassRealm classRealm, - ClassRealmRequest.RealmType type, - ClassLoader parent, - List parentImports, - Map foreignImports, - List constituents) { - List delegates = new ArrayList<>(this.delegates); - - if (!delegates.isEmpty()) { - ClassRealmRequest request = - new DefaultClassRealmRequest(type, parent, parentImports, foreignImports, constituents); - - for (ClassRealmManagerDelegate delegate : delegates) { - try { - delegate.setupRealm(classRealm, request); - } catch (Exception e) { - logger.error( - delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": " - + e.getMessage(), - e); - } - } - } - } - - private Set populateRealm(ClassRealm classRealm, List constituents) { - Set includedIds = new LinkedHashSet<>(); - - if (logger.isDebugEnabled()) { - logger.debug("Populating class realm " + classRealm.getId()); - } - - for (ClassRealmConstituent constituent : constituents) { - File file = constituent.getFile(); - - String id = getId(constituent); - includedIds.add(id); - - if (logger.isDebugEnabled()) { - logger.debug(" Included: " + id); - } - - try { - classRealm.addURL(file.toURI().toURL()); - } catch (MalformedURLException e) { - // Not going to happen - logger.error(e.getMessage(), e); - } - } - - return includedIds; - } - - private void wireRealm(ClassRealm classRealm, List parentImports, Map foreignImports) { - if (foreignImports != null && !foreignImports.isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("Importing foreign packages into class realm " + classRealm.getId()); - } - - for (Map.Entry entry : foreignImports.entrySet()) { - ClassLoader importedRealm = entry.getValue(); - String imp = entry.getKey(); - - if (logger.isDebugEnabled()) { - logger.debug(" Imported: " + imp + " < " + getId(importedRealm)); - } - - classRealm.importFrom(importedRealm, imp); - } - } - - if (parentImports != null && !parentImports.isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("Importing parent packages into class realm " + classRealm.getId()); - } - - for (String imp : parentImports) { - if (logger.isDebugEnabled()) { - logger.debug(" Imported: " + imp + " < " + getId(classRealm.getParentClassLoader())); - } - - classRealm.importFromParent(imp); - } - } - } - - private String getId(ClassLoader classLoader) { - if (classLoader instanceof ClassRealm) { - return ((ClassRealm) classLoader).getId(); - } - return String.valueOf(classLoader); - } -} 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..940d6180b 100644 --- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java +++ b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java @@ -179,8 +179,7 @@ public DaemonMavenCli() throws Exception { slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName()); plexusLoggerManager = new Slf4jLoggerManager(); - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - classWorld = new ClassWorld("plexus.core", cl); + this.classWorld = ((ClassRealm) Thread.currentThread().getContextClassLoader()).getWorld(); 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..f416b5dee 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java @@ -99,7 +99,13 @@ public class Server implements AutoCloseable, Runnable { private final DaemonMemoryStatus memoryStatus; private final long keepAliveMs; - public Server() throws IOException { + public static void main(String[] args) { + try (Server server = new Server()) { + server.run(); + } + } + + public Server() { // 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 diff --git a/dist/src/main/distro/bin/mvnd-server.conf b/dist/src/main/distro/bin/mvnd-server.conf new file mode 100644 index 000000000..45976afba --- /dev/null +++ b/dist/src/main/distro/bin/mvnd-server.conf @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +main is org.mvndaemon.mvnd.daemon.Server from plexus.core + +set maven.home default ${mvnd.home} +set maven.conf default ${maven.home}/conf + +[plexus.core] +load ${maven.conf}/logging +optionally ${maven.home}/lib/ext/*.jar +load ${maven.home}/lib/*.jar diff --git a/pom.xml b/pom.xml index 27fcaf6a9..25a2863f4 100644 --- a/pom.xml +++ b/pom.xml @@ -320,7 +320,6 @@ -