转自: http://www.open-open.com/lib/view/open1341393917328.html
Jenkins插件开发指南
环境变量
为了能开发插件,开发环境需安装Maven和JDK 6.0以上版本
配置maven的settings.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<
settings
>
<
pluginGroups
>
<
pluginGroup
>org.jenkins-ci.tools</
pluginGroup
>
</
pluginGroups
>
<
profiles
>
<!-- Give access to Jenkins plugins -->
<
profile
>
<
id
>jenkins</
id
>
<
activation
>
<
activeByDefault
>true</
activeByDefault
>
<!-- change this to false, if you don't like to have it on per default -->
</
activation
>
<
repositories
>
<
repository
>
<
id
>repo.jenkins-ci.org</
id
>
<
url
>http://repo.jenkins-ci.org/public/</
url
>
</
repository
>
</
repositories
>
<
pluginRepositories
>
<
pluginRepository
>
<
id
>repo.jenkins-ci.org</
id
>
<
url
>http://repo.jenkins-ci.org/public/</
url
>
</
pluginRepository
>
</
pluginRepositories
>
</
profile
>
</
profiles
>
</
settings
>
|
创建新的插件
创建插件之前需运行以下Maven命令:
1
|
mvn -cpu hpi:create
|
该操作需要你输入一些参数,比如说groupid,artifactid。之后会创建一个新的插件模板便于开发者之后的开发工作。确保你可以使用一下命令:
1
2
|
cd
newly-create-directory
mvn package
|
设置Eclipse开发环境
1
|
mvn -DdownloadSources=
true
-DdownloadJavadocs=
true
-DoutputDirectory=target
/eclipse-classes
eclipse:eclipse
|
或者 使用m2eclipse插件在Eclipse打开即可
插件目录结构
pom.xml:Maven的构建配置文件
src/main/java:Java源文件目录
src/main/resources:插件Jelly/Grovy视图
src/main/webapps:插件的静态资源如images和html文件
插件调试
插件开发中在使用一下命令对插件进行调试
Windows
1
2
|
set
MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
mvn hpi:run
|
Linux
1
2
|
$
export
MAVEN_OPTS=
"-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n"
$ mvn hpi:run
|
改变端口
1
|
mvn hpi:run -Djetty.port=8090
|
设置comtext path
1
|
mvn hpi:run -Dhpi.prefix=
/jenkins
|
插件发布
1
|
mvn package
|
源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
<b>
import
hudson.Launcher;
import
hudson.Extension;
import
hudson.util.FormValidation;
import
hudson.model.AbstractBuild;
import
hudson.model.BuildListener;
import
hudson.model.AbstractProject;
import
hudson.tasks.Builder;
import
hudson.tasks.BuildStepDescriptor;
import
net.sf.json.JSONObject;
import
org.kohsuke.stapler.DataBoundConstructor;
import
org.kohsuke.stapler.StaplerRequest;
import
org.kohsuke.stapler.QueryParameter;
import
javax.servlet.ServletException;
import
java.io.IOException;
/**
* Sample {@link Builder}.
*
* <p>
* When the user configures the project and enables this builder,
* {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked
* and a new {@link HelloWorldBuilder} is created. The created
* instance is persisted to the project configuration XML by using
* XStream, so this allows you to use instance fields (like {@link #name})
* to remember the configuration.
*
* <p>
* When a build is performed, the {@link #perform(AbstractBuild, Launcher, BuildListener)}
* method will be invoked.
*/
public
class
HelloWorldBuilder
extends
Builder {
private
final
String name;
// Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
@DataBoundConstructor
public
HelloWorldBuilder(String name) {
this
.name = name;
}
/**
* We'll use this from the <tt>config.jelly</tt>.
*/
public
String getName() {
return
name;
}
@Override
public
boolean
perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
// This is where you 'build' the project.
// Since this is a dummy, we just say 'hello world' and call that a build.
// This also shows how you can consult the global configuration of the builder
if
(getDescriptor().getUseFrench())
listener.getLogger().println(
"Bonjour, "
+name+
"!"
);
else
listener.getLogger().println(
"Hello, "
+name+
"!"
);
return
true
;
}
// Overridden for better type safety.
// If your plugin doesn't really define any property on Descriptor,
// you don't have to do this.
@Override
public
DescriptorImpl getDescriptor() {
return
(DescriptorImpl)
super
.getDescriptor();
}
/**
* Descriptor for {@link HelloWorldBuilder}. Used as a singleton.
* The class is marked as public so that it can be accessed from views.
*
* <p>
* See <tt>src/main/resources/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt>
* for the actual HTML fragment for the configuration screen.
*/
@Extension
// This indicates to Jenkins that this is an implementation of an extension point.
public
static
final
class
DescriptorImpl
extends
BuildStepDescriptor<Builder> {
/**
* To persist global configuration information,
* simply store it in a field and call save().
*
* <p>
* If you don't want fields to be persisted, use <tt>transient</tt>.
*/
private
boolean
useFrench;
/**
* Performs on-the-fly validation of the form field 'name'.
*
* @param value
* This parameter receives the value that the user has typed.
* @return
* Indicates the outcome of the validation. This is sent to the browser.
*/
public
FormValidation doCheckName(
@QueryParameter
String value)
throws
IOException, ServletException {
if
(value.length() ==
0
)
return
FormValidation.error(
"Please set a name"
);
if
(value.length() <
4
)
return
FormValidation.warning(
"Isn't the name too short?"
);
return
FormValidation.ok();
}
public
boolean
isApplicable(Class<?
extends
AbstractProject> aClass) {
// Indicates that this builder can be used with all kinds of project types
return
true
;
}
/**
* This human readable name is used in the configuration screen.
*/
public
String getDisplayName() {
return
"Say hello world"
;
}
@Override
public
boolean
configure(StaplerRequest req, JSONObject formData)
throws
FormException {
// To persist global configuration information,
// set that to properties and call save().
useFrench = formData.getBoolean(
"useFrench"
);
// ^Can also use req.bindJSON(this, formData);
// (easier when there are many fields; need set* methods for this, like setUseFrench)
save();
return
super
.configure(req,formData);
}
/**
* This method returns true if the global configuration says we should speak French.
*
* The method name is bit awkward because global.jelly calls this method to determine
* the initial state of the checkbox by the naming convention.
*/
public
boolean
getUseFrench() {
return
useFrench;
}
}
}</b>
|
这里主要使用了jenkins的Builder作为扩展点,通过内部类DescripotorImpl添加@Extension声明,告诉系统该内部类是作为BuildStepDescriptor的扩展出现
这里基本完成了扩展点的后台代码部分,但是扩展过程中还需要对前端页面进行扩张,这时就需要建立一个pcakage放置该扩展类对应的视图
视图有三种:1,全局配置(global.jelly)2,Job配置(config.jeely),还有就是使用帮助(help-字段名).html
global.jeely(对于插件需要使用的全局配置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<
j:jelly
xmlns:j
=
"jelly:core"
xmlns:st
=
"jelly:stapler"
xmlns:d
=
"jelly:define"
xmlns:l
=
"/lib/layout"
xmlns:t
=
"/lib/hudson"
xmlns:f
=
"/lib/form"
>
<!--
This Jelly script is used to produce the global configuration option.
Jenkins uses a set of tag libraries to provide uniformity in forms.
To determine where this tag is defined, first check the namespace URI,
and then look under $JENKINS/views/. For example, <f:section> is defined
in $JENKINS/views/lib/form/section.jelly.
It's also often useful to just check other similar scripts to see what
tags they use. Views are always organized according to its owner class,
so it should be straightforward to find them.
-->
<
f:section
title
=
"Hello World Builder"
>
<
f:entry
title
=
"French"
field
=
"useFrench"
description
=
"Check if we should say hello in French"
>
<
f:checkbox
/>
</
f:entry
>
</
f:section
>
</
j:jelly
>
|
将插件部署到Jenkins后实际效果如下图(系统管理-系统设置)
config.jeely(正对每个Job而言需要的配置信息)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
j:jelly
xmlns:j
=
"jelly:core"
xmlns:st
=
"jelly:stapler"
xmlns:d
=
"jelly:define"
xmlns:l
=
"/lib/layout"
xmlns:t
=
"/lib/hudson"
xmlns:f
=
"/lib/form"
>
<!--
This jelly script is used for per-project configuration.
See global.jelly for a general discussion about jelly script.
-->
<!--
Creates a text field that shows the value of the "name" property.
When submitted, it will be passed to the corresponding constructor parameter.
-->
<
f:entry
title
=
"Name"
field
=
"name"
>
<
f:textbox
/>
</
f:entry
>
</
j:jelly
>
|
部署到jenkins后的实际效果
这里细心的人可能已经看出来了,config.jelly中定义的字段实际就是扩展类中构造函数的参数,对于HelloWorldBuilder类而言自成了Builder父类,通过使用@DataBoundConstructor申明,当用户在界面填写配置信息点击保存后将自动初始化该类,同时会在对应的job的配置文件中保存相关信息本机是在.jenkins\jobs\TestJob目录下的config.xml文件中点击查看文件,在publishers节点下即可看见与该插件有关的信息
1
2
3
4
5
|
<
prebuilders
>
<
org.wocloud.jenkins.manager.HelloWorldBuilder
>
<
name
>Hello!!!!!!!!!!</
name
>
</
org.wocloud.jenkins.manager.HelloWorldBuilder
>
</
prebuilders
>
|
在Job进行构建时,将会激活HelloWorldBuilder类的perform方法,而该方法中就是你插件真正开始完成工作的地方
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
listener是此次构建工作的监听器
通过该listener可以输出内容信息到前端jenkins页面
使用build可以判断当前构建工作的结果