entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

ピクシブ百科事典に静的解析のCIを導入した話

ピクシブ百科事典に静的解析のCIを導入した話

こんにちは、4月からピクシブでアルバイトをしているmdanです。 今回はピクシブ百科事典に静的解析のCIを導入した話を書きます。

これまでピクシブ百科事典には解析ツールが導入されていませんでした。 といってもピクシブ百科事典のリリース頻度はそこまで多くなく、PhpStormが静的解析してくれるので大きな問題はありませんでした。 しかし社内では様々なエディタが使われているので万全ではありませんし、リリースの際は本当にエラーがないのか不安が残ります。 このような理由から静的解析のCIを導入することにしました。

静的解析ツールの導入

静的解析ツールとは、コードを実行せずにプログラムの性質を解析するツールのことです。コードを実行しない解析なのでテストコードを書く必要がなく、比較的簡単に使うことができます。

ピクシブ百科事典はPHPで開発されており、PHPの変数やメソッド、構文などをチェックする静的解析ツールとしてはPHPStanとPhanがあります。

PHPStanはautoloadなどの実行時情報も考慮して解析してくれるため、解析の度に全てのコードを読み込むPhanと比較して高速に動作します。

今回はマージリクエストごとに変更箇所だけチェックしたかったので、PHPStanを導入しました。

またピクシブ百科事典は専任の開発チームがいるわけではなく、社内の多くのエンジニアがコミットする可能性があります。ピクシブではすでに多くのサービスの開発でPHPStanのCIを導入しており、他のサービスと開発体験を揃えたいというのもPHPStanを選んだ理由です。

PHPStanとPhanについては以下の資料に詳しく書いてあります。

PHPStanはcomposerで簡単にインストールができて、以下のコマンドで実行できます。

phpstan analyze /path/to/directory_or_file

PHPStanではどのレベルまで解析するのかを0~7の8段階まで設定できます。 それぞれのレベルがどこまで解析するのかは、リポジトリ内のconfを見るとなんとなく掴めると思います。

https://github.com/phpstan/phpstan/tree/master/conf

もちろんレベルを上げるほど厳格にチェックしてくれるのですが、今回は現状のコードのエラー数を考慮してレベル3に設定しました。

解析対象となるグローバル関数や定数は、 autoload に予めロードしておくか、クラスをオートロードできるようにしておく必要があります。composer以外でautoloadしている場合は明示的に記述が必要です。 これらのオプションは引数としても渡せますが、実行時のカレントディレクトリにphpstan.neon または phpstan.neon.dist というファイルがあれば設定ファイルとして読み込んでくれます。

ピクシブ百科事典ではプロジェクトの構造上Composerのオートロードを使っていないので、 vendor/autoload.php ではなく autoloader_for_static_analysis.php というファイルにオートロードできないファイルの読み込みを集めています。 (メンターのtadsanはこのファイルをPhpactorという別のツールのためにも使っています)

# phpstan.neon.dist
parameters:
  level: 3
  autoload_files:
    - %currentWorkingDirectory%/autoloader_for_static_analysis.php

これでPHPStanで静的解析ができるようになりました。 次はPHPStanをマージリクエストごとに実行するために、CIツールを導入します。

CIの導入

今回はCI(Continuous Integration)ツールとしてJenkinsを使用しました。 CIツールを使うことで、リポジトリへのプッシュやマージリクエストの作成など任意のタイミングで、タスクを実行することができます。 Jenkinsを選んだのは、静的解析ツールと同様で他サービスと開発体験を揃えたいという理由からです。

解析対象ファイルの抽出

JenkinsではGitlab Merge Request Builder Pluginを使うと、マージリクエストのターゲットブランチやソースブランチを取得することができます。 マージリクエストのブランチ名が取得できるというのが、今回重要な部分になります。

PHPStanで静的解析の対象を絞り込むには、対象ディレクトリ・ファイルを指定する必要があります。 そこで、git diffで変更箇所のファイル名のみを抽出することにしました。 --diff-filterは追加や削除などの条件で差分を絞り込むオプション、--name-onlyは差分があるファイル名を表示するオプションです。 このファイル名をxargsでPHPStanに引数として渡すことで、変更箇所のみを静的解析することができます。

git diff --name-only --diff-filter=ACMR "${gitlabTargetBranch}"..."${gitlabSourceBranch}" -- '*.php' |
    xargs -r phpstan analyze --no-interaction -c phpstan.neon.dist

また、メソッド名やメソッドの引数に変更があった場合、メソッドの呼び出し箇所でエラーが出る可能性があります。 そこで変更箇所のメソッド名を抽出し、そのメソッドが呼び出されているファイルもPHPStanで解析することにしました。

まずgit diffの出力結果から変更箇所のシグネチャ(メソッド名と引数)を抽出します。 以下のような出力があった場合、old_function($arg)new_function($arg)がシグネチャになります。

$ git diff "${gitlabTargetBranch}"..."${gitlabSourceBranch}" -- '*.php'
diff --git a/Sample.php b/Sample.php
index xxxxxxx..yyyyyyy zzzzzz
--- a/Sample.php
+++ b/Sample.php
@@ -488,7 +488,7 @@ class Sample
-    private function old_function($arg)
+    private function new_function($arg)

変更前のシグネチャ(行頭が-)と変更後のシグネチャ(行頭が+)を比較して、同じシグネチャがあればメソッド移動等の変更なので除外します。 そして、残りのシグネチャに含まれるold_functionnew_functionといったメソッド名を抽出します。

メソッド名が抽出できたら、git-grepを使ってそのメソッドを使用しているファイルを抽出します。 git-grepはgit管理されているファイルのみをgrepするコマンドで、-lオプションを付けることで該当箇所のファイル名のみを出力することができます。 これで変更箇所のメソッドが呼び出されているファイルを解析することができます。 異なるメソッドが同じファイルで呼び出されている可能性があるため、uniqコマンドで重複を除外しています。

echo ${methods} |
  xargs -n1 -IXXX git grep -lw XXX |
  uniq |
  xargs phpstan analyze --no-interaction -c php.neon.dist

出力結果の絞り込み

ここまでで解析対象のファイルを絞り込むことができました。しかし、すでに動いているサービスに静的解析を導入する場合、多くのエラーが出ることが予想されます。

そのため、エラーの中から自分の変更箇所を探すのが困難になります。ピクシブでは変更箇所のエラーにハイライトを付けることでこの問題を解消しています。

詳しい実装については省きますが、PHPStanの出力とgit diffで得られる変更箇所の行番号を比較することで、変更箇所のエラーを絞り込むことができます。

ジョブの登録

最後にJenkins管理画面からジョブを作成します。 先ほど作成したスクリプトを登録し、トリガにマージリクエストを登録したら完成です!

おわりに

ピクシブ百科事典への静的解析CIの導入を紹介しました。CIに静的解析を導入することで開発者は環境に依らず安心してリリースができるようになりました。

ちなみにpixivでは静的解析の他にリンターや動的解析のCIも導入されています。 ピクシブ百科事典でもより良い開発体験のため、引き続き改善をしていきたいと思います!

ピクシブでは現在、長期インターン、アルバイトを募集中です。興味ある方は是非ご応募ください!

mdan
2019年4月からアルバイトしてます。主にPHPでサーバサイド開発を担当。趣味はGAS、インフラ、卓球。