bjam AdC jp 2011 19日目
やっべーはてな時間で日付計算してたわー
前回大まかな流れを説明したので今回はtype/cuda.jamです。が、これは単純で説明も何も無いのでちょっと掘り進めます。
scanner
前回ものすごくシンプルなcuda-scannerを示しました。というより単純に派生して何もしてないだけですが。
で、このscannerが何をしているかってのを見ていきます。
どうでもいいですが、ちょっと調べてみたらscannerのctorは1 sequence受け取ればいいようなので、cuda-scanner.__init__ ruleみたいに9まで並べる必要は無いです。
cuda-scannerの継承関係は
cuda-scanner -> c-scanner(tools/types/cpp.jam) -> scanner(build/scanner.jam)
となっています。
scannerクラスを見ると
class scanner { rule __init__ ( ) { } rule pattern ( ) { error "method must be overriden" ; } rule process ( target : matches * ) { error "method must be overriden" ; } }
となっていることから、pattern ruleとprocess ruleがあればとりあえず問題ないということがわかります。abstractキーワード的なのはbjamには無いのでとりあえずerrorにしている感じです。
問題のc-scannerは主要な部分だけ抜き出すと以下のようになってます。
rule pattern ( ) { return "#[ \t]*include[ ]*(<(.*)>|\"(.*)\")" ; } rule process ( target : matches * : binding ) { local angle = [ regex.transform $(matches) : "<(.*)>" ] ; angle = [ sequence.transform path.native : $(angle) ] ; local quoted = [ regex.transform $(matches) : "\"(.*)\"" ] ; quoted = [ sequence.transform path.native : $(quoted) ] ; local g = [ on $(target) return $(HDRGRIST) ] ; local b = [ NORMALIZE_PATH $(binding:D) ] ; local g2 = $(g)"#"$(b) ; angle = $(angle:G=$(g)) ; quoted = $(quoted:G=$(g2)) ; local all = $(angle) $(quoted) ; INCLUDES $(target) : $(all) ; NOCARE $(all) ; SEARCH on $(angle) = $(self.includes:G=) ; SEARCH on $(quoted) = $(b) $(self.includes:G=) ; scanner.propagate $(__name__) : $(angle) $(quoted) : $(target) ; ISFILE $(angle) $(quoted) ; }
pattern ruleでソースを走査する際に使う正規表現を定義します。この正規表現をずっと使うことになるので、意味不明な構文を持つ言語だとやたら変な正規表現を書くことになると思います。includeの次の空白部分に\tが含まれていないのが若干気になりますが後でバグレポ投げときます。多分。
この正規表現でマッチした場合、\1がprocess ruleのmatchesに投げられることになるので、依存しているファイル名やモジュール名を()で囲う必要があります。C系言語の場合<>と""でincludeの挙動が異なるのでこれも含めた部分を囲っています。
process ruleでpatternでマッチした部分から依存しているファイルのリストを作ります。c-scannerの場合ここで<>(angle)と""(quoted)を振り分けています。
で、本来なら再帰的にこいつらを走査すると思うのですが、どうもそうではないのかprocessが呼ばれてる様子が無いのにもかかわらず変更の検出などを行っています。まったく謎です...
register/set-scanner
registerとset-scannerは計3箇所あります。これらは前回軽く説明したので特に要らないかと思いますが、一応。
scanner.register cuda-scanner : include ; type.register CUDA : cu ; type.set-scanner CUDA : cuda-scanner ;
scanner.registerはscannerがどのプロパティを要求しているかという情報と共に登録しています。走査する際にはここで指定したプロパティがctorに渡されてscannerが構築されます。
type.registerはターゲットタイプを登録するものです。今回はCUDA Cの拡張子の.cuだけですが、C++の場合は.cpp .cxx .ccなどもすべてC++のソースなので
type.register CPP : cpp cxx cc ;
となっています。
type.set-scannerで、あるターゲットタイプはどのscannerを使うかを設定しています。
一連の流れ
たとえば
exe hoge : hoge.cu ;
というビルドターゲットがあった場合、hoge.cuというソースはtype.registerで設定したとおりCUDAという種類のソースであることがわかります。
またexe ruleの出力は実行形式のファイルなので、EXEという種類の出力を出すことがわかります。
これからCUDA -> EXEという関係が作られます。
そうするとソースであるCUDAを解析しようとします。CUDAというターゲットはtype.set-scannerで設定したとおりcuda-scannerを使うことがわかります。
scanner.registerでcuda-scannerはincludeプロパティが必要であることを設定したので、現在のプロジェクトおよびexe ruleのrequirementsから
scannerが構築できると実際にソースを走査します。ここでcuda-scanner.patternにマッチしたものをcuda-scanner.processに投げます。
こうすることで依存関係を解決します。
まとめ
よくわからん。