Jenkins 上で F# を動かす

このエントリは、Jenkins Advent Calendar jp 2011 の参加エントリです。
誕生日は Jenkins と過ごす・・・と思っていたら、一人キャンセルがでていて 22 日になってるし、仕事も忙しいしで、結局間に合いませんでした*1ずびばぜん・・・

Jenkins 上で好きな言語が使いたい!

そう思うのは自然なことです。
人間のごくごく当たり前の欲求です。
その欲求を満たすプラグインがすでに存在する場合、それを入れれば済む話です。
しかし、自分の好きな言語のプラグインが無い場合は・・・?
そうです、プラグインを自作すればいいのです。


ということで、Jenkins 上で F# を実行できるプラグインを作ってみます。

準備するもの

前提条件とも言います。

これらは既に導入済みと言うことで進めます。
まだという方は、以下のスライドが分かりやすいので参考にしましょう。
ちなみに今回は Eclipse は使いません。

プロジェクトを作る

まずはプロジェクトを作ります。
適当なディレクトリで、

mvn -cpu hpi:create

というコマンドを実行すると、プロジェクトの生成が始まります。
途中で groupId と artifactId の入力が求められます。
今回は F# を実行したいので、

groupId
com.github.bleistift.jenkins.fsplugin
artifactId
fsharp

としておきます*2
これで、現在のディレクトリに fsharp というディレクトリが作られました。
移動しておきましょう。

cd fsharp

Git で管理するなり Git で管理するなり Git で管理するなり、好きにしてください。
今回はこの中から Git を選びました。
.gitignore は Maven.gitignore を参考に、target/ だけ入れておけばいいでしょう。
https://github.com/github/gitignore 便利ですね。
・・・が、すぐわかるとおり今回は Git 自体必要なかったです。

プラグインを作る、というか真似る・・・むしろパクる

さてプラグインを作るわけですが、Jenkins にはたくさんのプラグインがありますので、自分の作るものに近いプラグインがある場合はそれを参考にすればいいでしょう。
今回は、PowerShell Pluginを参考にします。
PowerShell Plugin の構成は、

のようになっています。非常にシンプルですね。
さっそく真似ましょう。

  • src
    • main
      • java
        • com/github/bleistift/fsplugin/FSharp.java
      • resources
        • com/github/bleistift/fsplugin/FSharp/config.jelly
        • index.jelly
  • pom.xml
FSharp.java

PowerShell.java を参考にこんな感じで。

package com.github.bleistift.fsplugin;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import org.kohsuke.stapler.DataBoundConstructor;

/**
 * Invokes F# from Jenkins.
 * @author bleis-tift
 */
public class FSharp extends CommandInterpreter {
    @DataBoundConstructor
    public FSharp(String command) {
        super(command);
    }

    protected String getFileExtension() {
        return ".fsx";
    }

    public String[] buildCommandLine(FilePath script) {
        return new String[] { "fsi.exe", "--exec", script.getRemote() };
    }

    protected String getContents() {
        return command;
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return true;
        }

        public String getDisplayName() {
            return "F# Script";
        }
    }
}

FSharp クラスが継承している CommandInterpreter クラスというのは、Javadoc を見ると Shell クラスや BatchFile クラスが継承していることからもわかるように、

のあたりに機能を追加するための拡張ポイントです。
重要なのは buildCommandLine メソッドですね。これがほぼ全てです。

index.jelly
<div>
  This plugin allows Jenkins to invoke F# as build scripts.
</div>

index.jelly も PowerShell Plugin からパクってさくっと。

config.jelly
<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">
  <f:entry title="${%Script}" field="command">
    <f:textarea />
  </f:entry>
</j:jelly>

config.jelly も同じく。

ビルド、そして・・・

後はビルドするだけです。

mvn package

これで target ディレクトリに fsharp.hpi というファイルができるので、プラグインマネージャから .hpi をアップロードして、Jenkins を再起動すれば・・・


できた!
いやー、簡単ですね。


環境変数覗けば WORKSPACE やらなんやら使えますので、普通に使えます。
これで、Scala なり OCaml なり SML# なり、自分の好きな言語を Jenkins 上に簡単に乗っけれますね!


23 日は @okitan さんで、jenkins でビューを色々いじると仕事が捗る #jenkinsci です。

*1:23 でも結局間に合っていないという・・・

*2:JavaのパッケージはURLを前提としているのに、識別子にハイフンが使えないのとかどうにかならないんですかね・・・