CubicLouve

Spring_MTの技術ブログ

tmpwatchについて

よく忘れるので、整理しておく。

tmpwatchコマンド

コマンドは/usr/sbin/配下にあります。
使い方は下記の通り。

$ tmpwatch [option] [hours]  [dirs]

dirsの指定にはワイルドカード(*)が使えます。
基本的に空dir、ファイル、シンボリックリンクを削除するそうです。
root のファイルは消さない、シンボリックリンクをたどって削除することもないそうです。

When changing directories, tmpwatch is very sensitive to possible race conditions and will exit with an error if one is detected. It does not follow symbolic links in the directories it’s cleaning (even if a symbolic link is given as its argument), will not switch filesystems, skips lost+found directories owned by the root user, and only removes empty directories regular files, and symbolic links. 

オプション

man tmpwatch の内容

-u, --atime : 最終アクセス時刻  
-m, --mtime : 最終ファイル変更時刻  
-c, --ctime : 最終inode変更時刻(パーミッション、所有者、グループ、または他のメタデータが変更された時刻)  
-M, --dirmtime : 最終dir修正時刻  
-a, --all :全てのファイルタイプを削除する。  
-d, --nodirs : 空dirであってもdirを削除しない。  
-l, --nosymlinks :symbolic link は削除しない  
-f, --force : rootであっても削除する。  
-q, --quiet : エラー時のみ、ログを出す。  
-s, --fuser : ファイルを開いているか確認。  
-t, --test : test。実際には削除しない。  
-U, --exclude-user=user : ここで指定したuserが所有しているファイルは削除対象外。  
-v, --verbose : 詳細なログ表示。  
-x, --exclude=path : ここで指定したパスは削除対象外。  
-X, --exclude-pattern=pattern :ここで指定したパターンに一致すれば削除対象外。 

-umcとすると、上記3つの中の最大値(最新時刻)が削除判定のベースとなる。下記参照

If the --atime, --ctime or --mtime options are used in combination, the decision about deleting a file will be based on the maximum of these times. The --dirmtime option implies ignoring atime of directories, even if the --atime option is used. 

試してみる

/usr/sbin/tmpwatch -tumcv 72 /home/*/logs/
tオプションをつけてdry-runをしてみる

$ /usr/sbin/tmpwatch -tumcv 72 /home/*/logs/
grace period is 259200
cleaning up directory /home/hoge/logs
cleaning up directory /home/hoge/logs/httpd
removing file /home/hoge/logs/httpd/www29204u.sakura.ne.jp.error_log.20110506
removing file /home/hoge/logs/httpd/www29204u.sakura.ne.jp.access_log.20110506

tmpwatchをcronに登録

/etc/cron.daily/tmpwatchには↓のように既に登録されていいるものがあります。

$ less /etc/cron.daily/tmpwatch
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix 
        -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix 
        -X '/tmp/hsperfdata_*' 240 /tmp
/usr/sbin/tmpwatch "$flags" 720 /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
    if [ -d "$d" ]; then
        /usr/sbin/tmpwatch "$flags" -f 720 "$d"
    fi
done

ここに

/usr/sbin/tmpwatch "$flags" 240 /home/*/hoge

for d in `ls /home/`; do
    if [ -d "/home/$d/foo/bar" ]; then
    /usr/sbin/tmpwatch "$flags" 240 "/home/$d/foo/bar";
    fi
done

みたいに追記すればよいと思います。

参考にしたサイト

ameblo.jp

Stray Penguin - Linux Memo (logrotate)

nageyari.dig-it-all.jp

MySQLに数百万件のテストデータを作る方法

お手軽にMySQLにダミーデータを作る方法です。

INSERT INTO hoge SELECT * FROM hoge; を繰り返す

スキーマ

gist.github.com

(プライマリキーなしというありあえないテーブルで試しています。。。)

結果

gist.github.com

1280000 rows作るのに1分かからないですね。

プライマリキーの重複が起こる場合は、

INSERT INTO :table_name (プライマリキー以外のカラム) SELECT プライマリキー以外のカラム FROM :table_name;

LOAD DATA INFILE

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.6 LOAD DATA INFILE 構文

bundlerを使っていて、'/' is not writable. Bundler will use '/tmp/bundrer/home/unknown/' as your home directory temporarily.` と出たときの対応

daemontools経由等で、デーモンのプロセスを立ち上げるときに、プロセス内でbundle exec hogeなどのコマンドを実行すると、

'/' is not writable.
Bundler will use '/tmp/bundrer/home/unknown/' as your home directory temporarily.

のようなwarningメッセージがでます。

これは、HOME が設定されていない環境におけるfallbackのための修正によって発生します。

1.14.5 以降のbundlerを使うと発生します。

https://github.com/bundler/bundler/blob/master/CHANGELOG.md#1145-2017-02-22

github.com

github.com

github.com

原因

下記のようなコードで再現できます(OSはMacです)。

% cat test.rb
p ENV['HOME']
p Gem.user_home

% HOME=/ bundle exec ruby test.rb
`/` is not writable.
Bundler will use `/var/folders/kw/_3kqzwx93nvbbl0h2wk6nkz8bj8f_z/T/bundler/home/hoge' as your home directory temporarily.
"/"
"/"

エラーがでるところはここです。

bundler/bundler.rb at 3203fdd2ad861af2aedfa233b754a02bfc1c4741 · bundler/bundler · GitHub

まずbundleコマンドでbundler自体の設定を行うのですが、bundlerのconfigurationオプションの設定が書かれているファイルから設定を読み込みます。

bundler/settings.rb at 3203fdd2ad861af2aedfa233b754a02bfc1c4741 · bundler/bundler · GitHub

bundler/settings.rb at 3203fdd2ad861af2aedfa233b754a02bfc1c4741 · bundler/bundler · GitHub

このconfigurationオプションの設定とは、

Bundler: bundle config

ここにある設定群になります。

この内容はbundle configで設定した内容が反映されています。

実際の内容はこんな感じです。

% cat .bundle/config
---
BUNDLE_GEM__TEST: "rspec"
BUNDLE_GEM__MIT: "false"
BUNDLE_GEM__COC: "false"
BUNDLE_BUILD__THERUBYRACER: "--with-v8-dir=/usr/local/opt/v8@3.15"
BUNDLE_BUILD__LIBV8: "--with-system-v8"

bundlerのconfigurationオプションの設定があるファイルはglobalとlocalの2つがあります。

localは実行しているカレントディレクトリ/.bundle/config にファイルが設置されています。

globalの場合が今回の問題で、設置されているパスは、環境変数BUNDLE_CONFIGがあれば、 $BUNDLE_CONFIG/.bundle/configになり、なれば$HOME/.bundle/configに設置されています。

ただし、HOMEBUNDLE_CONFIGが設定されていない場合や、HOMEに書込み権限がなければ実際に書込みが発生する際にエラーになっていました。(bundle installbundle updatebundle configを実行した時)

.bundle配下はbundlerの設定やgemのchecksumなどのキャッシュファイルが格納するために必須です。

そこで、HOMEBUNDLE_CONFIGも設定されていない場合や、.bundle配下が書き込めない場合においてtempfileにディレクトリを作成してそこに格納するようにします。

tempfileに格納する際に、warningメッセージがでます。

このメッセージが出た場合

.bundle/configで設定される内容はほとんどがビルドオプションになります。

Bundler: bundle config

bundle installbundle updateを行う場合にはbundlerのンパイルオプション等に影響があるので、要確認です。

ただし、bundle exec 経由でコマンドを実行する場合においては影響がないことがほとんどです。 (特にデーモン化したプロセスでbundler経由でコマンドを呼び出すと、HOMEが/になっているのでこのwarningがでると思います。)

自分で設定した内容を確認して、問題なければwarningをスルーでも大丈夫です。

デーモンでもどうしてもこのwarningを止めたい

bundler binstubを使ってプロジェクトにbundleされた実行可能ファイルを生成してその実行ファイル経由でコマンドを実行してください。

Bundler: bundle binstubs

MAC(メッセージ認証コード)とは

MAC(Message Authentication Code メッセージ認証コード)について、整理してみました。

(MACアドレスMACはMedia Access Controlの頭文字なので同じ文字面ですが全然意味が違いますね。)

MACは自分に届いたメッセージが送信者が意図した通り(改竄されてない、なりすましていない)であることを確認できます。

メッセージが改竄されていないことは、メッセージのintegrity(正真性、完全性)という性質です。

メッセージがなりすまされて送信されていない(正しい送信者からのものである)ということを、メッセージの認証という性質です。

MACは正真性を確認し、メッセージの認証を行います。

メッセージ認証コードとは

メッセージ認証コードには、

  • 送信者と受信者が共有する鍵(共有鍵、MAC鍵、ハッシュ鍵ともよばれることもある)
  • 任意のメッセージ

を元に固定ビット長の出力をします。

出力された値は MAC と呼ばれます。

一方向ハッシュ関数でメッセージのハッシュ値を取るのと似ていますが、この場合共有鍵はでてきません。

一方向ハッシュ関数を拡張して認証をできるようにしたのがMAC(メッセージ認証コード)になります。

MAC(メッセージ認証コード)を利用するための手順

MACを使った改竄検知、認証のフローの手順です。

  1. 受信者、送信者の二者で共有鍵を共有する(鍵配送問題が存在します)
  2. 送信者はメッセージと共有鍵の2つからMAC値を計算
  3. 送信者はメッセージとMAC値の両方を受信者に送る
  4. 受信者はメッセージを元にMAC値を計算する
  5. 受信者は送信されたMAC値を自分で計算したMAC値を比較する。

最後に計算したMAC値を比較し、MAC値が等しい場合は、メッセージが間違いなく送信者から送られてきたものします。

MAC

MAC(メッセージ認証コード)はハッシュ関数の拡張か生まれているので、一方向ハッシュ関数を用いて実現できます。

また、ブロック暗号でも実現可能です。

CMAC - Wikipedia

HMAC

MAC(メッセージ認証コード)を、一方向ハッシュ関数(SHA256等)を用いて実現する方法です。

  1. 共通鍵を一方向ハッシュ関数のブロック長より短い場合は、0パディングをする。
  2. 固定文字列ipad(バイト値 0x36 を 64回(ブロック長)繰り返した文字列)とパディングした鍵とのビット列のXORをとる
  3. 2で生成したバイト列をメッセージの先頭につける
  4. 3の結果を一方向ハッシュ関数を適用して、ハッシュ値を得る
  5. 固定文字列opad(バイト値 0x5C を 64 回(ブロック長)繰り返した文字列)と4で得られたビット列とのXORをとる
  6. 5で生成したビット列に4で生成されたハッシュをつける
  7. 6の結果を一方向ハッシュ関数に適用してハッシュ値を得る -> これがMAC値になる

再送攻撃(再生攻撃)への対応

すでに送られて、正しく認証されたMAC値とメッセージを取得しておき、それをなにも加工せずに使いまわして同じメッセージを送り続けることです。

防御策としては、

  1. メッセージにシーケンス番号を含めておく

    • 通信相手ごとに最後のシーケンス番号を記録しておかなければならない
  2. メッセージにタイムスタンプを含めておきMAC値が一致しても古いメッセージが送られて来た場合にエラーにする(脆弱性残る)

    • タイムスタンプの粒度(一秒とかだと一秒の間で攻撃される)の問題がある
    • 送信者と受信者で時計を一致させるか、古いと判定するための時間に幅をもたせておくことをするが、ここも攻撃される可能性がある
  3. 使い捨てのランダムな値(ノンス)をメッセージに含めて送信する。

    • 通信量が少し多くなるが、有効な手法

鍵の推測への対応

共有鍵は暗号学的に安全な疑似乱数生成器を使ってください。

できないこと

  • 三者に対する証明 送信者と受信者の区別が第三者には判断できない。

デジタル署名によって解決できます。

  • 否認防止 送信者と受信者の区別が第三者には判断できない。

これもデジタル署名によって解決できます。

認証付き暗号(Authenticated Encryption with Associated Data)

認証付き暗号 - Wikipedia

Authenticated Encryption with Associated Dataを略してAEADとも呼ばれます。

MAC(メッセージ認証コード)と対称暗号を組み合わせて、機密性(暗号)、認証・正真性(MAC(メッセージ認証コード))を満たす仕組みです。

AEAD(認証付き暗号)では不適切に細工された暗号文を識別して(MACの認証と正真性)復号を拒否することができるため、選択暗号文攻撃に対して安全になります。

AEADは現在のところTLSで利用できる最良の暗号化利用モードです。

SSL/TLSの暗号化利用モードにおいて、AEAD(認証付き暗号)の一種GCMが利用可能です。

Galois/Counter Mode - Wikipedia

ただし下記記事にあるように、素のままのOpenSSLには問題ないのですが、自前で実装されている場合は注意が必要です。

d.hatena.ne.jp

国際標準暗号GCMの新たな安全性評価結果(2012年08月17日):プレスリリース | NEC

参照

HMAC: Keyed-Hashing for Message Authentication

qiita.com

http://www.gtc.co.jp/glossary/cia.html

この記事は、クリエイティブ・コモンズ・表示・継承ライセンス3.0のもとで公表されたウィキペディアの項目「メッセージ認証符号」を素材として二次利用しています。

Macでクラッシュレポートを見る

MacC++書いててSEGVとかを起こしたときのクラッシュレポートの置き場所をいつも忘れるのでメモ

/Users/:user/Library/Logs/DiagnosticReports 
/Library/Logs/DiagnosticReports 

この配下にある.crashを見る

stackprofの原理

stackprofがどうやってプロファイルを行っているかを追ってみます。

プロファイルするコードは下記を使います。

stackprof/sample.rb at master · tmm1/stackprof · GitHub

自分用のメモなので、間違い等があるのはご容赦ください。。(随時ブラッシュアップしていければ)

プロファイルを取る仕組み

StackProd::Middleware経由でのプロファイリングの流れは下記の通り。

  1. リクエストを受ける
  2. StackProf.startが呼ばれプロファイリング開始
  3. リクエストを処理する
  4. StackProf.stopを呼びプロファイルングを終了
  5. save_every で指定した回数にリクエスト回数が達した場合、プロファイル結果をファイルに書込む

さらに今回はStackProf.startからStackProf.stopの流れを詳細におっていきます。

準備

gdbでおっていくので、マクロ等も展開できるようにデバッグ情報を付けてrubyをビルドします。

$ wget https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.gz
$ tar zxf ruby-2.4.1.tar.gz
$ cd ruby-2.4.1 
# -O0で最適化を無効にし、-g3でデバッグ情報を付ける
./configure optflags="-O0" debugflags="-g3" --prefix="${HOME}/.rbenv/versions/2.4.1-O0_g3"
make
make install

サンプリング

StackProf.startが呼ばれると、stackprof_startが呼ばれます。 https://github.com/tmm1/stackprof/blob/master/ext/stackprof/stackprof.c#L61

start時に、modecpu または wallで設定すると、setitimer(2)intervalで指定した時間(マイクロ秒)のインターバルタイマーを設定し、タイマーが満了したときに送られるシグナルに対してsigaction(2)でサンプリングのシグナルハンドラーを設定します。

stackprofで指定するintervalはサンプリングする間隔(これは時間)で、save_everyはサンプリングした結果をファイル等に保存する間隔(これはリクエスト回数)です。

save_everyはHTTPサーバー向けのオプション(StackProf:: Middlewareでのみ指定可能)で、save_everyで指定した数リクエストがきたら、サンプリング結果を保存します。

save_everyが大きすぎると、サンプリング結果が肥大化し、保存するときに時間がかかってしまいますが、小さいとこまめに保存されるためファイル数が膨大になってしまうので運用しながら要調整です。

mode:objectの場合は、TracePointを使って、オブジェクトの確保のたびにサンプリングを行います。

mode 説明
:wall setitimer(2)ITIMER_REAL(実時間(時計時間 wall-clock-time)でタイマーが減少)を使います。タイマーが満了になるとSIGALRMシグナルが送られます。intervalはマイクロ秒になります。
:cpu setitimer(2)ITIMER_PROF(CPU時間でタイマーが減少)を使います。タイマーが満了になるとSIGPROFシグナルが送られます。intervalはマイクロ秒になります。
:object Rubyの新しいオブジェクト生成の毎にサンプリングします。RubyVM時間とでも言えるかな。

シグナルハンドラーはRubyのC-APIrb_postponed_job_register_oneを使ってサンプリングするjobを登録します。

サンプリングするときには、RubyのC-APIrb_profile_framesを使って、call stackにアクセスして、スタックフレームを取得します。

ruby/vm_backtrace.c at 249790802db62ff22c79830d4054c449fa3c243b · ruby/ruby · GitHub

サンプリング結果

各サンプルは複数個のスタックフレームで構成され、スタックフレームはMyClass#methodblock in MySingleton.methodように見えます。

サンプル内のこれらのフレームごとに、stackprofは下記のメタデータを収集します。

(aggregateオプションを有効にした場合のみです。デフォルトでは有効になっています。)

主に個々の部分でメタデータの収集を行っています。

https://github.com/tmm1/stackprof/blob/master/ext/stackprof/stackprof.c#L387

metadata 説明
samples スタックフレームで一番最初に呼ばれている回数。つまり、サンプリングしたときにまさに呼び出されていた回数。まずこの数字を確認する。
total_samples スタックフレームの中にふくまれてる回数。
lines スタックフレームの呼び出されている行とその回数。
edges このスタックフレームを呼び出したメソッドとその回数

結果

  • print_method(/pow|newobj|math/)

stackprof print_method(/pow|newobj|math/)

  • print_text

stackprof result.print_text

stackprof print_debug

サンプリングしていたときにスタックフレームは下記のようになっています。

  • A#powの場合

pow <- initializeなので、 powsamplestotal_samplesのカウントに1足され、initializeのtotal_samplesに1足されます。

$ gdb -nw -silen -x ./.gdbinit --args `rbenv which ruby` sample.rb

(gdb) break stackprof.c:387
(gdb) run
(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[0])
"pow"
(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[1])
"initialize"
(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[2])
  • A#mathの場合 math <- math <- initializeなので、matchsamplesには1、total_samplesのカウントに2足され、initializeのtotal_samplesに1足されます。

そのため、mathtotal_samplessamplesより約2倍ほど多くなります。

mathは中にブロックがあり、それがスタックに積まれています。

(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[0])
"math"
(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[1])
"math"
(gdb) rb_p rb_profile_frame_method_name(_stackprof.frames_buffer[2])
"initialize"

:wall:cpuの使い分け

ネットワークアクセスをする場合をサンプリングしてみます。

  • :wall

stackprof with wall

  • :cpu

stackprof with cpu

ネットワークI/Oの待ち時間やディスクI/Oの待ち時間はCPU時間には反映されないので、:cpuの場合は、ネットワークレイテンシが反映されないです。

これを踏まえると、まずは:wallモードでサンプリングしボトルネックがどこにあるか明らかにしましょう。

I/O待ち多く(特にデータベース外部サービスの外部要因でどうにもならない場合)、ノイズにしかならないので、:cpuモードでもサンプリングするのがよいでしょう。

参照

speakerdeck.com

関数一覧 (Ruby 2.7.0 リファレンスマニュアル)

macro RTEST (Ruby 2.7.0 リファレンスマニュアル)

https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigaction.2.html

https://linuxjm.osdn.jp/html/LDP_man-pages/man2/setitimer.2.html

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/sigdelset.3.html

real time/user CPU time/system CPU&nbsp;timeの違いをメモsiguniang.wordpress.com

rb_postponed_job_register_oneの説明 d.hatena.ne.jp ruby trunk changes

rb_profile_framesの説明 2013-10-07

Ruby 2.1: Profiling Ruby · computer talk by @tmm1

st_updateの説明 2011-12-28

BazelでC++をビルドする

以前Bazelの入門の記事をかきましたが、今回はC++をビルドする環境を作成してみます。 spring-mt.hatenablog.com

下記のチュートリアル通りにやっていきます。 Build Tutorial - C++ - Bazel

まずはexampleをcloneしてやっていくのですが、前回やった残りのプロジェクトもあるのでサクッと自分でやっていきます。

【1】単一パッケージ、単一ターゲットでビルド

workspaceのセットアップを行います。

workspaceにはソースファイルとビルド成果物が含まれます。

Bazelのowrkspaceとして、トップディレクトリにWORKSPACEファイルを置きます。

今回は依存関係がないので、空ファイルで置いておきます。

プロジェクトをビルドするために、全てのインプットと依存関係を同一workspaceに含める必要があります。

異なるワークスペースに存在するファイルは、リンクしていなければ互いに独立しています。

BUILDファイルはソースコードなどビルドに必要なファイルやビルド方法、出力されるファイルなどの設定を記述するファイルです。

BUILDファイルを含むワークスペース内のディレクトリはパッケージという単位になります。

BUILDファイルにはBazelのさまざまな種類の設定を記述しますが、一番重要な設定はビルドルールです。

ビルドルールは、実行可能なバイナリやライブラリなど、どのような成果物を作成するかを記述します。

BUILDファイル内のビルドルールのそれぞれの実体はターゲットと呼ばれ、ソース・ファイルと依存関係の特定のセットを指します。ターゲットは他のターゲット郡を指すこともできます。

最初に下記構成にしてみました。

.
├── WORKSPACE
└── main
    ├── BUILD
    └── hello-world.cc

まずは単純なBUILDファイルを見ていきます。

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

nameで指定したパラメータがターゲット名になります。(nameの指定は必須です。)

hello-worldターゲットはcc_binaryルールをインスタンス化します。 このルールでは依存関係のないhello-world.ccソースファイルから自己完結型の実行可能バイナリをビルドします。

ではビルドしてみます。

ビルドコマンドは

bazel build //main:hello-world

//main: はworkspaseのルートディレクトリからのBUILDファイルが配置されてる相対パスを示しています。hello-worldBUILDファイル内で指定したnameです。

% bazel build //main:hello-world
.....................
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 12.816s, Critical Path: 3.27s
% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.165s, Critical Path: 0.00s

(二回目はキャッシュが使われて高速にビルドできています)

ビルドが完了すると、workspaceのルート直下のbazel-binに成果物ができています。

 % bazel-bin/main/hello-world
Hello world
Fri Aug  4 01:11:51 2017

依存性の確認

BUILDファイルにはビルドの依存関係がすべて明示的に記述されています。

Bazelはこれらのステートメントを使用してプロジェクトの依存関係グラフを作成し、正確なインクリメンタルビルドを可能にします。

前述のプロジェクトの依存性のグラフは下記コマンドで生成できます。

% bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph
digraph mygraph {
  node [shape=box];
"//main:hello-world"
"//main:hello-world" -> "//main:hello-world.cc"
"//main:hello-world.cc"
}

グラフ構造はGraphViz で可視化できます。

【2】単一のパッケージ、複数のターゲット

大きなプロジェクトを複数のターゲットとパッケージに分割することで、変更した部分の再ビルドだけの高速なインクリメンタルビルドができ、分割されたプロジェクトを一度にビルドするとことでビルド時間の短縮ができます。

【1】プロジェクトを2つのターゲットに分けます。

diff --git a/CppExamples/main/BUILD b/CppExamples/main/BUILD
index 20c6f47..43b22a4 100644
--- a/CppExamples/main/BUILD
+++ b/CppExamples/main/BUILD
@@ -1,4 +1,13 @@
+cc_library(
+    name = "hello-greet",
+    srcs = ["hello-greet.cc"],
+    hdrs = ["hello-greet.h"],
+)
+
 cc_binary(
     name = "hello-world",
     srcs = ["hello-world.cc"],
+    deps = [
+        ":hello-greet",
+    ],
 )

hello-worldのリンクしたいライブラリのラベルをdepsに指定します。

こうすると、hello-worldをビルドする前に、hello-greetのビルドが実行されます。

% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 5.813s, Critical Path: 1.16s

% bazel-bin/main/hello-world
Hello world
Sun Aug  6 15:23:47 2017

hello-greet.ccが修正されたら、hello-greet.ccのみコンパイルが行われます。

【3】複数のパッケージ、複数のターゲット

BUILDファイルを含む新しいディレクトリを作成します。 (BUILDファイルを含むワークスペース内のディレクトリはパッケージという単位)

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

srcsでソースファイルを、hdrsでヘッダーファイルを指定します。

lib/BUILDのなかで指定した// lib:hello-timeターゲットをmain/ BUILDから明示的に見えるようにするためにvisibility//main:__pkg__を指定しています。

デフォルトでは、ターゲットは同じBUILDファイル内の他のターゲットに対してのみだけに公開されています。

Bazelは、ターゲットの可視性を利用して、ライブラリの実装の詳細がパブリックAPIに漏れることを防いでいます。

Common Definitions - Bazel

ビルドをしてみます。

% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.365s, Critical Path: 0.00s
% bazel-bin/main/hello-world
Hello world
Sun Aug  6 22:01:01 2017

ラベルの使い方

BUILDファイルやコマンドライン上では、ラベルからターゲットを参照します。

シンタックスは下記の通り。

//path/to/package:target-name

ターゲットがruleのターゲットの場合、path/to/packageはBUILDファイルを含むディレクトリへのパスで、target-nameはBUILDファイルのターゲット(name)の名前です。

ターゲットがファイルである場合、path/to/packageはパッケージのrootからのパスであり、target-nameはターゲットファイル名です。

同じパッケージ内のターゲットを参照するときは、パッケージパスを省略して//:target-nameとだけで使えます。

同じBUILDファイル内のターゲットを参照する場合は、//のworkspaceのルート識別子をスキップして、:target-nameだけを使用することができます。