Hudsonプラグイン開発

Hudsonプラグイン開発について調べたことを書いてみたいと思います。環境は下記の通りです。

OS Vista
JDK 1.6
Maven 2.0.10

http://wiki.hudson-ci.org/display/HUDSON/Plugin+tutorialにしたがってやってみます。このプラグインはビルド実行するとhelloと出力します。

%USERPROFILE%\.m2\settings.xmlを下記のように記述します。

<settings>

<profiles>
  <profile>
    <id>hudson</id>

    <activation>
      <activeByDefault />
    </activation>

    <pluginRepositories>
      <pluginRepository>
        <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/</url>
      </repository>
    </repositories>
  </profile>
</profiles>

<activeProfiles>
  <activeProfile>hudson</activeProfile>
</activeProfiles>

<pluginGroups>
  <pluginGroup>org.jvnet.hudson.tools</pluginGroup>
</pluginGroups>

</settings>

以下のコマンドでプロジェクトを作成します。このあとの作業でもmvnコマンドをよく使いますが、最初の作業ではjarファイルを大量にダウンロードするので気長に待ちましょう。

mvn hpi:create

途中でgroupIdとartifactIdを訊かれますが、ここではそれぞれorg.exampleとsampleにしました。

sampleディレクトリができるのでそこに移動して

mvn package

とすればビルドできてsample\target以下にHudsonプラグイン(sample.hpi)が出来ます。

mvn hpi:run

とすればJettyが起動し作成したプラグインを含んだHudsonが起動します。

ブラウザで http://localhost:8080 にアクセスして「Hudsonの管理」->「システムの設定」を見ると下記のように作成したプラグインの設定情報が表示されています。

フリースタイル・プロジェクトのビルドで新規ジョブを作成すると以下のような設定画面が表示されます。

Say hello worldを選択しテキストエリアに適当な文字を入れます。ここで入れた文字が出力されます。

保存してビルドを実行後、コンソール出力をみると下記のようにHello, hoge!と出力されています。

次にEclipseデバッグしてみます。

まず以下のコマンドでEclipseに取り込めるようにします。

mvn -DdownloadSources=true eclipse:eclipse

EclipseClasspath VariablesでM2_REPOを設定し、プロジェクトをimportすると下記のようになります。

ちなみにReferenced Librariesはいっぱいあります。70個以上ある。

HelloWorldBuilder#performメソッドがHudsonでのビルド実行時に呼ばれます。

HelloWorldBuilder.java

package org.example;

import hudson.Launcher;
import hudson.util.FormFieldValidator;
import hudson.model.Build;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.tasks.Builder;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.QueryParameter;

import javax.servlet.ServletException;
import java.io.IOException;

/**
 * Sample {@link Builder}.
 *
 * <p>
 * When the user configures the project and enables this builder,
 * {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked
 * and a new {@link HelloWorldBuilder} is created. The created
 * instance is persisted to the project configuration XML by using
 * XStream, so this allows you to use instance fields (like {@link #name})
 * to remember the configuration.
 *
 * <p>
 * When a build is performed, the {@link #perform(Build, Launcher, BuildListener)} method
 * will be invoked. 
 *
 * @author Kohsuke Kawaguchi
 */
public class HelloWorldBuilder extends Builder {

    private final String name;

    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     */
    public String getName() {
        return name;
    }

    public boolean 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+"!");
        return true;
    }

    public Descriptor<Builder> getDescriptor() {
        // see Descriptor javadoc for more about what a descriptor is.
        return DESCRIPTOR;
    }

    /**
     * Descriptor should be singleton.
     */
    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

    /**
     * Descriptor for {@link HelloWorldBuilder}. Used as a singleton.
     * The class is marked as public so that it can be accessed from views.
     *
     * <p>
     * See <tt>views/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt>
     * for the actual HTML fragment for the configuration screen.
     */
    public static final class DescriptorImpl extends Descriptor<Builder> {
        /**
         * To persist global configuration information,
         * simply store it in a field and call save().
         *
         * <p>
         * If you don't want fields to be persisted, use <tt>transient</tt>.
         */
        private boolean useFrench;

        DescriptorImpl() {
            super(HelloWorldBuilder.class);
        }

        /**
         * Performs on-the-fly validation of the form field 'name'.
         *
         * @param value
         *      This receives the current value of the field.
         */
        public void doCheckName(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException {
            new FormFieldValidator(req,rsp,null) {
                /**
                 * The real check goes here. In the end, depending on which
                 * method you call, the browser shows text differently.
                 */
                protected void check() throws IOException, ServletException {
                    if(value.length()==0)
                        error("Please set a name");
                    else
                    if(value.length()<4)
                        warning("Isn't the name too short?");
                    else
                        ok();

                }
            }.process();
        }

        /**
         * This human readable name is used in the configuration screen.
         */
        public String getDisplayName() {
            return "Say hello world";
        }

        public boolean configure(StaplerRequest req, JSONObject o) throws FormException {
            // to persist global configuration information,
            // set that to properties and call save().
            useFrench = o.getBoolean("useFrench");
            save();
            return super.configure(req);
        }

        /**
         * This method returns true if the global configuration says we should speak French.
         */
        public boolean useFrench() {
            return useFrench;
        }
    }
}

デバッグするにはまず以下のコマンドをうったあとにmvn hpi:runしてHudsonを起動します。

set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n

ブレークポイントはとりあえずHelloWorldBuilder#performに打ちます。

Eclipseの「Run」-「Debug Configurations」でRemote Java Applicationを選択してDebugボタンを押します。

ブラウザからジョブのビルドを実行すると、下記のようにperformメソッドで止まります。

まとめ

チュートリアルの実行とEclipseでのデバッグを取り上げてみました。
参考になりそうなエントリは下記でしょう。とくに一番上のエントリは本家からもリンクされておりかなり参考になると思います。

ただHudsonプラグイン開発の情報はそれほど多くないのであとは他のプラグインのソースを見ることになると思います。
とりあえずHudsonプラグイン開発にはMaven2とWebフレームワークStaplerとスクリプトエンジンJellyの知識が必要なようです。