にゃははー

はへらー

bjam AdC jp 2011 17日目

遅れてしまった...というか忘れてた...

今回からは何回かに分けてtoolsetについてやります。といっても私自身あんまりわかってないので昔書いたnvcc.jamを元に適当に説明します。

nvcc.jamは http://www.flast.jp/browser/btc/tools にあります。

toolsetとはなにか

toolsetはコンパイラなどのツールについてbjamから共通のインターフェースで操作するためにいろいろがんばってるやつです。

gccとmsvcではコンパイラオプションなどが違うことは周知の事実だと思います。しかし、bjamでビルドする際にはその差異についてはほとんど考慮する必要が無いように作られています。たとえばhoge.cppからhogeというバイナリを吐く場合、

project hoge ;

exe hoge : hoge.cpp ;

と書いてuser-config.jamやコマンドライン引数でどのコンパイラを使うか指定すると思います。

make単体ではこういったことはできないですし、autotoolsに任せるのも限界があります。configureでCC=clと指定してもconfigureが生成するMakefileはcl用にはなってないはずです。
まだgcc/msvc程度ならそれぞれ用意するとか.slnを用意するとかになると思いますが、他のコンパイラを使う必要があるとかどのコンパイラを使われるかわからないとかだともうお手上げなはずです。

つまりbjamの本領発揮となるわけです。

概要

今日は大まかな流れのみです。次回以降細かくやります。

toolsetはBoost.Buildのインストールディレクトリ(*nixではデフォルトで/usr/local/share/boost-build)以下にtoolsというディレクトリがありますが、このディレクトリ以下に用意されてるjamファイルがtoolsetを記述しているものです。
また、tools/typesというディレクトリもありますが、こちらはmakefileにおけるsuffixルールのようなものが記述されています。

つまり新たなtoolsetを作るには1つまたは2つの手順を踏む必要があります。

  • tools以下に追加するtoolsetを記述する
  • もしtoolsetが処理する拡張しがtools/types以下に登録されていないのであれば用意する

今回nvcc.jamを作るにあたって、CUDAは.cuファイルをソースとして用いるので

  • tools/nvcc.jamをつくる
  • tools/types/cuda.jamをつくる

という手順を踏みました。

tools/types/cuda.jam

cuda.jamは簡単なので先に説明します。

以下にソースの主要部分を載せます

import type ;
import scanner ;

class cuda-scanner : c-scanner
{
	rule __init__ ( * )
	{
		c-scanner.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
	}
}
scanner.register cuda-scanner : include ;

type.register CUDA : cu ;
type.set-scanner CUDA : cuda-scanner ;

それほど多くのことはやってないです。このscannerというのはソースの依存関係を走査するときに使われるもので、CUDAはC系言語と同じくincludeで他のファイルへの依存関係を構築するので既にBoost.Buildで用意されてるc-scannerを使います。
また、scannerモジュールのregister ruleの第2引数に渡してるのは、走査する際にどのプロパティに依存するかというsequenceです。C系言語なのでincludeプロパティだけあれば問題ないです。

ここでこう疑問に思うはずです、本来includeだけではなくdefineでインクルードすべきファイルを取捨選択してるはずだと。つまりdefineプロパティでもincludeするファイルは変わるし、そもそもコンパイラのbuilt-inマクロでも変化するはずだと。
実際そのとおりで、これはプリプロセスするまで真の依存関係はわかりません。で、Boost.Buildはどうしてるかというと、include <...>かinclude "..."となっている部分を正規表現で抜き出し、その部分のファイルを依存関係として扱い、再帰的にすべての依存関係を走査していきます。

そのため、Boost.Buildの現在のtoolsetでは

#define DEPENDING_HEADER() "dep.h"
#include DEPENDING_HEADER()

という記述がなされているとこれを依存関係に入れることができず、dep.hおよびdep.hが依存しているファイルに変更があっても検出することができません。Boost.PPのPP_ITERATEとかは特にこれに該当します。

また、次のようにBoostの全hppをincludeしているソースがあっても、すべて依存扱いになってしまうので注意です。

#if 0
#include <boost/token_functions.hpp>
#include <boost/assign.hpp>
...
#include <boost/dynamic_bitset/config.hpp>
#include <boost/dynamic_bitset/dynamic_bitset.hpp>
#endif

これの実行結果は

$ bjam
...found 9021 targets...
...updating 1 target...
gcc.compile.c bin/gcc-4.6.3/debug/test.o
...updated 1 target...

もちろん既に依存扱いになってるファイルがincludeされても再度見に行ったり、無限ループに陥るといったことは無いです。
ということで悲しいけどincludeプロパティだけで何とかしてもらうことにします。

type.registerでCUDAという型を.cuという拡張子に関連付けています。また、type.set-scannerで、CUDAという型はcuda-scannerというscannerを使うように関連付けています。

tools/nvcc.jam

# import省略
feature.extend toolset : nvcc ;
# 省略

rule init ( version ? command * : options * )
{
# 省略
}

class CUDA-compiling-generator : C-compiling-generator
{
	rule __init__ ( id : source-types + : target-types +
		: requirements * : optional-properties * )
	{
		C-compiling-generator.__init__ $(id)
			: $(source-types) : $(target-types)
			: $(requirements) : $(optional-properties) ;
	}
}

rule register-cuda-compiler ( id
	: source-types + : target-types +
	: requirements * : optional-properties * )
{
	generators.register
		[ new CUDA-compiling-generator $(id)
			: $(source-types) : $(target-types)
			: $(requirements) : $(optional-properties) ] ;
}

IMPORT $(__name__) : register-cuda-compiler : : generators.register-cuda-compiler ;

generators.register-cuda.compiler nvcc.compile.cu : CUDA : OBJ : <toolset>nvcc ;

type.set-generated-target-suffix OBJ : <toolset>nvcc : o ;

# flags 省略

rule compile.cu ( targets * : sources * : properties * )
{
	local target = [ feature.get-values target : $(properties) ] ;
	switch $(target)
	{
		case "" :
			OPTIONS on $(targets) += -c ;
		case * :
			EXIT "error" ;
	}
}

actions compile.cu
{
	$(CONFIG_COMMAND) $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -o "$(<)" "$(>)"
}

actions link bind LIBRARIES
{
	$(CONFIG_COMMAND) $(OPTIONS) -L"$(LINKPATH)" -o "$(<)" "$(>)" $(LIBRARIES)
}

省略と書いてる以上に省略してますが、重要な部分のみ残しました。

init ruleはusing ruleを使ってtoolsetを初期化するときに呼ばれるruleです。いつもusing gcc ;とかusing gcc : gcc-4.6 ;とか書いてると思いますが、それら引数はgcc.jamのinitに渡されているのです。

CUDA-compiling-generatorクラスはtoolsetとソースを関連付けるために使われるクラスです。CUDAの場合C(C++)のgeneratorと同一で問題ないですが、今後のことも考えて派生してあります。
Boost.Buildはこのほかにもlinking-generatorやarchive-generatorといったgeneratorを用意しています。

register-cuda-compiler ruleで実際に関連付けています。ちょうどIMPORT文のがそれにあたり、OBJというファイルをCUDAというファイルからtoolset nvccを用いて生成するといった意味合いになります。

compile.cu rule/actionsやlink actionsで実際にコンパイル/リンクする方法を記述します。

まとめ

おおまかな流れは以上のとおりです。実際はさまざまなプロパティに対応させるためにプロパティの登録や関連付けを行いますが、とりあえず今回は無視です。

次回はtools/type/cuda.jamのほうを見ますかね...