前回のUbuntu12.04にGITLABをインストールしました。につづいて、今回はさらにGITLAB-CIをインストールし、PHPプロジェクトのテストビルドまで書きたいと思います。

弊社は前からJenkinsを使っています。Jenkinsでもよいですが、ちょっとJava様系なのでメモリがかなり食っていますね。今回せっかくGitLabを入れたので、GitLab-CIをセットで入れたいと思っています。

 

GitLab-CIのインストール

 

基本的には本家サイトのインストールガイドに従ってやれば問題ないかと思います。弊社環境に合わせて、またちょっと工夫を書きたいと思います。

本家サイトのインストールガイド:

https://github.com/gitlabhq/gitlab-ci/blob/master/doc/install/installation.md

まず、GitLabとGitLab-CIは連携しているので、ファイル書き込みパーミッションとかうるさいのでGitLab-CIのユーザーもGitLabのインストールユーザーと同じくします。つまり、GitLab-CIもgitユーザーでインストールしていきます。

すると、config系のファイルを変更しないといけません。

例えば、

git@www1167uf:~/gitlab-ci/config$ diff unicorn.rb unicorn.rb.example 
24c24
< working_directory "/home/git/gitlab-ci" # available in 0.94.0+
---
> working_directory "/home/gitlab_ci/gitlab-ci" # available in 0.94.0+
28,29c28,29
< listen "/home/git/gitlab-ci/tmp/sockets/gitlab-ci.socket", :backlog => 64
< listen "127.0.0.1:8082", :tcp_nopush => true
---
> listen "/home/gitlab_ci/gitlab-ci/tmp/sockets/gitlab-ci.socket", :backlog => 64
> listen "127.0.0.1:8080", :tcp_nopush => true
35c35
< pid "/home/git/gitlab-ci/tmp/pids/unicorn.pid"
---
> pid "/home/gitlab_ci/gitlab-ci/tmp/pids/unicorn.pid"
40,41c40,41
< stderr_path "/home/git/gitlab-ci/log/unicorn.stderr.log"
< stdout_path "/home/git/gitlab-ci/log/unicorn.stdout.log"
---
> stderr_path "/home/gitlab_ci/gitlab-ci/log/unicorn.stderr.log"
> stdout_path "/home/gitlab_ci/gitlab-ci/log/unicorn.stdout.log"

そんなにインストールが簡単かと思ったら、早速GitLab-CIを起動します。しかし、ここで問題があります。GitLabとGitLab-CIは同じgitユーザーで起動されているため、現起動スクリプトは競合しました。その結果、どれかのサービスを再起動すると別のサービスのSidekiqデーモンが落ちてしまいます。これを解消するためには、起動スクリプトを変更します。これはGitLab側とGitLab-CI側は同じことをやります。

script/background_jobsファイルを編集します。

git@www1167uf:~/gitlab/script$ diff background_jobs background_jobs.orig 
21,27c21
< #  pkill -u $gitlab_user -f sidekiq
<   if [ -f "$sidekiq_pid_path" ]; then
<     spid=$(cat "$sidekiq_pid_path")
<   else
<     spid=0
<   fi
<   kill -0 "$spid" 2>/dev/null
---
>   pkill -u $gitlab_user -f sidekiq

これでうまくいくはずです。GitLab-CI画面が出て、GitLabのユーザーでログインできたら、一旦インストール完了です。

 

 

 

GitLab-CI-Runnerをインストール

 

GitLab-CI-Runnerはバックグラウンドで動いていて、実際にビルドスクリプトを実行する役割を果たします。もうここに来たら、イントール自体はあまり難しくありません。本家サイトのインストールガイドに従ってインストールします。

本家サイトのインストールガイド

https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/README.md

しかし、なぜか弊社環境では、起動スクリプトで起動がうまくできませんでした。意味不明ですけど、ちょっと我慢して手動で以下のようにサーバーが再起動されるたびに、GitLab-Ci-Runnerを起動しています。

su git
cd /home/git/gitlab-ci-runner/
nohup bundle exec ./bin/runner &

ここで、GitLab-CI画面でRunnerの確認ができます。

 

PHPプロジェクトをビルド

 

GitLabとGitLab-CIの連携がよく出来ているので、GitLab側でプロジェクトを作成したら、自動的にGitLab−CI側にもSyncされています。GitLab-CIトップ画面にすべてそのユーザーが見えるすべてのプロジェクトが表示されます。ビルド対象にするには、単純に右の「Add」をクリックするだけです。非常に簡単です。

PHPプロジェクトのビルドのための設定は極めて簡単です。

 

Build stepsにはantにするだけです。下には実際のAntスクリプトの内容を掲載致します。

 

また、ビルド失敗時にはエラーメールが飛ばされるように設定するといいですね。いつ、なぜ失敗するかエンジニアに知らせて、直してもらうことにすごく意味が成します。

これで弊社のYiiFramework PHPプロジェクトのビルドファイル内容はこんな感じです。build.xmlファイルはプロジェクトのルートフォルダに配置すれば、GitLab-CIでantを実行してくれるだけでOKです。

<?xml version="1.0" encoding="UTF-8"?>

<project name="PHP Project CI" default="build">
    <!--<target name="build"
         depends="prepare,phpunit,lint,phploc,pdepend,phpmd-ci,phpcs-ci,phpcpd,phpdox,phpcb,post-build"/> -->
    <target name="build"
         depends="prepare,phpunit,post-build"/>
    
    <target name="build-parallel"
         depends="prepare,lint,tools-parallel,phpunit,phpcb"/>
        
    <target name="tools-parallel" description="Run tools in parallel">
        <parallel threadCount="2">
            <sequential>
                <antcall target="pdepend"/>
                <antcall target="phpmd-ci"/>
            </sequential>
            <antcall target="phpcpd"/>
            <antcall target="phpcs-ci"/>
            <antcall target="phploc"/>
            <antcall target="phpdox"/>
        </parallel>
    </target>

    <target name="clean" description="Cleanup build artifacts">
        <delete dir="${basedir}/build/api"/>
        <delete dir="${basedir}/build/code-browser"/>
        <delete dir="${basedir}/build/coverage"/>
        <delete dir="${basedir}/build/logs"/>
        <delete dir="${basedir}/build/pdepend"/>
    </target>

    <target name="prepare" depends="clean" description="Prepare for build">
        <tstamp>
            <format property="now.time" pattern="yyyyMMddhhmmssSSS"/>
        </tstamp>
        
        <mkdir dir="${basedir}/build/api"/>
        <mkdir dir="${basedir}/build/code-browser"/>
        <mkdir dir="${basedir}/build/coverage"/>
        <mkdir dir="${basedir}/build/logs"/>
        <mkdir dir="${basedir}/build/pdepend"/>
        <mkdir dir="${basedir}/build/phpdox"/>
        <!-- Make db migrate -->        
        <copy file="${basedir}/build/db.php" tofile="${basedir}/protected/config/db.php" overwrite="true"/>
        <!-- <copy file="${basedir}/build/params.php" tofile="${basedir}/protected/config/params.php" overwrite="true"/> -->
        <copy file="${basedir}/build/WebTestCase.php" tofile="${basedir}/protected/tests/WebTestCase.php" overwrite="true"/>
        <copy file="${basedir}/build/cache.php" tofile="${basedir}/protected/config/cache.php" overwrite="true"/>
        <exec executable="php">
            <arg line="${basedir}/protected/yiic" />
            <arg line="migrate --interactive=0" />
        </exec>
        <!-- Chmod 777 for assets and protected/runtime -->
        <chmod dir="${basedir}/assets" perm="777"  />
        <chmod dir="${basedir}/protected/runtime" perm="777" />
    </target>

    <target name="post-build" description="Execute some things after build">
        <!-- Move libs folders from /tmp -->
        <!--
        <move todir="${basedir}/protected/vendor">
                <fileset dir="/tmp/vendor.${now.time}"/>
        </move>
        <move todir="${basedir}/protected/extensions">
                <fileset dir="/tmp/extensions.${now.time}"/>
        </move>
        -->        
    </target>

    <target name="lint" description="Perform syntax check of sourcecode files">
        <apply executable="php" failonerror="true">
            <arg value="-l" />

            <fileset dir="${basedir}/protected">
                <include name="commands/**/*.php" />
                <include name="components/**/*.php" />
                <include name="config/**/*.php" />
                <include name="controllers/**/*.php" />
                <include name="messages/**/*.php" />
                <include name="migrations/**/*.php" />
                <include name="models/**/*.php" />
                <include name="modules/**/*.php" />
                <include name="tests/**/*.php" />
                <include name="views/**/*.php" />
                <modified />
            </fileset>
        </apply>
    </target>

    <target name="phploc" description="Measure project size using PHPLOC">
        <exec executable="phploc">
            <arg value="--log-csv" />
            <arg value="${basedir}/build/logs/phploc.csv" />
            <arg path="${basedir}/protected" />
        </exec>
    </target>

    <target name="pdepend" description="Calculate software metrics using PHP_Depend">
        <exec executable="pdepend">
            <arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
            <arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
            <arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
            <arg path="${basedir}/protected" />
        </exec>
    </target>

    <target name="phpmd"
         description="Perform project mess detection using PHPMD and print human readable output. Intended for usage on the command line before committing.">
        <exec executable="phpmd">
            <arg path="${basedir}/protected" />
            <arg value="text" />
            <arg value="${basedir}/build/phpmd.xml" />
        </exec>
    </target>

    <target name="phpmd-ci" description="Perform project mess detection using PHPMD creating a log file for the continuous integration server">
        <exec executable="phpmd">
            <arg path="${basedir}/protected" />
            <arg value="xml" />
            <arg value="${basedir}/build/phpmd.xml" />
            <arg value="--reportfile" />
            <arg value="${basedir}/build/logs/pmd.xml" />
        </exec>
    </target>

    <target name="phpcs"
         description="Find coding standard violations using PHP_CodeSniffer and print human readable output. Intended for usage on the command line before committing.">
        <exec executable="phpcs">
            <arg value="--standard=${basedir}/build/phpcs.xml" />
            <arg path="${basedir}/protected" />
        </exec>
    </target>

    <target name="phpcs-ci" description="Find coding standard violations using PHP_CodeSniffer creating a log file for the continuous integration server">
        <exec executable="phpcs" output="/dev/null">
            <arg value="--report=checkstyle" />
            <arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
            <arg value="--standard=${basedir}/build/phpcs.xml" />
            <arg path="${basedir}/protected" />
        </exec>
    </target>

    <target name="phpcpd" description="Find duplicate code using PHPCPD">
        <exec executable="phpcpd">
            <arg value="--log-pmd" />
            <arg value="${basedir}/build/logs/pmd-cpd.xml" />
            <arg path="${basedir}/protected" />
        </exec>
    </target>

    <target name="phpdox" description="Generate API documentation using phpDox">
        <exec executable="phpdox">
            <arg line="--file ${basedir}/build/phpdox.xml" />
        </exec>
    </target>

    <target name="phpunit" description="Run unit tests with PHPUnit">
        <exec executable="phpunit" failonerror="true">
            <arg line="--configuration ${basedir}/protected/tests/phpunit.xml" />
            <arg line="--log-junit ${basedir}/build/logs/junit.xml" />
            <arg line="--coverage-clover ${basedir}/build/logs/clover.xml" />
            <arg line="${basedir}/protected/tests/unit" />
        </exec>
        <exec executable="phpunit" failonerror="true">
            <arg line="--configuration ${basedir}/protected/tests/phpunit.xml" />
            <arg line="--log-junit ${basedir}/build/logs/junit1.xml" />
            <arg line="--coverage-clover ${basedir}/build/logs/clover1.xml" />
            <arg line="${basedir}/protected/tests/functional" />
        </exec>          
        <!-- Move libs folders to /tmp -->
        <!--
        <move todir="/tmp/vendor.${now.time}">
                <fileset dir="${basedir}/protected/vendor"/>
        </move>
        <move todir="/tmp/extensions.${now.time}">
                <fileset dir="${basedir}/protected/extensions"/>
        </move>
        -->
    </target>

    <target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
        <exec executable="phpcb">
            <arg value="--log" />
            <arg path="${basedir}/build/logs" />
            <arg value="--source" />
            <arg path="${basedir}/protected" />
            <arg value="--output" />
            <arg path="${basedir}/build/code-browser" />
        </exec>
    </target>
</project>

実際にはビルド結果はこんな感じ

■成功時

 

■失敗時

その際はエラーメールも飛ばされます。

 

最後の考察

 

GitLabとGitLab-CIを導入して間もないですが、すごくよいと思います。なぜならば

  • 管理は簡単。GITコマンドはいちいち打たなくてもOK
  • GITHUBフローを利用して開発できる(Merge Request、Code Review等)
  • GitLab-CIでどのコミットに関しても、どのブランチに関してもビルドが自動的に走らせる。Merge RequestのブランチはまずビルドOKが条件

等です。

現在弊社の開発フローはこんな感じです。

  1. プロジェクトスタート時、マネージャーはRedmineプロジェクトを作成する
  2. 開発リーダはプロジェクトレポジトリをGITLABにて作成し、参加開発者をメンバーにする
  3. 開発用のdevelopブランチとリリース用のmasterブランチの2つが作成される
  4. 開発者はdevelopブランチから自分用ブランチを作成する。開発は基本自分用ブランチ上で開発する。
  5. 機能の開発が終わったら、担当者は現developブランチから自分用ブランチにマージしてからMerge Requestを出す
  6. Merge RequestがOKでした、開発コードはdevelopブランチにマージされる

ルールとしては、開発者は自分のコミットでCIのビルドが壊れたら、絶対に責任を持って対処します。また、その状態であれば、Merge Requestができないことになっています。

 

個人としては、GitLabを使うことで、品質向上できるのはいくつか考えられます。

  • しっかりCIビルドOKしたら、コードがマージされる。壊れたコードの事前発見
  • コードレビューすることで、コードの品質向上を図る。
  • コードの見える化によって、開発者でも自分のコードが見られるから恥せずに書かないと思うようになる
  • Merge Request機能により、しっかりと機能とその実装コードがレビュー、テストされるから品質が上がる

等です。

GITLABに感謝です。これからも使わせていただきます。


Leave a Comment

Fields with * are required.