搭建開發環境
為了能開發插件,開發環境需要安裝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

















