持续集成(CI)将软件项目流程的各个阶段进行自动化部署,从build, deploy, test automation到coverage分析,全部实现自动完成,而不需要每天的手工操作。

在敏捷开发过程中,持续集成大大提高了团队的工作效率,开发和测试人员可以专注于代码与测试用例的编写,而不需要过多关注编译和部署。每天夜晚进行持续集成的自动化部署,第二天可以马上开始测试和分析前日的测试效果与代码覆盖率,和敏捷开发的理念结合的恰到好处。

下面我就来介绍下如何开发一个Hudson的插件。

首先你要有Maven 2和JDK1.6以上,这是必须的。然后在你的Maven 2的setting.xml 文件中加入下列代码

[html] view plaincopyprint?
  1. <pluginGroups>
  2. <pluginGroup>org.jvnet.hudson.tools</pluginGroup>
  3. </pluginGroups>
  4. <profiles>
  5. <profile>
  6. <id>hudson</id>
  7. <activation>
  8. <activeByDefault />
  9. </activation>
  10. <pluginRepositories>
  11. <pluginRepository>
  12. <id>m.g.o-public</id>
  13. <url>http://maven.glassfish.org/content/groups/public/</url>
  14. </pluginRepository>
  15. </pluginRepositories>
  16. <repositories>
  17. <repository>
  18. <id>m.g.o-public</id>
  19. <url>http://maven.glassfish.org/content/groups/public/</url>
  20. </repository>
  21. </repositories>
  22. </profile>
  23. </profiles>
  24. <activeProfiles>
  25. <activeProfile>hudson</activeProfile>
  26. </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?
  1. mvn hpi:create

之后会问你一些如groupId和artifactId之类的问题,groupId填写成你一般开发java代码的package信息,例如com.webex.slim.hudsonplugin,artifactId则是你编写此hudson插件的名称,例如buildslim。

完成后计算机会自动的创建了一个项目,里面有一些模板代码,可供你学习如何开始写一个Hudson的插件,后面的代码全部来自模版代码。如果你需要在Eclipse里编辑插件可以执行  

[plain] view plaincopyprint?
  1. mvn -DdownloadSources=true eclipse:eclipse
然后你就可以在Eclipse中导入这个项目并开始开发了。Eclipse导入maven工程,需要安装maven插件,然后在工程里选择导入已有项目即可。

执行前面的maven命令后,在我们的指定目录下已经生成了一个Hudson 插件的项目文件夹,这个目录应该是~/artifactId/。在Eclipse中导入这个项目,我们可以看见项目有如下的结构:

[plain] view plaincopyprint?
  1. + src
  2. + main
  3. + java
  4. +  full.package.name
  5. +- HelloWorldBuilder.java
  6. +resources
  7. +  full.package.name
  8. +- config.jelly
  9. +- global.jelly
  10. +- index.jelly
  11. + webapp
  12. +- help-globalConfig.html
  13. +- help-projectConfig.html

HelloWorldBuilder.java

这个类就是具体实现某一扩展点的一个类,在这里由于要扩展Builder这个扩展点,所以继承了 Builder 这个类。在Hudson 中有很多不同种类的扩展点,比如Publisher、Recorder 等等。详细的说明可以参考Hudson 的网站。

建好工程后,已经有一些代码是maven自动生成的hudson插件示例代码。

下面我来逐步分析这些代码

[java] view plaincopyprint?
  1. @DataBoundConstructor
  2. public HelloWorldBuilder(String name) {
  3. this.name = name;
  4. }
  5. /**
  6. * We'll use this from the <tt>config.jelly</tt>.
  7. */
  8. public String getName() {
  9. return name;
  10. }

这段代码用于构造这个Bulider并且从相应的config.jelly中获取相应的参数。Hudson使用了一种叫structured form submission的技术,使得可以使用这种方式活动相应的参数。

[java] view plaincopyprint?
  1. public boolean perform(Build build, Launcher launcher, BuildListener listener) {
  2. // this is where you 'build' the project
  3. // since this is a dummy, we just say 'hello world' and call that a build
  4. // this also shows how you can consult the global configuration of the builder
  5. if(DESCRIPTOR.useFrench())
  6. listener.getLogger().println("Bonjour, "+name+"!");
  7. else
  8. listener.getLogger().println("Hello, "+name+"!");
  9. return true;
  10. }

方法perform()是个很重要的方法,当插件运行的的时候这个方法会被调用。相应的业务逻辑也可以在这里实现。比如这个perform()方法就实现了怎么说 “Hello”

接下来,在HelloBuilder 这个类里面有一个叫 DescriptorImpl 的内部类,它继承了Descriptor。在Hudson 的官方说明文档里说Descriptor包含了一个配置实例的元数据。打个比方,我们在工程配置那里对插件进行了配置,这样就相当于创建了一个插脚的实例,这时候就需要一个类来存储插件的配置数据,这个类就是Descriptor。

[java] view plaincopyprint?
  1. public String getDisplayName() {
  2. return "Say hello world";
  3. }
如上面的代码,可以在Descriptor的这个方法下设置插件在工程配置页面下现实的名字 

[java] view plaincopyprint?
  1. public boolean configure(StaplerRequest req, JSONObject o) throws FormException {
  2. // to persist global configuration information,
  3. // set that to properties and call save().
  4. useFrench = o.getBoolean("useFrench");
  5. save();
  6. return super.configure(req);
  7. }

如同注释属所说,这个方法用于将全局配置存储到项目中

注意点:

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?
  1. package zygroup;
  2. import hudson.FilePath;
  3. import hudson.Launcher;
  4. import hudson.Extension;
  5. import hudson.Proc;
  6. import hudson.util.FormValidation;
  7. import hudson.model.AbstractBuild;
  8. import hudson.model.BuildListener;
  9. import hudson.model.AbstractProject;
  10. import hudson.remoting.Channel;
  11. import hudson.tasks.Builder;
  12. import hudson.tasks.BuildStepDescriptor;
  13. import net.sf.json.JSONObject;
  14. import org.kohsuke.stapler.DataBoundConstructor;
  15. import org.kohsuke.stapler.StaplerRequest;
  16. import org.kohsuke.stapler.QueryParameter;
  17. import javax.servlet.ServletException;
  18. import java.io.IOException;
  19. public class HelloWorldBuilder extends Builder {
  20. private final String locate;
  21. private final String cmd;
  22. // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
  23. @DataBoundConstructor
  24. public HelloWorldBuilder(String locate, String cmd) {
  25. this.locate = locate;
  26. this.cmd = cmd;
  27. }
  28. /**
  29. * We'll use this from the <tt>config.jelly</tt>.
  30. */
  31. public String getLocate() {
  32. return locate;
  33. }
  34. public String getCmd() {
  35. return cmd;
  36. }
  37. @Override
  38. public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
  39. listener.getLogger().println("The SLiM build home is "+locate+".");
  40. listener.getLogger().println("The SLiM build command is "+cmd+".");
  41. try {
  42. FilePath path = new FilePath(Channel.current(),locate);
  43. Proc proc = launcher.launch(cmd, build.getEnvVars(), listener.getLogger(),path);
  44. int exitCode = proc.join();
  45. if (exitCode != 0) return false;
  46. return true;
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. listener.getLogger().println("IOException !");
  50. return false;
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. listener.getLogger().println("InterruptedException!");
  54. return false;
  55. }
  56. }
  57. @Override
  58. public DescriptorImpl getDescriptor() {
  59. return (DescriptorImpl)super.getDescriptor();
  60. }
  61. @Extension // this marker indicates Hudson that this is an implementation of an extension point.
  62. public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
  63. public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException {
  64. if(value.length()==0)
  65. return FormValidation.error("Please set a name");
  66. if(value.length()<4)
  67. return FormValidation.warning("Isn't the name too short?");
  68. return FormValidation.ok();
  69. }
  70. public boolean isApplicable(Class<? extends AbstractProject> aClass) {
  71. // indicates that this builder can be used with all kinds of project types
  72. return true;
  73. }
  74. public String getDisplayName() {
  75. return "SLiM build";
  76. }
  77. @Override
  78. public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
  79. save();
  80. return super.configure(req,formData);
  81. }
  82. }
  83. }

设置插件相关的用户输入页面的文件config.jelly

[html] view plaincopyprint?
  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. This jelly script is used for per-project configuration.
  4. See global.jelly for a general discussion about jelly script.
  5. -->
  6. <!--
  7. Creates a text field that shows the value of the "name" property.
  8. When submitted, it will be passed to the corresponding constructor parameter.
  9. -->
  10. <f:entry title="Build Home" help="plugin/zyartifact/WEB-INF/classes/zygroup/HelloWorldBuilder/help-buildhome.html">
  11. <f:textbox name="locate" type="text" value="${instance.locate}"/>
  12. </f:entry>
  13. <f:entry title="Build Command" help="plugin/zyartifact/WEB-INF/classes/zygroup/HelloWorldBuilder/help-cmd.html">
  14. <f:textbox name="cmd" type="text" value="${instance.cmd}"/>
  15. </f:entry>
  16. </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?
  1. public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
  2. /向hudson运行控制台输出日志信息
  3. listener.getLogger().println("The SLiM build home is "+locate+".");
  4. listener.getLogger().println("The SLiM build command is "+cmd+".");
  5. try {
  6. //将locate字符串转化为hudson的FilePath类型
  7. FilePath path = new FilePath(Channel.current(),locate);
  8. //在path路径下执行cmd命令
  9. Proc proc = launcher.launch(cmd, build.getEnvVars(), listener.getLogger(),path);
  10. //如果shell结果为失败,则返回失败
  11. int exitCode = proc.join();
  12. if (exitCode != 0) return false;
  13. //返回成功
  14. return true;
  15. } catch (IOException e) {
  16. ......
  17. }
  18. }

然后在windows的cmd或者linux的控制台中该项目目录下,键入mvn package,即可自动生成target目录下的文件,包括一个hpi文件和jar文件。

将hpi拷贝到hudson目录的plugin目录下,或者通过hudson的页面上传插件,重启hudson,即可使用。

这个插件是一个build类型的插件,会在hudson的job配置页面,出现在build step下拉菜单中,名字由HelloWorldBuilder.java的下面一个函数控制:

[java] view plaincopyprint?
  1. public String getDisplayName() {
  2. return "SLiM build";
  3. }

插件在hudson已安装插件列表中显示的名字,由该maven项目的poe.xml配置:

[html] view plaincopyprint?
  1. <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">
  2. <modelVersion>4.0.0</modelVersion>
  3. <parent>
  4. <groupId>org.jvnet.hudson.plugins</groupId>
  5. <artifactId>hudson-plugin-parent</artifactId>
  6. <version>2.1.1</version><!-- which version of Hudson is this plugin built against? -->
  7. </parent>
  8. <groupId>zygroup</groupId>
  9. <artifactId>zyartifact</artifactId>
  10. <version>1.0-SNAPSHOT</version>
  11. <packaging>hpi</packaging>
  12. <name>SLiM build</name>
  13. </project>

这样一个实现项目自动构建的简单插件就可以使用了^.^

Hudson插件开发入门体验相关推荐

  1. hudson插件开发入门

    持续集成(CI)是将多个团队成员贡献的代码频繁持续的构建并给与反馈,而不必将寻找和修复缺陷的过程放在开发后期.在典型的持续集成周期中,代码首先被周期性的从版本控制服务器(如CVS或Subversion ...

  2. 【直播回顾】蚂蚁金服高级开发工程师萧恺:IDEA 插件开发入门教程

    主讲人:萧恺(蚂蚁金服-支付宝事业群-高级开发工程师) 本名:肖汉松 讲师介绍: 热爱阅读,喜欢挑战,热衷尝试新的技术,关注技术背后的原理. 关注领域:Java 服务端开发,分布式系统 关注语言:Ja ...

  3. ormlite android studio,OrmLite-android入门体验

    入门体验 OrmLite 是一个轻量级的ORM(Object Relational Mapping)数据库工具,方便持久化java对象到数据库 1. 使用准备 使用androidADT创建androi ...

  4. python课程推荐-课程推荐:四天人工智能 python入门体验课

    作为一名被大数据和数量信息包裹的文案,我们必定要在文案力.创意和策略能力之外,准备一个加薪引擎--以技术思维处理数据运算的能力. 只有跑得足够快,才能不被行业当成 "老古董" 落下 ...

  5. PHPWeb开发入门体验学习笔记

    PHPWeb开发入门体验学习笔记 4 一.PHP web应用开发须知 1.入门要点 程序员三个阶段:码农(速成技能)->工程师(长期知识)->专家(研究论文) 编程三要素:声明变量(系统. ...

  6. springboot导包显示不存在_基础篇:Spring Boot入门体验(图文教程)

    优质文章,及时送达 什么是 Spring Boot? Spring Boot 是由 Pivotal 团队提供的全新框架.Spring Boot 是所有基于 Spring Framework 5.0 开 ...

  7. xposed模块编写教程_Xposed插件开发入门详解,

    Xposed插件开发入门详解, 前言 Xposed的用处不必言说,能hook任意java写的代码,修改替换apk内部的资源文件. 至于如何开发一个XPosed的插件,官方给出的答案如下: https: ...

  8. 在STM32Cube中使用FreeRTOS:入门体验

    文章目录 目的 基础说明 入门体验 基础配置 任务调度 消息队列 信号量 互斥量 定时器 其它补充 使用ST-LINK调试 中断嵌套 总结 目的 FreeRTOS是现在比较流行的主要应用于单片机等性能 ...

  9. python体验课是上纯代码_大陈教初中生学Python,入门体验第二课教学设计,溯本追源...

    第一课我们已经对Python有了一定的了解,那么接下来再通过补充程序.改正程序.编写程序,和学生一起明确变量.常量.表达式.数据类型.赋值语句.输入输出语句.数据类型转换函数.算术运算符等基础知识,溯 ...

最新文章

  1. 自动驾驶又陷“派系”之争:该约束行人还是让车更完美
  2. ceph与hdfs的比较_分布式存储中HDFS与Ceph两者的区别是什么,各有什么优势?
  3. java 1.7的新特性_[Java]  JDK 1.7版本的 新特性
  4. C/C++字符串输入方法比较(带回车不带回车输入)
  5. shiro整合mybatis数据库
  6. XP下安装装SQL2000企业版本
  7. 0x00000000指令引用的内存不能为written_JVM03——对象实例化,内存布局,访问定位...
  8. python下载_安装_配置_以及第一行python程序---python工作笔记009
  9. postgresql查看死锁及解决方法
  10. JqGrid常用示例
  11. 诺基亚s40机破权相关说明
  12. 如何在整个目录上运行dos2unix?
  13. 浪漫的表白(C语言)
  14. 基于simulink的风能/光伏发电系统仿真
  15. qt button clicked(bool) always false
  16. 我们在电脑房里上计算机课英语,英语复习1
  17. 路由器下一跳地址怎么判断_三分钟了解路由器路由表
  18. 怎么科学解读闪电鞭?年轻人我劝你耗子尾汁,好好反思
  19. LG WebOS TV降级方法
  20. Vue前端开发——微信扫码支付

热门文章

  1. 解决LVM和硬盘PVID问题
  2. linux查看端口pvid,关于PVID的几个疑问
  3. C++中的库文件导入与导出
  4. Vuex,Vue-router
  5. 看完秒懂ICA(含MATLAB和python代码)
  6. java servlet作用_Servlet的功能是什么
  7. 【太阳黑子预测】太阳黑子变化规律预测matlab仿真
  8. 动态规划(准备工作)
  9. 九月十月百度人搜,阿里巴巴,腾讯华为小米搜狗笔试面试八十题(10.29)
  10. element组件---Form