読者です 読者をやめる 読者になる 読者になる

will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

C++初心者ならビルドはBazelでラクしちゃいましょう

c++

この記事は 初心者C++er Advent Calendar 2015, Qiita C++ Advent Calendar 2015 15 日目の記事です.

注意

  1. UbuntuもしくはMac OSXの環境が作れない方はそっと閉じてください。(VagrantでUbuntuはすぐ準備できる)
  2. スマホアプリ畑の人間が書くので、それに向けた内容も多くなっています。
  3. PCからの閲覧をおすすめします。スマホで読むと、コードのレイアウトが崩れるようなので。

ビルドツールについて

みなさんはビルドオートメーションツールには何を使っていますか?

玄人界隈ではcmakeが定番です。

私が携わっているプロジェクトではgypと呼ばれるツールを使っています。
元々Chromiumで開発されていたGoogleのオープンソースの一つで、node.jsにも使われているビルドツールです。

特殊なフォーマットに従ってファイルやマクロを定義することで、設定ファイルに応じたmakeファイルやXcodeなどプロジェクトを吐き出してくれる中間的なツールで、今は開発が進んでいないようです。

さて、前振りが長くなりましたが、このgypに取って代わる次世代ビルドツールとして開発されているのがBazelです。

BazelはCookpadの技術ブログでも取り上げてられているように、C++だけではなくスマホアプリや複数言語のビルドにも対応しているので、延長線上にポテンシャルがあるという観点も含めてBazelをピックアップしました。

また、話題の機械学習フレームワークTensorFlowもBazelでビルドされてます。

少しの設定ファイルを書けば機能単位でライブラリ化や依存関係を定義できるので、初心者はBazelでラクしましょう!!!

サンプル

github.com

↑を元に、解説していきます。

Hello World

インストール方法は環境によって違うのと、簡単なので割愛します。

HelloWorldプロジェクトのファイル構成

HelloWorld
├── Makefile
├── WORKSPACE
└── src
    ├── BUILD
    └── main.cpp

注目すべきはWORKSPACEBUILDです。

WORKSPACE

The location of the workspace directory is not significant, but it must contain a file called WORKSPACE in the top-level directory. The WORKSPACE file may be an empty file, or it may contain references to external dependencies required to build the outputs.

Getting Started with Bazelより引用

「WORKSPACEという名前のファイルがプロジェクトのトップとなるディレクトリに必要で、他のプロジェクトへの依存性を定義するもの。依存性がなければ空ファイルとしておいておけばOK」とのこと

今回は、外部のソースに依存しないのでWORKSPACEは空ファイルです。

TensorFlowWORKSPACEでは、外部に依存する設定が定義されていますね!

zipをダウンロードしてhashチェックしたり、gitのリポジトリをhash指定で持ってきてくれたりしてくれるので使い勝手が良さそうです!

BUILD

Bazel figures out what to build by looking for files named BUILD in your workspace

Getting Started with Bazelより引用

「BazelはBUILDと呼ばれるファイルからビルドすべきファイルを割り出す」とのこと。

つまり、WORKSPACEが外部のソースに対する依存関係の設定、BUILDが具体的なビルドの設定であり、両方ともビルドに欠かせない設定ファイルということになります。

それでは早速設定を見ていきましょう。

main.cpp
#include <iostream>

int main()
{
  std::cout << "Hello World" << std::endl;
  return 0;
}

まずは、ソースから。シンプルなHello Worldなので解説は不要ですね。

src/BUILD
cc_binary(
  name = "main",
  srcs = ["main.cpp"],
)

main.cppmainというターゲット名で実行ファイルを作成するという設定になります。nameのみがrequiredなパラメータです。

Makefile
all: run

BAZEL=$(shell which bazel)
.PHONY: run
run : build
    $(BAZEL) run //src:main

.PHONY: build
build : 
    $(BAZEL) build //src:main

.PHONY: clean
clean :
    $(BAZEL) clean

BazelはURIスキームライクにパッケージを定義しており、それらをdependencyとして扱うことができます。 そして、サブコマンドの後にパッケージを指定することでビルドターゲットとしてビルドを開始することができます。

Makefile内に定義してある//src:mainはsrcディレクトリ以下にあるBUILDファイルに定義されているmainというターゲット名を示していることになります。

実行してみる

make runを実行すればsrc/BUILDに定義されているmainがビルドターゲットとなり、main.cppがコンパイルされ、mainターゲットが実行されます。

f:id:matsuokah:20151215010642g:plain

失敗させてみる

セミコロンを抜いてみました f:id:matsuokah:20151215010428g:plain

実行後のディレクトリ

HelloWorld
├── Makefile
├── WORKSPACE
├── bazel-HelloWorld
├── bazel-bin
├── bazel-genfiles
├── bazel-out
├── bazel-testlogs
└── src
    ├── BUILD
    └── main.cpp

このように、bazelというプレフィックスでビルド関連のディレクトリ郡が生成されます。自動生成されたディレクトリやファイルは、bazel cleanで一挙に削除することができます。

HelloWorldプロジェクトではMakefileにエイリアスを定義したのでmake cleanで同様のことが可能です。

static libraryの依存関係を定義し、mainから呼び出してみる

HelloWorld出力部分をstatic library化し、//src:mainから依存関係を解決して、コールしてみます。

HelloWorldStaticLibのプロジェクト構成

.
├── Makefile
├── WORKSPACE
└── src
    ├── greeting
    │   ├── BUILD
    │   ├── hello.cpp
    │   └── hello.hpp
    ├── BUILD
    └── main.cpp

MakefileWORKSPACEは全く一緒です。

また、hello.hpp,hello.cpp,main.cppはHello Worldの出力を関数化しただけなので記載を割愛します。

src/greeting/BUILD

cc_library(
  name = "greeting",
  srcs = glob(["*.cpp"]),
  hdrs = glob(["*.hpp"]),
  visibility = ["//visibility:public"],
  linkstatic = 1,
)

あらたな設定が出てきましたが、雰囲気でおわかりかと思います。 まず、cc_libraryという単位に変わりました。

さらに、srcsがファイルの列挙ではなくglob関数を使っています。
お察しの通りワイルドカードでコンパイルターゲットとなるファイルを指定しています。hdrsも同様です。

visibilityはそのビルド単位の可視性となります。適切に設定することでビルド単位間の結合性を疎にできるので、必要なcc_libaryだけをpublicにすることが好ましいです。 publicにしたことで、src:mainからsrc/greeting:greetingが可視化されています。

linkstaticはフラグです。

src/BUILD

cc_binary(
  name = "main",
  srcs = glob(["*.cpp"]),
  deps = ["//src/greeting"],
)

depsという設定が入りました。greetingライブラリに依存してるんですね〜。

実行してみる

実行結果は変わらないので割愛して、変わったところをば

~/p/h/HelloWorldStaticLib ❯❯❯ ls bazel-out/local_darwin-fastbuild/bin/src/greeting/
_objs         libgreeting.a

libgreeting.aができていますね!

と、こんな感じでstatic libraryやshared libraryをつくれて依存関係もよしなにしてくれるので、コーディングに時間を割きたい初心者C++erにはおすすめなのではないでしょうか。

gitignore

*.swp
*~
.DS_Store
bazel-*

特に問題なければ、bazel-*のようにワイルドカードでexcludeしちゃって問題無いでしょう。

Bazelはドキュメントもしっかりあるので、「最適化オプション使いたい!」だとか、「マクロを定義したい!」だとかあれば、参考にしてみてください。Bazelで良き、C++のビルドライフを〜!