项目介绍
介绍
该项目旨在使用Gradle和Takari已知的技术提供更快的Maven构建。
体系结构概述:
mvnd嵌入了Maven(因此不需要单独安装Maven)。
实际的构建发生在一个长期存在的后台进程中,也称为守护进程。
一个守护进程实例可以为来自mvnd客户端的多个连续请求提供服务。
mvnd客户端是使用GraalVM构建的本机可执行文件。与启动传统JVM相比,它启动得更快,使用的内存更少。
如果没有空闲的守护进程来为构建请求提供服务,则可以并行生成多个守护进程。
此体系结构具有以下优点:
用于运行实际构建的JVM不需要为每个构建重新启动。
持有Maven插件类的类加载器缓存在多个构建中。因此,插件jar只需读取和解析一次。不缓存SNAPSHOT版本的Maven插件。
JVM内部的实时(JIT)编译器生成的本机代码也保留了下来。与Maven相比,JIT编译所花费的时间更少。在重复构建过程中,JIT优化的代码会立即可用。这不仅适用于来自Maven插件和Maven Core的代码,也适用于来自JDK本身的所有代码。
其他功能
mvnd在Maven的基础上带来了以下功能:
- 默认情况下,mvnd使用多个CPU内核并行构建模块。使用的内核数由公式Math.max(Runtime.getRuntime().available Processors()-1,1)给出。如果源代码树不支持并行构建,请将-T1传递到命令行中,使构建串行化。
- 改进的控制台输出:我们认为在Maven上并行构建的输出很难遵循。因此,我们实现了一个简化的非滚动视图,在单独的行上显示每个构建线程的状态。
查看守护进程: 可以使用以下命令查看当前运行的守护进程:
mvnd --status
项目核心流程
-
执行
mvnd.cmd或mvnd.sh脚本,选择客户端:- 在Windows系统上,会执行
mvnd.cmd脚本。 - 在Unix系统上,会执行
mvnd.sh脚本。 - 这些脚本会首先确定是使用本地的原生客户端还是Java客户端,具体通过环境变量
MVND_CLIENT来控制。 - 如果选择原生客户端,会直接执行
mvnd.exe。 - 如果选择Java客户端,会调用Java命令行工具来运行
DefaultClient类。
- 在Windows系统上,会执行
-
启动客户端:
- 原生客户端(
mvnd.exe)和Java客户端(DefaultClient)的行为类似,都会连接到守护进程(Server)并发送构建请求。使用DaemonConnector尝试连接到守护进程。
- 原生客户端(
-
客户端发送请求:
- 客户端启动后,会读取用户的命令行参数,并创建一个构建请求(
BuildRequest)。 - 通过套接字(Socket)与守护进程(Daemon)通信,将构建请求发送给守护进程。
- 客户端启动后,会读取用户的命令行参数,并创建一个构建请求(
-
守护进程接收请求:
- 守护进程的
Server类在运行时会接受客户端的连接请求。 - 创建一个
BuildEventListener实例,用于监听构建过程中的各种事件。
- 守护进程的
-
执行构建:
- 守护进程解析构建请求,调用Maven CLI进行构建。
- 通过
SmartBuilder来优化构建过程,会并行处理多个构建任务。 - 构建过程中,会通过
BuildEventListener将各种事件和日志信息发送回客户端。
-
发送构建结果:
- 构建完成后,守护进程将结果(如成功或失败的状态码)发送回客户端。
- 客户端接收结果,并将其输出到用户的终端。
项目核心代码
围绕上述流程讲解对于代码:
- 执行
mvnd.cmd或mvnd.sh脚本,选择客户端:
2.
DefaultClient 类是 mvnd 项目中客户端的核心部分,负责解析命令行参数、连接守护进程,并处理构建请求。其主要功能包括设置系统属性、启动守护进程、发送构建请求、处理守护进程返回的消息以及日志清理等。
3.
DaemonConnector 通过connect连接到守护进程,查找所有空闲或忙碌的守护进程。如果没有可用的兼容守护进程,则启动一个新的守护进程并返回连接。
Client.java
public interface Client {
ExecutionResult execute(ClientOutput output, List<String> args) throws InterruptedException;
default ExecutionResult execute(ClientOutput output, String... args) throws InterruptedException {
return execute(output, Arrays.asList(args));
}
}
作用: 定义了客户端执行命令的接口方法。
DaemonConnector.java
- 作用: 负责连接到已有的守护进程或启动新的守护进程。
public class DaemonConnector {
private final DaemonParameters parameters;
private final DaemonRegistry registry;
public DaemonConnector(DaemonParameters parameters, DaemonRegistry registry) {
this.parameters = parameters;
this.registry = registry;
}
public DaemonClientConnection connect(ClientOutput output) {
// 连接或启动守护进程的逻辑
}
}
DaemonClientConnection.java
作用: 处理与守护进程的通信,发送和接收消息。DaemonClientConnection 类实现了 Closeable 接口,用于与 Daemon 进行通信。它维护了一个消息队列和一个接收线程,用于从 Daemon 接收消息。
public class DaemonClientConnection implements Closeable {
private final DaemonConnection connection;
private final DaemonInfo daemon;
private final boolean newDaemon;
private boolean hasReceived;
private final Lock dispatchLock = new ReentrantLock();
private final BlockingQueue<Message> queue = new ArrayBlockingQueue<>(16);
private final Thread receiver;
private final AtomicBoolean running = new AtomicBoolean(true);
private final AtomicReference<Exception> exception = new AtomicReference<>();
private final long maxKeepAliveMs;
private final DaemonParameters parameters;
public DaemonClientConnection(DaemonConnection connection, DaemonInfo daemon, boolean newDaemon, DaemonParameters parameters) {
this.connection = connection;
this.daemon = daemon;
this.newDaemon = newDaemon;
this.receiver = new Thread(this::doReceive);
this.receiver.start();
this.parameters = parameters;
this.maxKeepAliveMs = parameters.keepAlive().toMillis() * parameters.maxLostKeepAlive();
}
public void dispatch(Message message) throws DaemonException.ConnectException {
// 发送消息到守护进程
}
public List<Message> receive() throws ConnectException, StaleAddressException {
// 接收来自守护进程的消息
}
protected void doReceive() {
// 后台线程接收消息的实现
}
public void close() {
// 关闭连接
}
}
Server.java
Server类负责处理守护进程的启动、停止和管理,包括处理客户端请求和调度构建任务。
创建并运行一个
Server实例;
run方法启动守护进程,接受客户端连接,并处理构建请求。
提供了多个私有方法来处理客户端连接和请求,如
accept(接受连接)、client(处理客户端连接)和handle(处理构建请求)。
accept 方法不断地接受客户端连接,并为每个连接创建一个新的线程来处理客户端请求(即调用 client(socket) 方法)。
client类处理客户端连接:
handle类流程如下:
-
更新守护进程状态为
Busy。 -
初始化队列和监听器:
- 创建用于发送和接收消息的队列。
- 创建 ClientDispatcher 实例作为构建事件监听器。
- 创建 DaemonInputStream 实例,用于处理客户端输入数据请求。
-
启动一个新的线程,负责从
sendQueue队列中读取消息并通过连接发送到客户端。 -
启动一个新的线程,负责从客户端接收消息并将其放入
recvQueue队列。 -
如果接收到取消构建的消息,则更新状态为
Canceled。 -
如果守护进程不是在 noDaemon 模式下运行,则将状态更新为 Idle 并触发垃圾回收。
参考
- maven-mvnd github: github.com/apache/mave…