转载原文:http://my.oschina.net/songcser/blog/159170
搭建开发环境
为了能开发插件,开发环境需要安装Maven和JDK 6.0以上版本。
1、安装JDK
打开cmd, 输入java –version
如下图,说明安装完成。
图1
如果没安装,点击链接http://jingyan.baidu.com/article/bea41d435bc695b4c41be648.html
2、安装Maven
1. 下载Maven http://maven.apache.org/download.html
如下图:
图2
将安装包解压到任意地址,我的路径是D:\apache-maven-3.0.5
新建环境变量M2_HOME 指向D:\apache-maven-3.0.5
在path添加路径%M2_HOME%\bin
打开cmd 输入mvn –v,
如下图:
图3
安装成功。
给eclipse安装插件m2eclipse。
1、 打开eclipse
2、 Help-Install New Software出现下图:
图4
3、 点击Add
图5
在name输入 m2e
在Location输入 http://m2eclipse.sonatype.org/sites/m2e
4、 确定后出现下图:
图6
5、 勾选Maven Integration for Eclipse
6、 然后一直下一步直到安装完毕
7、 检查是否安装成功
(1) 点击Help – about eclipse – installation details,看是否存在Maven Integration for Eclipse(Required),如下图:
图7
(2) 再检查eclipse是否可以创建Maven项目了,File->New->Other
图8
到此Maven安装完成了。
3、安装jenkins
下载jenkins 链接http://jenkins-ci.org/
将jenkins.war,拷贝到D:\jenkins下,打开cmd,转到D:\jenkins目录下
然后运行java –jar jenkins.war
最后出现jenkins is fully up an running。说明安装成功。
访问http://localhost:8080界面如下图:
图9
插件开发流程
1、设置环境
由于是使用maven进行开发,需要对%USERPROFILE%\.m2\settings.xml(USERPROFILE为用户名路径如C:\Documents and Settings下的用户)文件添加以下内容:
<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>
<mirrors>
<mirror>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
<mirrorOf>m.g.o-public</mirrorOf>
</mirror>
</mirrors>
这将可以使用缩短的命令来执行运行。
2、生成新的插件
开发新的插件,执行以下命令:
mvn –U hpi:create
将会提示出现需要输入groupid和artifactid,如下:
groupid:com.jysong.jenkins
artifactid: newplugin
这样便生成了新的插件,会生成一个简单的例子,同时在当前目录下生成新的文件夹newplugin,然后再执行下面的命令。
cd newplugin
mvn package
使用这个命令将工程进行打包,不过由于版本的不同可能会出现错误。如果出现错误参考下面的源代码部分进行修改。
在第一次执行命令时会下载很多的文件,需要耐心的等待。
3、编译插件
mvn install
运行此命令将会生成文件 ./target/newplugin.hpi。可以把它加载到jenkins中。并且将./target/newplugin.hpi、pom.xml、./target/newplugin.jar这几个文件安装到maven的本地仓库中,就可以被其他的工程调用了。
也可以使用mvn package,只是进行打包生成所需文件,并不安装到本地仓库中。
4、为IDE设置开发环境
使用eclipse进行代码开发。
mvn -DdownloadSources=true -DdownloadJavadocs=true –DoutputDirectory= target/eclipse-classes eclipse:eclipse或者mvn eclipse:eclipse
在此目录中生成eclipse工程,可以使用eclipse将工程进行导入。如下图:
图10
5、工作空间布局
导入之后目录结构如下图:
图11
src/main/java :存放java源文件。
src/main/resources:jelly/Groovy视图文件。
src/main/webapp:静态资源文件,例如图片,HTML文件。
pom.xml:配置文件,Maven使用此文件编译插件。
6、源代码
在src/main/java/com/jysong/jenkins目录下可能有个null文件夹,在文件夹下面有HelloWorldBuilder.java文件,将HelloWorldBuilder.java文件拷贝到jenkins文件夹下面,将null文件夹删除。并且将HelloWorldBuilder.java文件中的第一行的package最后面的.null删除。HelloWorldBuilder.java文件是一个开发插件的模板,包含了开发一个简单插件的所有内容。后面将对这个文件代码进行详细分析。
在src/main/resources/com/jysong/jenkins目录下可能有个null文件夹,在文件夹下面有个HelloWorldBuilder文件夹,将HelloWorldBuilder文件夹拷贝到jenkins文件夹下面,将null文件夹删除。在HelloWorldBuilder文件夹下面有global.jelly和config.jelly配置文件。这两个文件是进行页面配置的文件。
7、调试插件
在windows系统上,执行以下命令:
set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket, server=y, address =8000, suspend=n
mvn hpi:run
使用http://localhost:8080/ 在浏览器中登录,将会看到jenkins页在Jetty中运行。MAVEN_OPTS启动了端口为8000的调试器,可以在这个端口开启一个调试会话。
如果8080的端口被占用,将会出现错误,不会运行jetty服务器。可以更改端口使用以下命令:
mvn hpi:run –Djetty.port=8090
可以使用http://localhost:8090/进行登录了。
设置上下文路径
mvn hpi:run –Dhpi.prefix=/Jenkins
执行这个命令之后登录地址将变为http://localhost:8090/jenkins
8、发布插件
运行以下命令,生成你的插件的图片。
mvn package
生成 ./target/*.hpi文件,其他使用者可以使用jenkins的web界面上传到jenkins。
9、安装插件
在jenkins的web界面中由
Manage Jenkins>Manage Plugins>Advanced
图12
点击Choose File,选择你的插件的target目录下的hpi文件。选择之后点击Upload,插件就会配置到jenkins中。
到此一个简单的插件开发完成了,可以在此基础上进行更复杂的开发。详细开发插件流程的地址https://wiki.jenkins-ci.org/display/JENKINS/Plugin+tutorial
源码分析
1、java源代码
在目录src/main/java/com/jysong/jenkins下有文件HelloWorldBuilder.java。代码如下:
1
public
class
HelloWorldBuilder
extends
Builder {
2
private
final
String name;
3
4
//
Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
5
@DataBoundConstructor
6
public
HelloWorldBuilder(String name) {
7
this
.name
=
name;
8
}
9
/**
10
* We'll use this from the <tt>config.jelly</tt>.
11
*/
12
public
String getName() {
13
return
name;
14
}
15
16
@Override
17
public
boolean
perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
18
//
This is where you 'build' the project.
19
//
Since this is a dummy, we just say 'hello world' and call that a build.
20
//
This also shows how you can consult the global configuration of the builder
21
if
(getDescriptor().getUseFrench())
22
listener.getLogger().println(
"
Bonjour,
"
+
name
+
"
!
"
);
23
else
24
listener.getLogger().println(
"
Hello,
"
+
name
+
"
!
"
);
25
26
return
true
;
27
}
28
29
//
Overridden for better type safety.
30
//
If your plugin doesn't really define any property on Descriptor,
31
//
you don't have to do this.
32
33
@Override
34
public
DescriptorImpl getDescriptor() {
35
return
(DescriptorImpl)
super
.getDescriptor();
36
}
37
38
/**
39
* Descriptor for {
@link
HelloWorldBuilder}. Used as a singleton.
40
* The class is marked as public so that it can be accessed from views.
41
*
42
* <p>
43
* See <tt>src/main/resources/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt>
44
* for the actual HTML fragment for the configuration screen.
45
*/
46
47
@Extension
//
This indicates to Jenkins that this is an implementation of an extension point.
48
public
static
final
class
DescriptorImpl
extends
BuildStepDescriptor
<
Builder
>
{
49
/**
50
* To persist global configuration information,
51
* simply store it in a field and call save().
52
*
53
* <p>
54
* If you don't want fields to be persisted, use <tt>transient</tt>.
55
*/
56
private
boolean
useFrench;
57
/**
58
* Performs on-the-fly validation of the form field 'name'.
59
*
60
*
@param
value
61
* This parameter receives the value that the user has typed.
62
*
@return
63
* Indicates the outcome of the validation. This is sent to the browser.
64
*/
65
public
FormValidation doCheckName(@QueryParameter String value)
66
throws
IOException, ServletException {
67
if
(value.length()
==
0
)
68
return
FormValidation.error(
"
Please set a name
"
);
69
if
(value.length()
<
4
)
70
return
FormValidation.warning(
"
Isn't the name too short?
"
);
71
return
FormValidation.ok();
72
}
73
public
boolean
isApplicable(Class
<?
extends
AbstractProject
>
aClass) {
74
//
Indicates that this builder can be used with all kinds of project types
75
return
true
;
76
}
77
/**
78
* This human readable name is used in the configuration screen.
79
*/
80
public
String getDisplayName() {
81
return
"
Say hello world
"
;
82
}
83
84
@Override
85
public
boolean
configure(StaplerRequest req, JSONObject formData)
throws
FormException {
86
//
To persist global configuration information,
87
//
set that to properties and call save().
88
useFrench
=
formData.getBoolean(
"
useFrench
"
);
89
//
^Can also use req.bindJSON(this, formData);
90
//
(easier when there are many fields; need set* methods for this, like setUseFrench)
91
save();
92
return
super
.configure(req,formData);
93
}
94
95
/**
96
* This method returns true if the global configuration says we should speak French.
97
*
98
* The method name is bit awkward because global.jelly calls this method to determine
99
* the initial state of the checkbox by the naming convention.
100
*/
101
102
public
boolean
getUseFrench() {
103
return
useFrench;
104
}
105
}
106
}
107
108
这里主要使用了jenkins的Builder作为扩展点,Builder扩展点是编译时的功能。更多扩展点https://wiki.jenkins-ci.org/display/JENKINS/Extension+points。
HelloWorldBuilder类中的构造函数使用@DataBoundConstructor来声明。构造函数要对变量进行赋值。
HelloWorldBuilder类中perform重载函数。构建的执行通过实现perform方法来进行自定义。每次执行编译时都会运行perform函数。它有三个参数:
Build参数是描述了当前任务的一次构建,通过它可以访问到一些比较重要的模型对象如:project当前项目的对象、workspace构建的工作空间、Result当前构建步骤的结果。
Launcher参数用于启动构建。
BuildListener该接口用于检查构建过程的状态(开始、失败、成功..),通过它可以在构建过程中发送一些控制台信息给jenkins。
perform方法的返回值告诉jenkins当前步骤是否成功,如果失败了jenkins将放弃后续的步骤。
在这个例子中if..else..语句是向控制台端输出日志信息,其中name的信息由构造函数有关。
将if..else..语句进行删除,添加以下代码。
1 int number = build.getNumber(); 2 3 String version = build.getHudsonVersion(); 4 5 Calendar startedTime = build.getTimestamp(); 6 7 SimpleDateFormat simpleDateFormat = new SimpleDateFormat ( " yyyy-MM-dd'T'HH:mm:ss.SSSZ " ); 8 9 String started = simpleDateFormat.format (startedTime.getTime()); 10 11 String durationMillis = build.getDuration(); 12 13 String log = build.getLog(); 14 15 String fileName = " D:\\workspace\\newplugin\\BuildLog " + number + " .txt " ; 16 17 String content; 18 19 content = version + " \n\t " 20 + name + " \n\t " 21 + started + " \n\t " 22 + durationMillis + " \n\t " 23 + log; 24 25 try { 26 FileWriter writer = new FileWriter(fileName, true ); 27 writer.write(content); 28 writer.close(); 29 } catch (IOException e) 30 { 31 e.printStackTrace(); 32 } 33 34
这段代码是获得编译时的一些信息,然后输出到一个文本文件中。其中AbstractBuild类包含了编译的大部分的信息,可以查看API获得更详细的信息 http://javadoc.jenkins-ci.org/ Hudson.model.AbstractBuild。
此外有一个内部静态类DescriptorImpl,该类通过@Extension声明告诉jenkins,告诉系统该内部类是作为BuildStepDescriptor的扩展出现。有以下几个方法。
isApplicable方法,是否对所有项目类型可用。
其他的方法和配置文件有关,在下面介绍配置文件时在详细说明。
2、视图配置文件
Jenkins使用了Jelly页面渲染技术,这是一个基于XML的服务端页面渲染引擎,其将基于Jelly的xml标签转换为对应的Html标签并输出到客户端。模型对象的信息通过Jexl表达式被传递到页面上(相当于Jsp的JSTL)。jelly文件以.jelly为后缀,在hudson中使用类全名的形式来查找模型类对应的jelly页面文件,如名为src/main/java/com/jysong/jenkins/HelloWorldBuilder.java的类,其对应的页面文件应该存在于src/main/resources/com/jysong/jenkins/HelloWorldBuilder目录的下。
此外hudson通过固定的命名方式来确定页面文件属于局部配置还是全局配置:config.jelly提供局部配置;global.jelly提供全局配置。config.jelly是具体的某个job的配置,global.jelly是指jenkins的系统配置。
视图有三种:1,全局配置(global.jelly)2,Job配置(config.jelly),还有就是使用帮助(help-字段名).html
1、全局配置详解
global.jelly为全局配置页面。
1
<
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
"
>
2
3
<!--
4
5
This Jelly script is used to produce the global configuration option.
6
7
Jenkins uses a set of tag libraries to provide uniformity in forms.
8
9
To determine where
this
tag is defined, first check the namespace URI,
10
11
and then look under $JENKINS
/
views
/
. For example,
<
f:section
>
is defined
12
13
in $JENKINS
/
views
/
lib
/
form
/
section.jelly.
14
15
It
'
s also often useful to just check other similar scripts to see what
16
17
tags they use. Views are always organized according to its owner
class
,
18
19
so it should be straightforward to find them.
20
21
-->
22
23
<
f:section title
=
"
Hello World Builder
"
>
24
25
<
f:entry title
=
"
French
"
field
=
"
useFrench
"
26
27
description
=
"
Check if we should say hello in French
"
>
28
29
<
f:checkbox
/>
30
31
</
f:entry
>
32
33
</
f:section
>
34
35
</
j:jelly
>
36
37
其中title为标题,表示要显示的内容。field为将调用DescriptorImpl内部类的方法getUseFrench(),field域会将方法去掉get并且将第一个字母小写,找到相对应的方法。description将显示描述信息。f:checkbox为复选框控件。
在每次保存全局配置时,jenkins都会调用该descriptor对象,并调用其configure方法,可以实现该方法并提供自己的定制
在DescriptorImpl中的configure方法中,可以对全局配置进行操作。save方法用于将当前Descriptor所提供的配置持久化(通过get**方法),为了使save能正常工作,需要提供配置项的get方法。
2、局部配置详解
config.jelly 的内容将被包含在扩展功能的配置中,在HelloWorldBuilder文件夹下面的配置内容是:
1
<
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
"
>
2
3
<!--
4
5
This jelly script is used
for
per
-
project configuration.
6
7
See global.jelly
for
a general discussion about jelly script.
8
9
-->
10
11
<!--
12
13
Creates a text field that shows the value of the
"
name
"
property.
14
15
When submitted, it will be passed to the corresponding constructor parameter.
16
17
-->
18
19
<
f:entry title
=
"
Name
"
field
=
"
name
"
>
20
21
<
f:textbox
/>
22
23
</
f:entry
>
24
25
</
j:jelly
>
26
27
其中entry 表示用于交互的html表单域,title将作为表单域label的值,在界面中要显示的内容。 field表示的是HelloWorldBuilder的构造函数中的参数。如果有多个field,就要有多个相对应的参数。textbox 表示简单的渲染一个输入文本,输入的值将赋给name。
将插件部署到jenkins后实际效果如下图:
图13
选择Say hello world,
图14
Say hello world是由于在DescriptorImpl内部类中有方法getDisplayName(),此方法返回的字符串作为pre-build step的名称。
在DescriptorImpl内部类中doCheckName(@QueryParameter String value)方法,在光标不再在输入框时,将执行这个方法,其中输入框的输入值以value值传入,在这个函数里可以进行验证,是否符合输入条件。
允许为表单域增加帮助说明(在页面上对应于文本框后面出现问号按钮,一点击可出现提示):
在同名目录下有help-{fileName}.html,在该文件中添加帮助内容;帮助内容允许是动态的,即可以从模型中拉取信息进行显示,这需要将html后缀改为jelly,而help-name.html文件的形式大致如下:
1
<
div
>
2
3
Help file
for
fields are discovered through a file name convention. This file is
4
5
help
for
the
"
name
"
field. You can have
<
i
>
arbitrary
</
i
>
HTML here. You can write
6
7
this
file as a Jelly script
if
you need a dynamic content (but
if
you
do
so, change
8
9
the extension to
<
tt
>
.jelly
</
tt
>
).
10
11
</
div
>
12
13
在输入框中输入相对目录。
测试案例
在此基础之上,实现一个简单的案例。
1、编写代码
在HelloWorldBuilder类中的perform方法中,添加代码:
1
FilePath workspace
=
build.getWorkspace();
2
3
String filePath
=
workspace.toString()
+
"
\\
"
+
path;
4
5
File files
=
new
File(filePath);
6
7
File[] fileList
=
files.listFiles();
8
9
String strFile
=
""
;
10
for
(
int
i
=
0
;i
<
fileList.length;i
++
)
11
{
12
strFile
+=
fileList[i].getName()
+
"
\n
"
;
13
}
14
String fileName
=
"
D:\\workspace\\newplugin\\FilePath
"
+
number
+
"
.txt
"
;
15
try
16
{
17
FileWriter writer
=
new
FileWriter(fileName,
true
);
18
writer.write(strFile);
19
writer.close();
20
}
catch
(IOException e)
21
{
22
e.printStackTrace();
23
}
24
25
以上代码是为了获得job的workspace,然后根据传入的name,获取目录下面的文件名,最后写入到一个文本文件中。
打开cmd,转到工程目录下,执行
mvn package
2、准备编译工作
将jetty服务器启动,使用端口8090
在浏览器中打开http://loaclhost:8090,新建一个job。点击运行。
在工程目录下面有个work文件夹,进入work文件夹,下面有个jobs文件夹。在jobs文件夹下面存放着所有的job,进入刚新建的job,下面有builds文件夹和workspace文件夹。其中builds文件夹下面存放着每次编译产生的所有的信息文件。workspace文件夹下面存放着要进行编译的工程文件和代码文件。
在workspace目录下使用VS2010新建一个工程,HelloTest。
3、配置jenkins
在jenkins中点击新建的工程,然后点击configure,在Build下Add build step
图15
选择Execute Windows batch command,输入以下
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" "\HelloTest\HelloTest.sln"
如下图:
图16
在点击Add build step,选择Say hello work在下面输入:
HelloTest\Debug
如下图:
图17
点击保存。
4、进行编译
点击 进行编译,编译完成之后就会在工程目录下面生成两个文件,一个文件中存放着编译的信息,一个文件中存放着workspace目录下工程Debug编译生成的文件名。
参考资料
https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins
http://my.oschina.net/fhck/blog/64639
http://blog.csdn.net/littleatp2008/article/details/7001793
http://javaadventure.blogspot.jp/2008/01/writing-hudson-plug-in-part-1.html
https://github.com/jenkinsci/artifactory-plugin