Hudson外掛開發入門體驗
持續整合(CI)將軟體專案流程的各個階段進行自動化部署,從build, deploy, test automation到coverage分析,全部實現自動完成,而不需要每天的手工操作。
在敏捷開發過程中,持續整合大大提高了團隊的工作效率,開發和測試人員可以專注於程式碼與測試用例的編寫,而不需要過多關注編譯和部署。每天夜晚進行持續整合的自動化部署,第二天可以馬上開始測試和分析前日的測試效果與程式碼覆蓋率,和敏捷開發的理念結合的恰到好處。
下面我就來介紹下如何開發一個Hudson的外掛。
首先你要有Maven 2和JDK1.6以上,這是必須的。然後在你的Maven 2的setting.xml 檔案中加入下列程式碼
- <pluginGroups>
- <pluginGroup>org.jvnet.hudson.tools</pluginGroup>
- </pluginGroups>
- <profiles>
- <profile>
- <id>hudson</id>
- <activation>
- <activeByDefault/>
- </activation>
- <pluginRepositories>
- <
- <id>m.g.o-public</id>
- <url>http://maven.glassfish.org/content/groups/public/</url>
- </pluginRepository>
- </pluginRepositories>
- <repositories>
- <repository>
- <id>m.g.o-public</id>
- <url>http://maven.glassfish.org/content/groups/public/
- </repository>
- </repositories>
- </profile>
- </profiles>
- <activeProfiles>
- <activeProfile>hudson</activeProfile>
- </activeProfiles>
這樣會將你的Maven指向有著Hudson-related Maven plugins的倉庫,而且允許你使用Hudson Maven plugins的短名字來呼叫相關的外掛(例如:hpi:create 代替org.jvnet.hudson.tools:maven-hpi-plugin:1.23:create)。
接著在CMD中輸入 [plain] view plaincopyprint?
- mvn hpi:create
之後會問你一些如groupId和artifactId之類的問題,groupId填寫成你一般開發java程式碼的package資訊,例如com.webex.slim.hudsonplugin,artifactId則是你編寫此hudson外掛的名稱,例如buildslim。
完成後計算機會自動的建立了一個專案,裡面有一些模板程式碼,可供你學習如何開始寫一個Hudson的外掛,後面的程式碼全部來自模版程式碼。如果你需要在Eclipse裡編輯外掛可以執行
- mvn -DdownloadSources=true eclipse:eclipse
執行前面的maven命令後,在我們的指定目錄下已經生成了一個Hudson 外掛的專案資料夾,這個目錄應該是~/artifactId/。在Eclipse中匯入這個專案,我們可以看見專案有如下的結構:
- + src
- + main
- + java
- + full.package.name
- +- HelloWorldBuilder.java
- +resources
- + full.package.name
- +- config.jelly
- +- global.jelly
- +- index.jelly
- + webapp
- +- help-globalConfig.html
- +- help-projectConfig.html
HelloWorldBuilder.java
這個類就是具體實現某一擴充套件點的一個類,在這裡由於要擴充套件Builder這個擴充套件點,所以繼承了 Builder 這個類。在Hudson 中有很多不同種類的擴充套件點,比如Publisher、Recorder 等等。詳細的說明可以參考Hudson 的網站。
建好工程後,已經有一些程式碼是maven自動生成的hudson外掛示例程式碼。
下面我來逐步分析這些程式碼
- @DataBoundConstructor
- public HelloWorldBuilder(String name) {
- this.name = name;
- }
- /**
- * We'll use this from the <tt>config.jelly</tt>.
- */
- public String getName() {
- return name;
- }
這段程式碼用於構造這個Bulider並且從相應的config.jelly中獲取相應的引數。Hudson使用了一種叫structured form submission的技術,使得可以使用這種方式活動相應的引數。
[java] view plaincopyprint?- publicboolean perform(Build 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(DESCRIPTOR.useFrench())
- listener.getLogger().println("Bonjour, "+name+"!");
- else
- listener.getLogger().println("Hello, "+name+"!");
- returntrue;
- }
方法perform()是個很重要的方法,當外掛執行的的時候這個方法會被呼叫。相應的業務邏輯也可以在這裡實現。比如這個perform()方法就實現了怎麼說 “Hello”
接下來,在HelloBuilder 這個類裡面有一個叫 DescriptorImpl 的內部類,它繼承了Descriptor。在Hudson 的官方說明文件裡說Descriptor包含了一個配置例項的元資料。打個比方,我們在工程配置那裡對外掛進行了配置,這樣就相當於建立了一個插腳的例項,這時候就需要一個類來儲存外掛的配置資料,這個類就是Descriptor。
- public String getDisplayName() {
- return"Say hello world";
- }
- publicboolean configure(StaplerRequest req, JSONObject o) throws FormException {
- // to persist global configuration information,
- // set that to properties and call save().
- useFrench = o.getBoolean("useFrench");
- save();
- returnsuper.configure(req);
- }
注意點:
HUDSON_HOME:
Hudson需要一個位置來進行每次構建,保留相關的配置資訊,以及儲存測試的結果,這就是在部署好了Hudson環境以後,系統就會自動在當前使用者下新建一個.hudson,在linux下如:~/.hudson,我們有三種方式來改變這個路徑:
1. 在啟動servlet容器之前,設定:“HUDSON_HOME”環境變數,指向你需要設定的目錄
2. 在servlet容器之中,設定系統屬性
3. 設定一個JNDI的環境實體<env-entry>“HUDSON_HOME”指向您所想要設定的目錄
目前我們在glassfish中設定jvm-option的方式屬於第二種。
當我們設定好這個變數以後想要換個目錄,但又不想丟掉以前的配置怎麼辦,很簡單,關閉Hudson,將目錄A的內容拷貝的目錄B中去,然後重新設定“HUDSON_HOME”的值,然後重啟,你會發現之前你所作的所有配置都完好的保留了下來
1、Hudson-home的目錄結構:
HUDSON_HOME
+- config.xml (hudson的基本配置檔案,如:jdk的安裝路徑)
+- *.xml (其他系統相關的配置檔案,比如:旺旺外掛的全域性配置資訊)
+- fingerprints (儲存檔案版本跟蹤記錄資訊)
+- plugins (存放外掛)
+- jobs
+- [JOBNAME] (任務名稱,對應頁面上的project name)
+- config.xml (任務配置檔案,類似於CC的config.xml中各個專案的配置)
+- workspace (SCM所用到的目錄,hudson所下載程式碼預設存放在這個目錄)
+- builds
+- [BUILD_ID] (每一次構建的序號)
+- build.xml (構建結果彙總)
+- log (執行日誌)
+- changelog.xml (SCM修改日誌)
小提示:如果你使用了e-mail來接受測試訊息,並且hudson的遷移設計到不同ip地址機器的遷移的話,可能需要去Hudson的主配置中修改一下Hudson的訪問地址
workspace:
剛才在hudson-home的目錄結構中已經看到了workspce,假設當前hudson-home為/home/hudson-home,那麼當我們在hudson上配置一個專案demo的時候,就會在新建一個目錄/home/hudson-home/demo,在第一次執行之前,在jobs下並沒有demo這個資料夾,只有當第一次執行以後才會在jobs目錄下建立demo目錄,當代碼順利從svn上下載下來時才會建立workspace資料夾,所有從svn下載下來的程式碼都會存放在這個目錄下。
1、相對路徑:
專案配置過程中,Hudson使用的是相對路徑,對於Hudson,在我們新建一個專案比如demo後,假設workspace的目錄結構為:
workspace
+- demo
+- pom.xml
+- src
那麼測試報告的路徑就為demo/target/surefire-reports/*.xml,系統會自動去當前專案的workspace中去尋找這個路徑
mvn package -- 完成程式碼開發之後執行,按照pom.xml 中的配置資訊將會打包為hpi 格式的外掛檔案,這個就是你最終可以拿來上傳給你的hudson 平臺的玩意
mvn hpi:run -- 在本地的Jetty 中執行你的hudson 外掛,除錯專用,當然可以使用Debug 模式,執行之後,在本地訪問http://localhost:8080/ 即可見(注意不要佔用8080 埠)
mvnDebug hup:run ,debug除錯模式
下面貼出一個我自己寫的用於專案構建,自動編譯打包的Hudson外掛原始碼。
HelloWorldBuilder.java
[java] view plaincopyprint?- package zygroup;
- import hudson.FilePath;
- import hudson.Launcher;
- import hudson.Extension;
- import hudson.Proc;
- import hudson.util.FormValidation;
- import hudson.model.AbstractBuild;
- import hudson.model.BuildListener;
- import hudson.model.AbstractProject;
- import hudson.remoting.Channel;
- 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;
- publicclass HelloWorldBuilder extends Builder {
- privatefinal String locate;
- privatefinal String cmd;
- // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
- @DataBoundConstructor
- public HelloWorldBuilder(String locate, String cmd) {
- this.locate = locate;
- this.cmd = cmd;
- }
- /**
- * We'll use this from the <tt>config.jelly</tt>.
- */
- public String getLocate() {
- return locate;
- }
- public String getCmd() {
- return cmd;
- }
- @Override
- publicboolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
- listener.getLogger().println("The SLiM build home is "+locate+".");
- listener.getLogger().println("The SLiM build command is "+cmd+".");
- try {
- FilePath path = new FilePath(Channel.current(),locate);
- Proc proc = launcher.launch(cmd, build.getEnvVars(), listener.getLogger(),path);
- int exitCode = proc.join();
- if (exitCode != 0) returnfalse;
- returntrue;
- } catch (IOException e) {
- e.printStackTrace();
- listener.getLogger().println("IOException !");
- returnfalse;
- } catch (InterruptedException e) {
- e.printStackTrace();
- listener.getLogger().println("InterruptedException!");
- returnfalse;
- }
- }
- @Override
- public DescriptorImpl getDescriptor() {
- return (DescriptorImpl)super.getDescriptor();
- }
- @Extension// this marker indicates Hudson that this is an implementation of an extension point.
- publicstaticfinalclass DescriptorImpl extends BuildStepDescriptor<Builder> {
- 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();
- }
- publicboolean isApplicable(Class<? extends AbstractProject> aClass) {
- // indicates that this builder can be used with all kinds of project types
- returntrue;
- }
- public String getDisplayName() {
- return"SLiM build";
- }
- @Override
- publicboolean configure(StaplerRequest req, JSONObject formData) throws FormException {
- save();
- returnsuper.configure(req,formData);
- }
- }
- }
- <j:jellyxmlns: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:entrytitle="Build Home"help="plugin/zyartifact/WEB-INF/classes/zygroup/HelloWorldBuilder/help-buildhome.html">
- <f:textboxname="locate"type="text"value="${instance.locate}"/>
- </f:entry>
- <f:entrytitle="Build Command"help="plugin/zyartifact/WEB-INF/classes/zygroup/HelloWorldBuilder/help-cmd.html">
- <f:textboxname="cmd"type="text"value="${instance.cmd}"/>
- </f:entry>
- </j:jelly>
其中<f:entry>的help屬性指向了一個html檔案,位於程式碼中設定的位置下,可以寫入標準的html標記,用於在此輸入框右邊顯示幫助按鈕和點出幫助資訊。
該外掛的主要輸入內容是:
locate和cmd兩個字串,傳遞給build程式使用,成為locate和cmd兩個變數。用於使用者輸入構建程式碼的目錄和需要啟動構建的命令。
例如
/opt/CruiseControl/apache-ant-1.7.0/
ant antbuild
build程式得到這兩個變數後,就啟動shell並在locate目錄下執行cmd命令。這個功能在perform函式中實現。
[java] view plaincopyprint?- publicboolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
- /向hudson執行控制檯輸出日誌資訊
- listener.getLogger().println("The SLiM build home is "+locate+".");
- listener.getLogger().println("The SLiM build command is "+cmd+".");
- try {
- //將locate字串轉化為hudson的FilePath型別
- FilePath path = new FilePath(Channel.current(),locate);
- //在path路徑下執行cmd命令
- Proc proc = launcher.launch(cmd, build.getEnvVars(), listener.getLogger(),path);
- //如果shell結果為失敗,則返回失敗
- int exitCode = proc.join();
- if (exitCode != 0) returnfalse;
- //返回成功
- returntrue;
- } catch (IOException e) {
- ......
- }
- }
然後在windows的cmd或者linux的控制檯中該專案目錄下,鍵入mvn package,即可自動生成target目錄下的檔案,包括一個hpi檔案和jar檔案。
將hpi拷貝到hudson目錄的plugin目錄下,或者通過hudson的頁面上傳外掛,重啟hudson,即可使用。
這個外掛是一個build型別的外掛,會在hudson的job配置頁面,出現在build step下拉選單中,名字由HelloWorldBuilder.java的下面一個函式控制:
[java] view plaincopyprint?- public String getDisplayName() {
- return"SLiM build";
- }
外掛在hudson已安裝外掛列表中顯示的名字,由該maven專案的poe.xml配置: [html] view plaincopyprint?
- <projectxmlns="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.jvnet.hudson.plugins</groupId>
- <artifactId>hudson-plugin-parent</artifactId>
- <version>2.1.1</version><!-- which version of Hudson is this plugin built against? -->
- </parent>
- <groupId>zygroup</groupId>
- <artifactId>zyartifact</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>hpi</packaging>
- <name>SLiM build</name>
- </project>
這樣一個實現專案自動構建的簡單外掛就可以使用了^.^