插件开发入门 #

本节介绍Jenkins插件开发的基本概念,帮助了解如何创建自定义插件。

开发环境准备 #

前提条件 #

  • JDK 11或更高版本
  • Maven 3.6+
  • IDE(推荐IntelliJ IDEA)

Maven配置 #

xml
<!-- ~/.m2/settings.xml -->
<settings>
    <pluginGroups>
        <pluginGroup>org.jenkins-ci.tools</pluginGroup>
    </pluginGroups>
    
    <profiles>
        <profile>
            <id>jenkins</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <repositories>
                <repository>
                    <id>repo.jenkins-ci.org</id>
                    <url>https://repo.jenkins-ci.org/public/</url>
                </repository>
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>repo.jenkins-ci.org</id>
                    <url>https://repo.jenkins-ci.org/public/</url>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>
</settings>

创建插件项目 #

使用原型 #

bash
mvn archetype:generate \
  -Dfilter=io.jenkins.archetypes:

选择原型类型:

text
1. empty-plugin - 空插件模板
2. global-configuration-plugin - 全局配置插件
3. hello-world-plugin - Hello World示例

项目结构 #

text
my-plugin/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── io/jenkins/plugins/sample/
│   │   │       ├── SamplePlugin.java
│   │   │       └── SampleBuilder.java
│   │   └── resources/
│   │       ├── io/jenkins/plugins/sample/
│   │       │   └── SampleBuilder/
│   │       │       ├── config.jelly
│   │       │       └── help.html
│   │       └── index.jelly
│   └── test/
│       └── java/
├── pom.xml
└── README.md

pom.xml配置 #

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.jenkins-ci.plugins</groupId>
        <artifactId>plugin</artifactId>
        <version>4.x</version>
    </parent>
    
    <groupId>io.jenkins.plugins</groupId>
    <artifactId>my-plugin</artifactId>
    <version>1.0.0</version>
    <packaging>hpi</packaging>
    
    <name>My Plugin</name>
    <description>A sample Jenkins plugin</description>
    
    <properties>
        <jenkins.version>2.400</jenkins.version>
        <java.level>11</java.level>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.jenkins-ci.plugins</groupId>
            <artifactId>structs</artifactId>
            <version>324.va_f5d6774f3a_d</version>
        </dependency>
    </dependencies>
    
    <repositories>
        <repository>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
        </repository>
    </repositories>
</project>

创建构建步骤 #

Builder类 #

java
package io.jenkins.plugins.sample;

import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import java.io.IOException;

public class SampleBuilder extends Builder {
    
    private final String name;
    
    @DataBoundConstructor
    public SampleBuilder(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    @Override
    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) 
            throws InterruptedException, IOException {
        listener.getLogger().println("Hello, " + name + "!");
        return true;
    }
    
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        
        public FormValidation doCheckName(@QueryParameter String value) {
            if (value.isEmpty()) {
                return FormValidation.error("Name is required");
            }
            return FormValidation.ok();
        }
        
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return true;
        }
        
        @Override
        public String getDisplayName() {
            return "Sample Builder";
        }
    }
}

Jelly配置页面 #

xml
<!-- src/main/resources/io/jenkins/plugins/sample/SampleBuilder/config.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
    <f:entry title="Name" field="name">
        <f:textbox />
    </f:entry>
</j:jelly>

帮助文档 #

html
<!-- src/main/resources/io/jenkins/plugins/sample/SampleBuilder/help-name.html -->
<div>
    Enter the name to greet.
</div>

创建全局配置 #

GlobalConfiguration类 #

java
package io.jenkins.plugins.sample;

import hudson.Extension;
import jenkins.model.GlobalConfiguration;
import org.kohsuke.stapler.DataBoundSetter;

@Extension
public class SampleGlobalConfiguration extends GlobalConfiguration {
    
    private String defaultName;
    
    public SampleGlobalConfiguration() {
        load();
    }
    
    public String getDefaultName() {
        return defaultName;
    }
    
    @DataBoundSetter
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
        save();
    }
}

全局配置Jelly #

xml
<!-- src/main/resources/io/jenkins/plugins/sample/SampleGlobalConfiguration/config.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
    <f:section title="Sample Plugin">
        <f:entry title="Default Name" field="defaultName">
            <f:textbox />
        </f:entry>
    </f:section>
</j:jelly>

构建和测试 #

本地运行 #

bash
mvn hpi:run

访问 http://localhost:8080/jenkins

打包插件 #

bash
mvn clean package

生成的 .hpi 文件在 target/ 目录。

安装测试 #

text
Manage Jenkins → Plugins → Advanced → Deploy Plugin
上传 .hpi 文件

编写测试 #

单元测试 #

java
package io.jenkins.plugins.sample;

import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class SampleBuilderTest {
    
    @Rule
    public JenkinsRule jenkins = new JenkinsRule();
    
    @Test
    public void testBuilder() throws Exception {
        FreeStyleProject project = jenkins.createFreeStyleProject();
        project.getBuildersList().add(new SampleBuilder("World"));
        
        FreeStyleBuild build = project.scheduleBuild2(0).get();
        jenkins.assertBuildStatus(Result.SUCCESS, build);
    }
}

插件发布 #

准备发布 #

  1. 在Jenkins Wiki创建插件页面
  2. 配置pom.xml中的scm信息
  3. 准备README.md

发布到Jenkins仓库 #

bash
mvn release:prepare release:perform

开发最佳实践 #

1. 遵循命名规范 #

text
类名: PascalCase
方法名: camelCase
常量: UPPER_SNAKE_CASE

2. 使用@DataBoundConstructor #

java
@DataBoundConstructor
public MyBuilder(String param1, boolean param2) {
    this.param1 = param1;
    this.param2 = param2;
}

3. 实现Descriptor #

java
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
    // 配置验证和显示名称
}

4. 编写测试 #

text
单元测试覆盖主要功能
集成测试验证完整流程

下一步学习 #

小结 #

  • 使用Maven原型创建插件项目
  • Builder类实现构建步骤
  • Jelly定义配置界面
  • 使用@DataBoundConstructor绑定参数
  • 编写单元测试验证功能
  • 发布到Jenkins插件仓库
最后更新:2026-03-28