C#でもUbuntu+Docker+Jenkins+GitBucket+MonoでCIしたい(CI編)

はじめに

前回はJenkinsとGitBucketの環境を構築しました。 今回は実際にJenkinsでビルドとテストを実行させてみましょう。

プロジェクトの作成

まずはCIをブンブン回すプロジェクトを作成します。

テストの実装及びJenkinsでのテストの実行のために

  • NUnit
  • NUnit.Runners

をNuGet経由でインストールし、リポジトリ直下のnugetディレクトにNuGetの実行ファイルを突っ込んでおきます。

というわけで今回使ったのがこちら。 なお、弊社はなんとなくGitHubリポジトリが不用意に増えるのが嫌なのでGitHubに上げてるのは汎用リポジトリに突っ込んじゃってますが、今回の内容ではHelloMonoCiが独立したリポジトリだと思って下さい。

github.com

GitBuckerリポジトリの作成

とりあえずリポジトリを作成して、先程作成したVisual Studioプロジェクトを突っ込んでおきましょう。

Jenkinsビルドジョブの作成

ジョブの作成からフリースタイル・プロジェクトのビルドを選択します。

設定画面ではとりあえずこんな感じに設定します。

f:id:jyuch:20161012220806p:plain

名前は自由で構いません。

f:id:jyuch:20161012220807p:plain

Monoはスレーブノードにしか(今回の構成では)インストールされていないので、スレーブノードで実行されるように実行ノードの制限はかけておきましょう。

f:id:jyuch:20161012220810p:plain

この辺は特にこれと言った注意点はありません。

f:id:jyuch:20161012220812p:plain

あとでweb hookを引っ掛けるので、Build when a change is pushed to GitBucketのチェックを入れておきましょう。

f:id:jyuch:20161012220750p:plain

この辺は好みでどうぞ。

f:id:jyuch:20161012220757p:plain

ビルドの最初のステップでNuGet経由でライブラリを復元します。

mono nuget/nuget.exe restore HelloMonoCi/HelloMonoCi.sln


f:id:jyuch:20161012220803p:plain

リリースビルドを実行します。

xbuild /p:Configuration=Release HelloMonoCi/HelloMonoCi.sln


f:id:jyuch:20161012220804p:plain

後続のテストリザルドパブリッシャー殿がNUnit3形式だと『無☆理』と言って死ぬので--result:TestResult.xml;format=nunit2を指定します。

mono HelloMonoCi/packages/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe \
    HelloMonoCi/HelloMonoCi.Test/HelloMonoCi.Test.csproj \
    "--config=Release" "--result:TestResult.xml;format=nunit2" 


f:id:jyuch:20161012220816p:plain

テストリザルドファイルを指定します。

サーバのローカルアドレス192.168.0.10だとかGitBucketのユーザがurikkだとかどうでもいい情報を全世界に垂れ流しつつ、とりあえずJenkinsはこれでokです。

GitBucker Web Hook

GitBucketのリポジトリに戻ってweb hookの設定をします。

f:id:jyuch:20161012224239p:plain

Payload URLにはhttp://192.168.0.10:8090/gitbucket-webhook/を指定して、あとはデフォルトで大丈夫です。

ここまで来ればもう終わり。あとはGitBuckerにプッシュするだけで優秀な執事がビルドとテストを回してくれます。

おわりに

まぁ、ふつーにWindowsでビルドサーバーを立てるかAppVeyorを使ったほうが圧倒的に楽ですね。 公開前に読み返して『これ何かの意味があるのかなぁ?』と頭を抱えています。

あと、全く関係がないのですが弊社は.NETの単体テストライブラリはMSTestしか使ったことがなく、またMSTestで事足りてたので他のライブラリを使うという発想自体がなく今までNUnitを触ったことすらなかったのですが、NUnitって良いですね。

特にTestCaseによるパラメータ化テストはMSTestには無い*1のでそのうち追加されたらいいな〜とおもいました。

おわり

*1:外部ライブラリを導入すれば同じようなことはできるっぽい

C#でもUbuntu+Docker+Jenkins+GitBucket+MonoでCIしたい(環境構築編)

はじめに

今回はLinuxサーバ環境で半ば無理やりCI環境を構築して.NETプラットフォームのアプリケーションをCIするという誰が得をするのだろうという内容の話です。

具体的に言うと、GitBucketでソースコードをホストして、GitBucketにプッシュされたときにJenkinsでビルド+テスト実行を行うというごくありふれた内容です。 こいつらをDocker上に構築します。

   +-------------------+                    +-------------------+
   | GitBucket         | -- (2)web hook --> | Jenkins           |-----
-->| 192.168.0.10:8080 |                    | 192.168.0.10:8090 |    |
|  +-------------------+ ----------         +-------------------+<-- |
|                                 |                                | |
| (1)push                         | (4)pull                        | | (3)order
|                                 |               (6)return result | |
|                                 |                                | |
|  +-------------------+          |         +-------------------+  | |
---| Windows Client    |          --------->| Jenkins Slave     |--- |
   +-------------------+                    | 192.168.0.10:9000 |    |
                                            +-------------------+<----
                                              (5)build & test

余談ですが、最初はいつも通り『VB.NETでも〜』という内容にしようと思ったのですが、xbuildvbprojをどうしてもビルドしたくないと言うので急遽C#殿にお越しいただきました。

環境

とりあえず以下の環境を使用しました。

セットアップ

Docker・docker-compose

公式マニュアルを以下略。

Jenkins・GitBucket・Jenkinsビルドスレーブ環境構築

まずはGitBuckt環境を構築しましょう。

こちらは特に変わったことはしてません。openjdk-8-jre-alpineをベースにGitBucketのバイナリをダウンロードして実行しているだけです。

FROM java:openjdk-8-jre-alpine

ADD https://github.com/gitbucket/gitbucket/releases/download/4.5/gitbucket.war /opt/gitbucket/gitbucket.war

VOLUME /srv/gitbucket

EXPOSE 8080

CMD ["java", "-jar", "/opt/gitbucket/gitbucket.war", "--gitbucket.home=/srv/gitbucket"]

Jenkinsは公式イメージを使用しています。

ただ、公式イメージにはMonoの環境が入っておらず、途中でユーザを切り替えてるせいで公式イメージをベースにMono環境を追加するという手法も使えなかったので、別途Monoをインストールしたコンテナをスレーブとして使用しています。(クソポイントその1)

ubuntu:16.04をベースにビルド及びテスト実行のためのMono、Jenkinsスレーブデーモンを動かすためのJava、Jenkinsのスレーブとして動作させるためにDockerでは禁忌とされているSSHサーバをインストールしています。(クソポイントその2)

何処かで見たことがあると思ったあなた。素晴らしいです。先頭部分はbuildpack-deps:jessie-scmからパクって拝借してきました。

あとはNuGetにパッケージの復元を許可するためにEnableNuGetPackageRestoreTRUEに設定しています。

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
        wget \
    && rm -rf /var/lib/apt/lists/*

RUN apt-get update && apt-get install -y --no-install-recommends \
        bzr \
        git \
        mercurial \
        openssh-client \
        subversion \
        \
        procps \
    && rm -rf /var/lib/apt/lists/*

RUN set -x \
    && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \
    && echo "deb http://download.mono-project.com/repo/debian wheezy main" | tee /etc/apt/sources.list.d/mono-xamarin.list \
#   && echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list \
    && echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | tee -a /etc/apt/sources.list.d/mono-xamarin.list \
    && apt-get update \
    && apt-get -y install mono-complete openjdk-8-jdk \
    && rm -rf /var/lib/apt/lists/*

RUN set -x \
    && apt-get update \
    && apt-get install -y openssh-server \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /var/run/sshd
RUN echo 'root:screencast' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile

ENV EnableNuGetPackageRestore TRUE

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

あとはこれらのコンテナを上げたり下げたりビルドする為のdocker-compose.ymlをでっち上げれば構築は完了です。

version: '2'
services:
  gitbucket-websrv:
    build: './gitbucket'
    ports:
      - '8080:8080'
    volumes:
      - '/srv/mono-ci/gitbucket:/srv/gitbucket'
  jenkins:
    image: 'jenkins:2.19.1'
    ports:
      - '8090:8080'
      - '50000:50000'
    volumes:
      - '/srv/mono-ci/jenkins:/var/jenkins_home'
  jenkins-slave-mono:
    build: './jenkins-slave-mono'
    ports:
      - '9000:22'

ちなみにこれらの環境を構築するだけなら10秒*1で終わります。

git clone https://github.com/jyuch/mono-ci.git
cd mono-ci
docker-compose up

github.com

Jenkins・GitBucket初期設定

GitBucket

GitBucketにroot:rootでログインし、自身の作業用アカウントとJenkins用のアカウントを作成しましょう。 Jenkinsアカウントはコントリビューターになれれば良いので管理者でなくても大丈夫です。

Jenkins

アカウント登録後のプラグイン選択画面ではとりあえずおすすめをインストールしておきましよう。

その後、管理画面より以下のプラグインをインストールします。

  • GitBucket Plugin
  • NUnit plugin

その後はJenkinsの管理 > ノードの管理よりスレーブノードを登録します。 今回の構成ですとSSHポートは9000なのでそこだけは注意が必要です。

最後にCSRF対策チェックボックスを外します。セキュリティを切るのはいささか気が引けますがこの作業をしないとGitBucketからのweb hookが通らないのでまぁ仕方ないのかなと自分の中で折り合いを付けながらチェックボックスを外すか別のいい感じの方法を探して下さい。いい感じの方法がありましたら教えてください。

ここまでで初期セットアップは完了です。お疲れ様でした。

*1:イメージのプル・ビルド時間を除く

VB.NETでも制約ソルバーを用いて数独を解きたい その2

はじめに

前回は地獄のSMT-LIBコピペ祭りを経て、z3で数独を解くことに成功しました。 今回はVBから制約を発行してz3に数独を解かせてみましょう。

.NETバインディング

z3には.NETのバインディングが用意されており、P/Invoke祭りやCOM祭りをやらなくともz3の機能が利用できます。やったね

まぁ、CodeDOMからコードを生成するのによく似たつらみが待ち構えているので結局アレですけど。

z3のGitHubページからライブラリをダウンロードして、Microsoft.Z3.dllを参照に加えましょう。 また、Microsft.Z3.dllは単なるバインディングなので、libz3.dllも一緒にコピーしておく必要があります。

あと、libz3は(たぶん)ネイティブなので、ダウンロードしたアーキテクチャに合わせて.NETのアーキテクチャを合わせておく必要があります。 WOW64の加護により64bitOSでも32bitアプリケーションが動くので今回はx86を使用しています。 アプリケーションに割り当て可能なメモリ量の制約や若干のオーバーヘッドなどがありますが、今回は目をつぶりましょう。

余談ですが、x86のネイティブライブラリを使用しているプロジェクトで、Debugビルドはx86に直してあるのにReleaseはAnyCPUのままになっていてReleaseすると無事に64bitOSで死ぬみたいな間抜けな事があったので気を付けましょう。

数独

正直なことを申しますと、z3の公式サンプルに数独を解くコードがあるのでそちらを参考にしました。

Dim opt = New Dictionary(Of String, String)
opt("proof") = "true"

Using c = New Context(opt)
    Dim solver = c.MkSolver()

    Dim cells(8)() As IntExpr

    ' x_1_1 から x_9_9のシンボルを生成
    For i = 0 To 8
        Dim r(8) As IntExpr
        cells(i) = r

        For j = 0 To 8
            cells(i)(j) = CType(c.MkConst(c.MkSymbol($"x_{i + 1}_{j + 1}"), c.IntSort), IntExpr)
        Next
    Next

    ' (assert (and (<= 1, x_?_?) (>= x_?_? 9)))
    For i = 0 To 8
        For j = 0 To 8
            solver.Assert(
                c.MkAnd(
                    c.MkLe(c.MkInt(1), cells(i)(j)),
                    c.MkLe(cells(i)(j), c.MkInt(9))))
        Next
    Next

    ' (assert (distinct x_?_1 ... x_?_9))
    For i = 0 To 8
        solver.Assert(c.MkDistinct(cells(i)))
    Next

    ' (assert (distinct x_1_? ... x_9_?))
    For j = 0 To 8
        Dim col(8) As IntExpr
        For i = 0 To 8
            col(i) = cells(i)(j)
        Next

        solver.Assert(c.MkDistinct(col))
    Next

    ' (assert (distinct 3x3のあのマス))
    For i0 = 0 To 2
        For j0 = 0 To 2
            Dim square(8) As IntExpr

            For i = 0 To 2
                For j = 0 To 2
                    square(3 * i + j) = cells(3 * i0 + i)(3 * j0 + j)
                Next
            Next
            solver.Assert(c.MkDistinct(square))
        Next
    Next

    Dim inst =
    {
        {0, 0, 5, 3, 0, 0, 0, 0, 0},
        {8, 0, 0, 0, 0, 0, 0, 2, 0},
        {0, 7, 0, 0, 1, 0, 5, 0, 0},
        {4, 0, 0, 0, 0, 5, 3, 0, 0},
        {0, 1, 0, 0, 7, 0, 0, 0, 6},
        {0, 0, 3, 2, 0, 0, 0, 8, 0},
        {0, 6, 0, 5, 0, 0, 0, 0, 9},
        {0, 0, 4, 0, 0, 0, 0, 3, 0},
        {0, 0, 0, 0, 0, 9, 7, 0, 0}
    }

    For i = 0 To 8
        For j = 0 To 8
            If inst(i, j) <> 0 Then
                solver.Assert(c.MkEq(c.MkInt(inst(i, j)), cells(i)(j)))
            End If
        Next
    Next

    If solver.Check() = Status.SATISFIABLE Then
        Dim m = solver.Model

        For Each i In cells
            For Each j In i
                Console.Write($"{m.Evaluate(j)} ")
            Next
            Console.WriteLine()
        Next
    Else
        Console.WriteLine("Failed to solve sudoku")
    End If

End Using

おわりに

z3で数独を解くこと自体は前回の時点で出来てるので今回は特に感動もないのですが、まぁなるほどなといった感じです。 しかしまぁ、慣れは必要ですが問題を数式で表現さえ出来ればアルゴリズムを実装せずに解けるというのはかなり強力ですね。

公式サンプルではほとんどの制約を(and ...)で連結してAssertを2つにまで削減してました。 内部実装的にこうしたほうが早いというのがあるのかもしれませんが、今回は分かりやすさの為に条件一つ一つにAssertを適用しました。

github.com

おわり