1. 概述
Jenkins 是开源的持续集成服务器,支持为特定任务/环境创建自定义插件。本文将完整演示如何开发一个扩展插件,用于在构建输出中添加项目统计信息(类数量和代码行数)。
2. 环境搭建
Jenkins 官方提供了便捷的 Maven 原型,我们直接使用它初始化项目:
mvn archetype:generate -Dfilter=io.jenkins.archetypes:plugin
执行后会显示可选原型列表:
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart
(org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> io.jenkins.archetypes:empty-plugin (Skeleton of
a Jenkins plugin with a POM and an empty source tree.)
2: remote -> io.jenkins.archetypes:global-configuration-plugin
(Skeleton of a Jenkins plugin with a POM and an example piece
of global configuration.)
3: remote -> io.jenkins.archetypes:hello-world-plugin
(Skeleton of a Jenkins plugin with a POM and an example build step.)
选择第一个选项(empty-plugin),然后在交互模式中定义 groupId/artifactId/package。完成后需要修改 pom.xml
中的占位符(如 <name>TODO Plugin</name>
)。
3. Jenkins 插件设计
3.1 扩展点机制
Jenkins 提供了丰富的扩展点(Extension Points)。这些接口或抽象类定义了特定场景的契约,允许插件实现自定义功能。
典型扩展点示例:
BuildStep
:定义构建步骤(如 "从 VCS 检出"、"编译"、"测试")BuildWrapper
:定义构建前/后操作- 第三方插件如 Email Extension 的
RecipientProvider
:提供邮件收件人
⚠️ 注意:早期插件需继承
hudson.Plugin
,现在推荐使用扩展点机制。
3.2 插件初始化
需要通过注解告知 Jenkins 如何实例化插件:
定义静态内部类并添加
@Extension
注解:class MyPlugin extends BuildWrapper { @Extension public static class DescriptorImpl extends BuildWrapperDescriptor { @Override public boolean isApplicable(AbstractProject<?, ?> item) { return true; } @Override public String getDisplayName() { return "UI 中显示的名称"; } } }
使用
@DataBoundConstructor
标记构造函数:@DataBoundConstructor public Maven( String targets, String name, String pom, String properties, String jvmOptions, boolean usePrivateRepository, SettingsProvider settings, GlobalSettingsProvider globalSettings, boolean injectBuildVariables) { ... }
构造函数参数会自动映射到 UI 配置界面(如 Maven 插件配置):
✅ 也可用
@DataBoundSetter
注解 setter 方法实现参数绑定。
4. 插件实现
我们通过 BuildWrapper
在构建过程中收集项目统计信息:
class ProjectStatsBuildWrapper extends BuildWrapper {
@DataBoundConstructor
public ProjectStatsBuildWrapper() {}
@Override
public Environment setUp(
AbstractBuild build,
Launcher launcher,
BuildListener listener) {}
@Extension
public static class DescriptorImpl extends BuildWrapperDescriptor {
@Override
public boolean isApplicable(AbstractProject<?, ?> item) {
return true;
}
@Nonnull
@Override
public String getDisplayName() {
return "构建时生成项目统计";
}
}
}
4.1 核心功能实现
定义统计实体类:
class ProjectStats {
private int classesNumber;
private int linesNumber;
// 标准构造/getter
}
实现统计逻辑(递归扫描 Java 文件):
private ProjectStats buildStats(FilePath root)
throws IOException, InterruptedException {
int classesNumber = 0;
int linesNumber = 0;
Stack<FilePath> toProcess = new Stack<>();
toProcess.push(root);
while (!toProcess.isEmpty()) {
FilePath path = toProcess.pop();
if (path.isDirectory()) {
toProcess.addAll(path.list());
} else if (path.getName().endsWith(".java")) {
classesNumber++;
linesNumber += countLines(path);
}
}
return new ProjectStats(classesNumber, linesNumber);
}
4.2 生成报告
创建 HTML 模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$PROJECT_NAME$</title>
</head>
<body>
Project $PROJECT_NAME$:
<table border="1">
<tr>
<th>Classes number</th>
<th>Lines number</th>
</tr>
<tr>
<td>$CLASSES_NUMBER$</td>
<td>$LINES_NUMBER$</td>
</tr>
</table>
</body>
</html>
在构建完成后生成报告文件:
public class ProjectStatsBuildWrapper extends BuildWrapper {
@Override
public Environment setUp(
AbstractBuild build,
Launcher launcher,
BuildListener listener) {
return new Environment() {
@Override
public boolean tearDown(
AbstractBuild build, BuildListener listener)
throws IOException, InterruptedException {
ProjectStats stats = buildStats(build.getWorkspace());
String report = generateReport(
build.getProject().getDisplayName(),
stats);
File artifactsDir = build.getArtifactsDir();
String path = artifactsDir.getCanonicalPath() + REPORT_TEMPLATE_PATH;
File reportFile = new File(path);
// 将报告内容写入文件
}
};
}
}
5. 使用指南
5.1 安装插件
构建插件并复制到 Jenkins:
mvn install
cp ./target/jenkins-hello-world.hpi ~/.jenkins/plugins/
重启 Jenkins 后验证安装:
- 访问 http://localhost:8080
- 进入 Manage Jenkins → Manage Plugins → Installed
- 确认插件列表中存在我们的插件
5.2 配置任务
创建新任务并配置 Git 仓库(以 commons-lang 为例):
启用我们的插件:
5.3 查看结果
执行构建后,在构建产物中找到 stats.html
:
打开报告文件:
结果符合预期:1 个类,3 行代码。
6. 总结
本文完整演示了 Jenkins 插件开发流程,从环境搭建到最终验证。虽然未覆盖所有高级特性,但提供了核心设计思路和基础实现。完整代码请参考 GitHub 仓库。