Twitter製の大規模ソフトウェア向けビルドツール「Pants」を試してみる

PantsはTwitter社が開発した大規模ソフトウェア向けのビルドツールです。今年の5月に1.0がリリースされました。

github.com

主にPythonで書かれているようですが、JavaScalaPythonGolangのビルドに対応しており、Thriftのコード生成やMarkdownからのドキュメント生成などもサポートしているようです。

同種の大規模向けビルドツールにはGoogle製のBazelやFacebook製のBuckがあります。

github.com

github.com

いずれも元は社内用に開発されたツールがOSS化されたものですが、GoogleのBazelは2009年頃からGoogle社内で使われておりかなり歴史のあるツールのようです。

BuckやPantsはBazelを参考にしているようですが*1、BuckはAndroidアプリのビルドが高速だったり、Pantsは複数言語への対応やThriftのサポートなど各社のカラーが色濃く現れています。

大規模ソフトウェアのビルドの課題

そもそも大規模なソフトウェアのビルドにおける課題とは何なのでしょうか?大きく整理すると以下の2点が問題となるようです。

  • バージョン管理の煩雑さ
  • ビルドに時間がかかる

ソフトウェアの規模が大きくなるほどビルドに時間がかかるのは直感的に理解できると思いますが、バージョン管理の煩雑さというのはどういうことなのでしょうか。

大規模なソフトウェアでは機能単位でモジュール化したり、共通部分をライブラリ化するなどして開発の効率化を図りますが、ソフトウェアが大きくなればなるほどモジュールが増えていきます。それぞれのモジュールが独自にバージョンアップしていくと、同じライブラリを使っているモジュールでもモノによって異なるバージョンを使用していたりといった事態は普通に発生します。

そうすると最終的に複数のモジュールをまとめて1つの巨大なモジュールをビルドする際にライブラリのコンフリクトが発生してしまいます。規模が多くなればなるほどこのようなライブラリの整合性を取る作業は難しくなっていきます。

現在はGitHubなどの流行もありモジュール毎にリポジトリを作成する文化が一般的ですが、大規模なソフトウェアではこの問題を避けるため単一のリポジトリで全てのモジュールを管理するMono Repositoryが適しているとされています。Blaze、BuckそしてPantsといった大規模ソフトウェア向けのビルドツールはすべてこのMono Repositoryを前提としています。

Pantsを使ってみる

Pantsのインストールは簡単で、リポジトリのルートディレクトリで以下のコマンドを実行します。*2

$ curl -L -O https://pantsbuild.github.io/setup/pants && chmod +x pants && touch pants.ini

pantsコマンドが使用できるようになり、初回実行時にPythonや必要なライブラリなどが物凄い勢いでインストールされます。

$ ./pants -V
New python executable in /Users/naoki.takezoe/.cache/pants/setup/bootstrap-Darwin-x86_64/pants.ZQsYCJ/install/bin/python2.7
Also creating executable in /Users/naoki.takezoe/.cache/pants/setup/bootstrap-Darwin-x86_64/pants.ZQsYCJ/install/bin/python
Installing setuptools, pip, wheel...done.
Collecting pantsbuild.pants
  Downloading pantsbuild.pants-1.1.0.tar.gz (874kB)
    100% |████████████████████████████████| 880kB 1.4MB/s
...
Successfully installed Markdown-2.1.1 Pygments-1.4 ansicolors-1.0.2 cov-core-1.15.0 coverage-3.7.1 docutils-0.12 fasteners-0.14.1 futures-3.0.5 lmdb-0.89 monotonic-1.2 pantsbuild.pants-1.1.0 pathspec-0.3.4 pex-1.1.10 psutil-4.3.0 py-1.4.31 pystache-0.5.3 pytest-2.6.4 pytest-cov-1.8.1 pywatchman-1.3.0 requests-2.5.3 scandir-1.2 setproctitle-1.1.10 setuptools-5.4.1 six-1.10.0 twitter.common.collections-0.3.7 twitter.common.confluence-0.3.7 twitter.common.dirutil-0.3.7 twitter.common.lang-0.3.7 twitter.common.log-0.3.7 twitter.common.options-0.3.7
1.1.0

ビルドの設定はBUILDという名前のファイルに記述します。以下のような簡単な構成で試してみます。

reporoot/
 |
 +- java-sample/
     |
     +-HelloWorld.java
     |
     +-BUILD

BUILDファイルの内容は以下のような感じです。ソースディレクトリやパッケージがネストしている場合はglobsのパターンの書き方や、パターンを複数列挙することで対応できます。

java_library(
  name = 'java-sample',
  dependencies = [
  ],
  sources = globs('*.java'),
)

ビルドしてみます。

$ ./pants compile java-sample
...
11:08:37 00:02         [zinc]
                       [info] Compiling 1 Java source to /Users/naoki.takezoe/pants/.pants.d/compile/zinc/252d64521cf9/java-sample.java-sample/current/classes...
                       [info] Compile success at 2016/09/01 11:08:38 [0.072s]
...
11:08:38 00:03   [complete]
               SUCCESS

外部のライブラリを使う場合、別途ライブラリを定義する必要があります。

reporoot/
 |
 +- java-sample/
 |   |
 |   +-HelloWorld.java
 |   |
 |   +-BUILD
 |
 +- 3rdparty/
     |
     +-BUILD

3rdparty/BUILDはこんな感じです。java_libraryを列挙することで複数のライブラリを1つのBUILDファイルで定義することができます。

java_library(
  name = 'junit',
  jars = [
    jar('junit', 'junit', '4.12')
  ],
  scope = 'forced',
)

java-sample/BUILDでは以下のようにdependeniesを記述することでライブラリを使用することができます。

java_library(
  name = 'java-sample',
  dependencies = [
    '3rdparty:junit',
  ],
  sources = globs('*.java'),
)

他のプロジェクトを参照する場合も基本的には同じような感じです。以下のような構造のリポジトリで、java-sampleプロジェクトがjava-commonプロジェクトに依存しているとします。

reporoot/
 |
 +-java-sample/
 |  |
 |  +-BUILD
 |  |
 |  +-HelloWorld.java
 |
 +-java-common/
    |
    +-BUILD
    |
    +-StringUtils.java

java-sample/BUILDでは以下のようにBUILDファイルが存在するディレクトリを指定することで対象のプロジェクトを依存関係に追加することができます。

java_library(
  name = 'java-sample',
  dependencies = [
    'java-common',
  ],
  sources = globs('*.java'),
)

BUILDファイルはプロジェクト毎だけでなくディレクトリ毎に作ることができるのでパッケージ単位でBUILDファイルを作成することでビルド単位を細かく分割することができます。また、1つのBUILDファイルで複数のjava_libraryを定義することもできるので同一のディレクトリでも条件によってビルド単位を分けることが可能です。

まとめ

既存のビルドツールとPantsを比較した場合の大きな違いはプロジェクト毎ではなく、ディレクトリ毎など自由な単位でビルドファイルを配置できるということです。「ビルド単位を細分化することでビルドの並列可能性や、キャッシュの有効性を高める」というのが基本的なコンセプトのようです。

他のビルドツールと併用することもできるので*3、デプロイ用のビルドには既存のビルドツールを使用し、ローカルでの開発時や実行頻度の高いプルリクエストのビルドのみPantsで高速化する、というような使い方もありかもしれません。

*1:Buckは元Google社員が作ったそうで、ビルドファイルの記法もBazelそっくりらしいです。

*2:PantsはMono Repository用のビルドツールということもあり、リポジトリのルートディレクトリにインストールして使用することが想定されているようです。

*3:既存のビルドが特定のビルドツール向けのプラグインなどに依存してると厳しいですが…。