You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2023/02/14 16:06:41 UTC

[maven-mvnd] branch master updated: Make Classworld setup more alike to vanilla Maven (#784)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-mvnd.git


The following commit(s) were added to refs/heads/master by this push:
     new 1bcfc29d Make Classworld setup more alike to vanilla Maven (#784)
1bcfc29d is described below

commit 1bcfc29d457f7c3923efb0b5144056ac76170397
Author: Stefan Oehme <st...@gmail.com>
AuthorDate: Tue Feb 14 17:06:34 2023 +0100

    Make Classworld setup more alike to vanilla Maven (#784)
    
    Use the plexus Launcher to start the daemon server, just like we would
    launch a normal Maven process.
    
    This improves compatibility with any extensions or plugins that assume that
    their ClassLoader is a ClassRealm.
---
 .../org/mvndaemon/mvnd/client/DaemonConnector.java |  43 ++-
 .../org/mvndaemon/mvnd/common/MavenDaemon.java     |  85 -----
 .../maven/classrealm/MvndClassRealmManager.java    | 385 ---------------------
 .../java/org/apache/maven/cli/DaemonMavenCli.java  |   4 +-
 .../java/org/mvndaemon/mvnd/daemon/Server.java     |   8 +-
 dist/src/main/distro/bin/mvnd-server.conf          |  25 ++
 pom.xml                                            |   1 -
 7 files changed, 60 insertions(+), 491 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 eeb3c7d1..4c2fc477 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.DaemonRegistry;
 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 @@ public class DaemonConnector {
         final Path mvndHome = parameters.mvndHome();
         final Path workingDir = parameters.userDir();
         String command = "";
-        try (DirectoryStream<Path> jarPaths =
-                Files.newDirectoryStream(mvndHome.resolve("lib").resolve("ext"))) {
+        try {
             List<String> 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<Path> 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<Path> 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()) {
@@ -422,6 +430,7 @@ public class DaemonConnector {
             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 +448,7 @@ public class DaemonConnector {
                             .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 0ebbfee5..00000000
--- 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 67dea464..00000000
--- 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 <a href="https://github.com/apache/maven-mvnd/issues/690">mvnd#690</a>
- */
-@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.
-     * <p>
-     * 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<ClassRealmManagerDelegate> 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<String> providedArtifacts;
-
-    @Inject
-    public MvndClassRealmManager(
-            Logger logger,
-            PlexusContainer container,
-            List<ClassRealmManagerDelegate> delegates,
-            CoreExportsProvider exports) {
-        this.logger = logger;
-        this.world = ((MutablePlexusContainer) container).getClassWorld();
-        this.containerRealm = container.getContainerRealm();
-        this.delegates = delegates;
-
-        Map<String, ClassLoader> 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<String> parentImports,
-            Map<String, ClassLoader> foreignImports,
-            List<Artifact> artifacts) {
-        Set<String> artifactIds = new LinkedHashSet<>();
-
-        List<ClassRealmConstituent> 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<String> 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<Artifact> 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<Artifact> artifacts) {
-        Objects.requireNonNull(plugin, "plugin cannot be null");
-
-        ClassLoader parent = PARENT_CLASSLOADER;
-
-        Map<String, ClassLoader> foreignImports = Collections.<String, ClassLoader>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<String> parentImports,
-            Map<String, ClassLoader> foreignImports,
-            List<Artifact> 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<String> parentImports,
-            Map<String, ClassLoader> foreignImports,
-            List<ClassRealmConstituent> constituents) {
-        List<ClassRealmManagerDelegate> 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<String> populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) {
-        Set<String> 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<String> parentImports, Map<String, ClassLoader> foreignImports) {
-        if (foreignImports != null && !foreignImports.isEmpty()) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Importing foreign packages into class realm " + classRealm.getId());
-            }
-
-            for (Map.Entry<String, ClassLoader> 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 765430a7..940d6180 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 class DaemonMavenCli {
         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 @@ public class DaemonMavenCli {
 
         List<File> 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 115417cd..f416b5de 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 00000000..45976afb
--- /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 27fcaf6a..25a2863f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -320,7 +320,6 @@
           </exclusion>
         </exclusions>
       </dependency>
-
     </dependencies>
   </dependencyManagement>