早期地下采煤的时候,矿井中的金丝雀常常拥有短暂而有价值的一生。因为它们对于甲烷和一氧化碳等致命毒气特别敏感,从栖木上掉下来的金丝雀是一个明显的信号,告诉矿工是时候离开了。过一段时间之后,假如 新换上的金丝雀安然无恙,矿工们又可以安全地重返矿井。
您的软件项目也可以有自己的矿井金丝雀。假如 您在运用 CruiseControl 之类的持续集成工具,那么很可能熟悉当构建失败时发送给团队的电子邮件消息。这是一个信号,说明项目代码中有些地点 须要 马上改正。但是,当收件箱中还有很多其他邮件时,这些消息有时候会被忽略。然后,在知晓 消息之前,我已经从有疑问 的储存库中更新了本地副本,或者直接回家了,让团队中的其他人直抱怨。
这时须要 某种高度可视的东西,就像金丝雀一样,高速 一瞥后就能发觉 持续构建流程 的状态。我的金丝雀是一种新的来自 Sun Microsystems 的开源技能 ,它的名称是 Sun Small Programmable Object Technology(SPOT)。本文推荐 SPOT,并展示如何 建立用于监视 CruiseControl 的构建监视器。
什么是 SPOT?
SPOT(见 图 1)是运行 Java™ 程序的小型无线装备。SPOT 载有很多传感器,用于监视它的环境,还有一组多彩 LED 用于与外部通信,以及两个用于提供基本反馈的按钮。我运用 LED 来显示构建的状态。可以通过一条 USB 线将一些 SPOT 连接起来,作为一个基站,其他 SPOT 可以通过这个基站访问工作站上的资源,例如数据库或 Web 运用 程序。
图 1. Sun SPOT
取得 SPOT
假如 要取得 一些 SPOT,以便组成自己的构建监视器,那么可以通过 Sun SPOT World购买工具包。在工具包中,有两个 SPOT 和一个基站,一些 USB 线缆,以及放在 CD 中的 SDK。这种工具包有时候会脱销(而且仍然比较贵),但是不要因此而耽误进程。随 SDK 还附带了一个名为 Solarium 的模拟器,这意味着可以在虚拟 SPOT 上马上开始工作。
安装 SDK 后,可以探索示例运用 程序、开发人员指南、技能 规范、源代码和一些项目描述,例如运用 SPOT 控制 Web 相机。
SPOT 由以下硬件元件组成:
主处理器是一个 180MHz Atmel AT91RM9200 系统芯片(system-on-chip)。
每个 SPOT 有 4MB Flash RAM 和 512K 伪静态 RAM。
电力由内部充电电池(圆柱形锂电池)、外接电源或 USB 主机提供。
电池充电后可无间断运用 大约 3 小时。当无事发生时,它执行 休眠,从而延长运用 寿命。
演示子板包含温度和光传感器、一个三轴加快 计、8 个三色 LED 以及两个按钮开关。必要时,还可以通过 5 个通用 I/O 插脚和 4 个高电流输出插脚添加 更多的子板。
无线通信通过一个遵从 IEEE 802.15.4 的收发器完成,该收发器采用 2.4GHz-to-2.4835GHz 免授权频段。
在这样的硬件上,SPOT 运行一个名为 Squawk 的小型 JVM,这个 JVM 几乎完全是用 Java 语言编写的。Squawk 遵从 Connected Limited Device Configuration(CLDC)1.1 Java Micro Edition(Java ME)配置。它无需底层操作系统便可运行 — 也就是所谓的 “在裸机上运行”。
计算物理系统
SPOT 是计算物理系统的一个例子。计算物理系统中有一些嵌入的装备,这些装备运行可感知环境并作出反应的软件和通信协议。
用于 SPOT 的运用 程序是根据 Java ME MIDlet 编程模型编写的。这意味着每个 SPOT 上的 JVM 以类似于 Java EE 下维护 servlet 和 Enterprise JavaBeans(EJBs)的方式来维护 MIDlet 的生命周期。但是,由于 MIDlet 运行环境的限定 ,CLDC 以 JDK 1.3.1 作为开始的基础,剥离所有不必要的部分。因此,SPOT 程序无法访问文件流;没有反射,没有串行化,没有本地法,没有正则表达式,没有 Swing,只有有限的数据类型。独一可用的集合数据结构是向量栈、枚举和 hash 表。有些特定于 CLDC 的连接类被添加到这个子集中,但是编程流程中仍然要受很多限定 。
为SPOT 编写、构建和部署代码
为SPOT 编写、构建和部署代码特别基本 ,可以运用自己挑选的IDE。例如,若要在 Eclipse 中执行 开发:
建立一个规则的 Java 项目,删除默认的JRE。
将SPOT SDK的 lib文件夹中的以下 JAR 添加到 classpath:
transducer_device.jar
multihop_common.jar
spotlib_device.jar
spotlib_common.jar
squawk_device.jar
在 resources/META-INF 目录中建立一个MANIFEST.MF文件。该文件包含 Squawk VM用于运行程序的信息。例如,清单1是我的构建监视器的manifest文件:
清单 1. resources/META-INF/MANIFEST.MF 文件的内容MIDlet-Name:
BuildCanary
MIDlet-Version:
1.0.0
MIDlet-Vendor:
Craig Caulfield
MIDlet-1:
Build Canary, , speck.buildmonitor.BuildCanary
MicroEdition-Profile:
IMP-1.0
MicroEdition-Configuration:
CLDC-1.1
BaseStationAddress:
0014.4F01.0000.3A3C
PortNumber:
100
清单1中最首要的一行是:MIDlet-1:
Build Canary, ,speck.buildmonitor.BuildCanary
第一个参数是运用 程序的名称,第三个参数是运用 程序主类的完全限定类名。
可以在该文件中添加自己的属性,并在运行时读取这些属性,例如:String
baseStationAddress = getAppProperty("BaseStationAddress");
建立 一个扩展 javax.microedition.midlet.MIDlet 的类,然后开始开发运用 程序。
当准备好部署代码时,将代码打包到一个 JAR 中,通过无线的方式将它发送到 SPOT:
运用 USB 线将一个基站 SPOT 连接到工作站。
执行 SPOT SDK 安装目录中的 ant startbasestation,启动基站。
执行以下命令部署 JAR:ant
-DremoteId=0014.4F01.0000.3A19 deploy
下载中提供了 build-canary 运用 程序的 Eclipse 项目,可以以此为基础。
运用程序概述
图 2 中的部署图展示我如何 配置 构建监视器,以监视 CruiseControl 构建。
图 2. 构建监视器的部署图
CruiseControl 循环构建在一个构建服务器上运行,该构建服务器有一个通过 USB 线连接的 SPOT 基站。每当构建的当前状态(SUCCESS、FAILED 或 RUNNING)改动时,构建服务器上都会调用一个基本的Java SE运行程序 — CanaryHandler。然后,通过基站 SPOT 将一条无线消息发送到BuildCanary— 远程 SPOT 上运行的一个MIDlet—以更新该SPOT的LED,从而反映构建的新状态。
CanaryHandler 代码
为了让 CanaryHandler 程序有一个良好的开端,我运用 Apache Commons CLI 分析 命令行参数。CLI 负责收集和验证参数,并提供方便的帮助功能。例如,假如输入java CanaryHandler --help,可以看到清单 2 中的输出:
清单 2. CanaryHandler 的帮助文本usage:java
CanaryHandler (--running --failed --success) --spot "0014.4F01.xxxx.yyyy" --port 100 --serial COM4 This program connects with a remote Sun SPOT to set a colour to denote the current state of the Continuous Integration build process.
-a,--spot <spot> The IEEE wireless address (like 0014.4F01.0000.30E0) of the SPOT (enclose in double quotes).
-c,--serial <serial>The serial port (e.g. COM4) to which the SPOT base station is attached.
-f,--failed The build has failed.
-h,--help Print this usage information.
-p,--port <port> The port address (range 32 to 255) to be used for communicating with the SPOT.
-r,--running The building is running
-s,--success The building was successful.
For more instructions see the Javadoc
in the docs/api directory.
从 清单 2 中可以看到,CanaryHandler 接受 4 个参数:
构建的当前状态,可能的值有:
Running(SPOT 的 LED 中显示蓝色流光)。
Failed(SPOT 的 LED 闪烁红色)。
Successful(SPOT 的 LED 显示为不动的绿条)。
一个远程 SPOT 的地址。每个 SPOT 由一个 64 位的 IEEE 无线地址标识,该地址以 0014.4F01 开头,后面再加上两个四位字节,从而惟一地标识 SPOT。
一个端口号,惟一地标识基站与远程 SPOT 之间的连接。
一个串行端口,标识构建服务器上与基站连接的串行端口。
分析 命令行参数后,CanaryHandler 打开与远程 SPOT 的 radiostream 连接,如清单 3 所示:
清单 3. CanaryHandler 中的 main() 要领 /**
* Respond to the state a continuous build process by setting the LEDs on a
* remote SPOT accordingly. This is done by writing a simple message to an
* output stream, on the end of which is a SPOT waiting to read.
*
* @param args the command line arguments. See the printUsage
* method further down for a full description of the parameters.
*/
public
static void main(String[] args) throws IOException {
createCommandLineParser();
StreamConnection connection = null;
DataOutputStream dos = null;
DataInputStream dis = null;
try {
CommandLine commandLine = parser.parse(options, args);
String spotAddress = commandLine.getOptionValue("spot");
String port = commandLine.getOptionValue("port");
String spotConnection = "radiostream://" + spotAddress + ':' + port;
System.setProperty("SERIAL_PORT",
commandLine.getOptionValue("serial"));
log.info("Setting address to " + spotConnection);
connection= (StreamConnection) Connector.open(spotConnection);
dos = connection.openDataOutputStream();
dis = connection.openDataInputStream();
if (commandLine.hasOption("running")) {
log.info("Setting build state to RUN.");
dos.writeUTF("RUN");
dos.flush();
log.info("SPOT responded with: " + dis.readUTF());
} else if (commandLine.hasOption("failed")) {
log.info("Setting build state to FAIL.");
dos.writeUTF("FAIL");
dos.flush();
log.info("SPOT responded with " + dis.readUTF());
} else if (commandLine.hasOption("success")) {
log.info("Setting build state to SUCCESS.");
dos.writeUTF("SUCCESS");
dos.flush();
log.info("SPOT responded with " + dis.readUTF());
} else {
printUsage(options);
System.exit(1);
} } catch (ParseException e) {
// This will be thrown if the command line arguments are malformed.
printUsage(options);
System.exit(1);
} catch (NoRouteException nre) {
log.severe("Couldn't get a connection to the remote SPOT.");
nre.printStackTrace();
System.exit(1);
} finally {
if (connection != null) {
connection.close();
} System.exit(0);
}
}
radiostream 协议类似于点对点套接字连接,它为基站与远程 SPOT 之间提供可靠、有缓冲的基于流的 I/O。另外,还打开数据输入和输出流。然后,一条关于构建的基本 的消息被写到输出流,远程 SPOT 读取该消息,然后返回一条简短的确认消息。接着 CanaryHandler 结束,等待下一次构建状态改动 时再次被调用。
BuildCanary 代码
当 CanaryHandler 打开一个 radiostream 连接并发送一条关于构建的基本 消息时,连接的另一端是 BuildCanary,它是部署在远程 SPOT 上的一个 Java ME MIDlet。MIDlet 与 servlet 和 EJB 类似,也是实现一个专门的接口,运行时环境负责在它的生命周期中的某些时候调用某些要领 。
例如,MIDlet 的典型的入口点是 startApp() 要领 。在 BuildCanary 中,startApp() 委托给另一个要领 ,后者又产生一个线程,以侦听来自 CanaryHandler 的消息。清单 4 显示 BuildCanary 的入口点的代码:
清单 4. BuildCanary 的入口点/**
* MIDlet call to start our application.
*/
protected
void startApp() throws MIDletStateChangeException {
run();
} /**
* Main application run loop which spawns a thread to listen for updates
* about the build status from the host.
*/
private
void run() {
// Spawn a thread to listen for messages from the host.
new Thread() {
public void run() {
try {
updateBuildStatus();
} catch (IOException ex) {
try {
if (connection != null) {
connection.close();
} if (dos != null) {
dos.close(); } if (dis != null) {
dis.close();
} ex.printStackTrace();
} catch (IOException ex1) {
// Can't do much now.
ex1.printStackTrace();
} }
}
}.start();
}
updateBuildStatus() 要领 处理 MIDlet 的首要 工作,如清单 5 所示:
清单 5. 侦听 CanaryHandler 发送的消息的要领 /**
* Reflect the status of the continuous build process taking place on the
* host PC by setting the colours of the SPOT's LEDs appropriately.
*/
private
void updateBuildStatus() throws IOException {
setColour(LEDColor.WHITE, "");
String baseStationAddress=getAppProperty("BaseStationAddress");
String portNumber = getAppProperty("PortNumber");
String connectionString="radiostream://" + baseStationAddress + ':' + portNumber;
// Open a connection to the base station.
connection=(RadiostreamConnection)
Connector.open(connectionString);
dis = connection.openDataInputStream();
dos = connection.openDataOutputStream();
while (true) {
// dis will block here forever until it has something from the host
// to read.
String buildStatus = dis.readUTF();
if (buildStatus.equals("RUN")) {
setColour(LEDColor.BLUE, buildStatus);
} else if (buildStatus.equals("SUCCESS")) {
setColour(LEDColor.GREEN, buildStatus);
} else if (buildStatus.equals("FAIL")) {
setColour(LEDColor.RED, buildStatus);
} else {
throw new IllegalArgumentException("The build status of " + buildStatus + " isn't recognised.");
} dos.writeUTF("Build status updated to " + buildStatus);
dos.flush();
}
}
该要领 最先 将所有 LED 变成白色,示意 SPOT 就绪。然后,与 CanaryHandler 类似,它打开一个 radiostream 连接以及数据输入和输出流。但是,在这里,它最先 尝试从输入流读一条消息,并阻塞直到收到消息。
如清单 6 所示,当收到一条消息时,setColour() 要领 产生另一个线程,以更新 SPOT 上的 LED 的颜色。然后,BuildCanary 回到 while 循环的顶端,等待下一条消息。
清单 6. 产生一个配置 颜色的新线程/**
* Set a colour for the LEDs to display.
*
* @param colour an LEDColor value.
* @param buildStatus the current status if the build.
*/
private
void setColour(final LEDColor colour, final String buildStatus) {
if (colourThread != null) {
colourThread.interrupt();
} setColour = new SetColour(colour, buildStatus);
colourThread = new Thread(setColour);
colourThread.start();
}
与此同时,真实 操作 LED 是在 SetColour 类中,如清单 7 所示:
清单 7. 操作 LED 的内部类/**
* An inner class to handle the colour and behaviour (flashing, solid,
* running) of the LEDs of a SPOT.
*/
public
class SetColour implements Runnable {
/*** A reference to the SPOTs LEDs so they can be set according to the
* state of the build.
*/
private ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();
/**
* The RGB colour to set the LEDs to.
*/
private LEDColor colour;
/**
* The current status of the build. This will be used to set the behaviour of the LEDs (flashing, solid, running).
*/
private String buildStatus;
public SetColour(LEDColor colour, final String buildStatus) {
this.colour = colour;
this.buildStatus = buildStatus;
}
public void run() {
try {
if (buildStatus.equals("RUN")) {
// Display running LEDs.
while (true) {
for (int i = 0; i < 8; i++) {
leds[i].setColor(colour);
leds[i].setOn();
Thread.sleep(200);
leds[i].setOff();
} }
} else if (buildStatus.equals("FAIL")) {
// Flash the LEDs on and off.
while (true) {
for (int i = 0; i < 8; i++ ) {
leds[i].setColor(colour);
leds[i].setOn();
} Thread.sleep(250);
for (int i = 0; i < 8; i++ ) {
leds[i].setOff();
} Thread.sleep(250);
}
} else {
// Display the LEDs as a solid bar.
for (int i = 0; i < 8; i++) {
leds[i].setColor(colour);
leds[i].setOn();
} }
} catch (InterruptedException ie) {
// Do nothing. Just bail out so we can set the LEDs to another colour.
} }
}
清单7 为 SPOT 的8 个LED 分别配置 颜色,并根据构建的状态打开或关上 这些 LED。
下一步是将构建监视器附加到持续构建流程 。
CruiseControl 配置
当 CruiseControl 开始时,它在一个连续循环中运行,周期性地检验源代码库,例如 Subversion,然后从头开始构建和测试项目。然后,CruiseControl 可以将构建成功或失败的状态揭晓 到站点 上,供所有人查看,并发出各种不同的消息。
CruiseControl 循环的行为由一个 XML 配置文件 config.xml 示意 ,如清单 8 所示:
清单 8. CruiseControl 配置文件<cruisecontrol>
<property environment="env"/>
<property name="local.directory" value="C:/data/skills/development/builds"/>
<property name="repository" value="http://kimba/svn"/>
<property name="javaExecutable" value="C:/applications/java/jdk1.6.0_14/bin/java.exe"/>
<property name="workingDirectory" value="C:/data/skills/development/java/micro/netbeans/sunspots/CanaryHandler/dist"/>
<property name="libraryPath"
value="-Djava.library.path=C:/applications/Sun/ SunSPOT/sdk-red-090706/lib"/>
<property name="commonArguments" value="${libraryPath} -jar
${workingDirectory}/CanaryHandler.jar
--spot "0014.4F01.0000.3A19"
--port 100
--serial COM4"/>
<plugin name="log" dir="${logdir}"/>
<plugin name="svn" classname="net.sourceforge.cruisecontrol.sourcecontrols.SVN"
username="cruise"
password="catbert"/>
<project name="developer-ci-build" buildafterfailed="false">
<!--
Defines where CruiseControl looks for changes to decide whether to run the build. -->
<modificationset quietperiod="30"> <svn localWorkingCopy="${local.directory}/checkout/SunDeveloper" repositoryLocation="${repository}/DEVELOPER/trunk/SunDeveloper"/>
</modificationset>
<!--
Check for modifications every 60 seconds -->
<schedule interval="60">
<composite>
<exec command="${javaExecutable}" args="${commonArguments} --running"/>
<maven2 mvnhome="${env.MAVEN_HOME}" pomfile="${local.directory}/checkout/SunDeveloper/pom.xml"
goal="clean scm:update compile"/>
</composite>
</schedule>
<listeners>
<currentbuildstatuslistener file="${local.directory}/logs/${project.name}/buildstatus.txt"/>
</listeners>
<!--
The publishers are run when a build completes one way or another. -->
<publishers>
<onsuccess>
<execute command="${javaExecutable} ${commonArguments} --success"/>
</onsuccess>
<onfailure>
<execute command="${javaExecutable} ${commonArguments} --failed"/>
</onfailure>
</publishers>
</project>
</cruisecontrol>
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。