yamake planned features
Command-Line Options
Project Options
Output Formats
state and status
プログラミングをしていると、状態管理をすることがよくあります。私がよく迷うのは、state、status の使い分け方です。
私の理解では、state = one of status です。例えば、HTTP レスポンスの最初の一行目*1には status code というものがありますが、これは決して state code ではありません。あえていうなら 200 とか 404 とか 500 とかいうものが state code です。
ですので、以下のように使い分けています。
- status
- 状態の型
- 状態変数名
- state
- 状態型が列挙型である場合、そのひとつひとつの定義に用いる
- getter/setter の関数名 ( ex. getState, setState )
で、実際どうなっているのか少し調べただけでも結構ばらつきがありました。厄介ですね。
というわけで、状態の型として state を使うか status を使うかは微妙です。が、割と意味は通じるのでどっちでもいいよ、という説もあります。
*1:通称 status line
Tools to build Google Chrome, gyp and Ninja
先日から yamake というオレオレメイクツールを作っている*1のですが、その直後のとあるツイートで gyp、Ninja というビルドシステムがあるということを知りました。どちらも、Chromium のビルドをより良くするために生まれたようです。知らなかったので、少し調べてみました。
Google がこういったビルドツールの開発に積極的な理由
Google Chrome というブラウザがクロスプラットフォームなブラウザだからだと思います。詳細は、gyp のドキュメントに書かれています。
特に厄介なのが Mac OS X での開発だそうで、Xcode を必ず通さないといけないようです。なので、まずはそれにパスすることを目標とし、それに併せて他を揃えていったらしいです。
gyp
以下の動画が分かりやすいと思います。
gyp 自体はビルドツールではなく、ビルドするための何らかのファイルを生成するビルド補助ツールです。*2ドキュメントとソースコードをチラ見した限りでは、以下のような特徴があるように思えました。
- 記法は JSON 形式っぽいもの
- Visual Studio、Xcode、SCons、Autotools に対応している
- Python で実装されている
依存性解決などはそれぞれの環境でそれなりのものがあるので、中継部分だけを作るという考え方には非常に共感できますが、記法が少しまどろっこしい気がします。ドキュメントにあったサンプル*3を抜粋します。
{ 'target_defaults': { 'defines': [ 'U_STATIC_IMPLEMENTATION', ['LOGFILE', 'foo.log',], ], 'include_dirs': [ '..', ], }, 'targets': [ { 'target_name': 'foo', 'type': 'static_library', 'sources': [ 'foo/src/foo.cc', 'foo/src/foo_main.cc', ], 'include_dirs': [ 'foo', 'foo/include', ], 'conditions': [ [ 'OS==mac', { 'sources': [ 'platform_test_mac.mm' ] } ] ], 'direct_dependent_settings': { 'defines': [ 'UNIT_TEST', ], 'include_dirs': [ 'foo', 'foo/include', ], }, }, ], }
Makefile に比べると、記述すべきことがかなり絞られていることがよく分かります。SCons を使っていたときに思ったのですが、私は文字列を引用符で括ったり、何か記号が多いのは嫌いです。*4
Ninja
少し前に SourceForge.JP からニュースが出ています。
こっちの記事が元ネタですかね。
ドキュメントとソースコードをチラ見した限りでは、以下のような特徴があるように思えました。
- 「とにかく速く」という哲学を持っている
- 記法は make と似ている
- C++ で実装されている
- 並列ビルドに対応しているっぽい
- Visual Studio などを使ったビルドをするには一手間必要になりそう
ドキュメントにあったサンプルを抜粋します。ノリは make に近いと思います。
cflags = -Wall rule cc command = gcc $cflags -c $in -o $out build foo.o: cc foo.c
もう少し調べておきたいこと
自分で書いたコードのビルドはどうにでもできるのですが、何らかのライブラリを利用している場合、そのビルドを取り込む必要があります。大抵のオープンソースプロジェクトでは基本的に Autotools で configure、make するためのファイルが提供されています。しかし、Windows や Mac OS X などに向けたものはなかったり、あったとしても用意されているものの形式はばらつきがあります。Google Chrome ではこれらをどうやって統合しているのでしょうか。というあたりを次回あたりで書いてみようと思います。
*1:yamake is makefile generator from YAML
*2:yamake は Ninja よりも gyp に近いです
*3:GypUserDocumentation - gyp - Project Hosting on Google Code
*4:日本語キーボードでは記号を打つのは面倒ですよね
Goodbye Eclipse, Hello Emacs.
気づけばあまり Eclipse のお世話になっていないので、Emacs でいいやと思うようになりました。で、Eclipse でお気に入りだった機能と最低限必要な機能をどうやって代替するかのメモを書いておきます。
- タグジャンプ
- M-.
- 先に TAGS ファイルを作っておく必要あり
- M-*
- ジャンプ元にもどれる
- Visual Studio でいうところの Ctrl-Shift-Left
- 定義、宣言にぽんぽんジャンプする方法はどうすれば・・・?
- M-.
- include ジャンプ
- これが見当たらない・・・
- タグジャンプで上等でしょ、ってことかな?
- コード補完
- M-TAB
- ビルド
- M-x compile
- デフォルトで make -k ってのが入ってくるので好みに応じて変える
- M-x compile
- エラーにジャンプ
- C-x `
- 移動
- C-a
- 行頭に移動
- C-e
- 行末に移動
- M-f
- 一単語前に移動
- M-b
- 一単語後に移動
- C-a
- 編集
- C-_
- アンドゥ
- C-d
- Windows 系の DEL キーと同じ働き
- C-Space
- コピー開始
- M-w
- コピー
- C-w
- カット
- C-y
- ペースト
- C-_
- スクロール
- C-v
- PageUp
- M-v
- PageDown
- C-v
- その他
- M-x shell
- シェルを開く
- M-x shell
TAGS ファイルの作り方
find . -name "*.[ch]" -print | etags -
リファクタリング
「Xrefactory for Emacs and XEmacs.」というのでできるっぽい。Eclipse のリファクタリングは強力だったので、すんなり移行できるかちょっと不安。
C code directory style
C でコードを書くときのディレクトリの作り方の話です。これはまぁ好みの話ですね。私は大抵こうします。
- contrib
- include
- lib
- src
- doc
- include
- xxx
- lib
- src
- samples
- tests
contrib には、外部ライブラリを入れます。中に include と lib ディレクトリを作って、contrib 内のものはすべてここに集まるようにしておきます。こうしておくとパスを通すのが楽だからです。場合によっては、外部ライブラリをダウンロードしてきてビルドまでしてくれる bootstrap スクリプトを用意しておきます。パッチなどをあてなくてもいい場合は、それで十分です。
doc には、主に doxygen の設定ファイルを入れます。doxygen のメインファイルは doxyfile、補助ファイルは xxx.dox という感じの名前にしてます。
include には、公開ヘッダとを入れます。include 直下には何もおかず、xxx というライブラリ名をつけてその下に入れます。大抵の場合これで衝突を避けることができます。
lib には、ライブラリコード を入れます。ここだけで利用する非公開ヘッダもここに入れます。
src には、アプリコードを入れます。ここだけで利用する非公開ヘッダもここに入れます。
samples には、サンプルアプリコードを入れます。ここだけで利用する非公開ヘッダもここに入れます。
tests には、テストコードを入れます。ここだけで利用する非公開ヘッダもここに入れます。
すべてのディレクトリには、それぞれのメイクファイルを入れます。
多くのオープンソースプロジェクトも似たような感じだと思いますが、とりあえず備忘録ということで書いておきました。あーすっきり。
Thrift IDL part 1
RPC → シリアライズ → protobuf → MessagePack と見ているうちに、IDL がやりたくなってきました。MessagePack では、Thrift IDL が使えるようです。Thrift IDL*1 は facebook*2 で開発されたんですねー。
多言語開発の手法の一つとして、C/C++ で書いて SWIG*3 で多言語向けのバインディングを作るってのもありますが、そもそも C/C++ で書かねぇよ他の言語で書くよっていう状況が ( Web サービスだと特に ) よくありそうなので、落とし所としては Thrift IDL くらいがちょうどいいのかもしれません*4。XML-RPC*5は少し野暮な感じがしますし。これだとプラットフォーム非依存にできますし。クライアントとサーバーも各言語向けのものが入っているので動作確認というかプロトタイピングは簡単にできそうですね。
コードがあったので少し見てみました。compiler/cpp/src ディレクトリに thriftl.ll、thrifty.yy というコードがあるので、yacc*6/lex*7 が使われているっぽいですね。main() で parse() を呼び出していますし。温故知新ならぬ温新知故な気分です。MessagePack で Thrift を解釈できるのも、この辺があって作るのが簡単だったから、という理由があるのかもしれません。
深く利用するまで新しいものはよく見える、ということが多いのでちょっと使ってみました。tarball をダウンロード*8して、configure します。
$ wget http://ftp.jaist.ac.jp/pub/apache//incubator/thrift/0.5.0-incubating/thrift-0.5.0.tar.gz $ tar xzf thrift-0.5.0.tar.gz $ cd thrift-0.5.0 $ ./configure
ひとまず、Thrift IDL コンパイラを試したいのでそこだけビルドします。終わると、thrift というプログラムが生成されます。
$ cd compier/cpp
$ make
試しに適当な thrift ファイルをつくって食わせてみます。
struct test { 1: required string id; 2: optional string name; }
$ ./thrift --gen cpp test.thrift
gen-cpp というディレクトリが生成されて、そこにファイルが四つほど生成されました。
$ ls gen-cpp test_constants.cpp test_types.cpp test_constants.h test_types.h
xxx_constants というファイルは今回定数を使っていないので無視するとして、test_types.h と test_types.cpp はこんな感じでした。
/** * Autogenerated by Thrift * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING */ #ifndef test_TYPES_H #define test_TYPES_H #include <Thrift.h> #include <TApplicationException.h> #include <protocol/TProtocol.h> #include <transport/TTransport.h> typedef struct _test__isset { _test__isset() : name(false) {} bool name; } _test__isset; class test { public: static const char* ascii_fingerprint; // = "5B708A954C550ECA9C1A49D3C5CAFAB9"; static const uint8_t binary_fingerprint[16]; // = {0x5B,0x70,0x8A,0x95,0x4C,0x55,0x0E,0xCA,0x9C,0x1A,0x49,0xD3,0xC5,0xCA,0xFA,0xB9}; test() : id(""), name("") { } virtual ~test() throw() {} std::string id; std::string name; _test__isset __isset; bool operator == (const test & rhs) const { if (!(id == rhs.id)) return false; if (__isset.name != rhs.__isset.name) return false; else if (__isset.name && !(name == rhs.name)) return false; return true; } bool operator != (const test &rhs) const { return !(*this == rhs); } bool operator < (const test & ) const; uint32_t read(::apache::thrift::protocol::TProtocol* iprot); uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; }; #endif
/** * Autogenerated by Thrift * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING */ #include "test_types.h" const char* test::ascii_fingerprint = "5B708A954C550ECA9C1A49D3C5CAFAB9"; const uint8_t test::binary_fingerprint[16] = {0x5B,0x70,0x8A,0x95,0x4C,0x55,0x0E,0xCA,0x9C,0x1A,0x49,0xD3,0xC5,0xCA,0xFA,0xB9}; uint32_t test::read(::apache::thrift::protocol::TProtocol* iprot) { uint32_t xfer = 0; std::string fname; ::apache::thrift::protocol::TType ftype; int16_t fid; xfer += iprot->readStructBegin(fname); using ::apache::thrift::protocol::TProtocolException; bool isset_id = false; while (true) { xfer += iprot->readFieldBegin(fname, ftype, fid); if (ftype == ::apache::thrift::protocol::T_STOP) { break; } switch (fid) { case 1: if (ftype == ::apache::thrift::protocol::T_STRING) { xfer += iprot->readString(this->id); isset_id = true; } else { xfer += iprot->skip(ftype); } break; case 2: if (ftype == ::apache::thrift::protocol::T_STRING) { xfer += iprot->readString(this->name); this->__isset.name = true; } else { xfer += iprot->skip(ftype); } break; default: xfer += iprot->skip(ftype); break; } xfer += iprot->readFieldEnd(); } xfer += iprot->readStructEnd(); if (!isset_id) throw TProtocolException(TProtocolException::INVALID_DATA); return xfer; } uint32_t test::write(::apache::thrift::protocol::TProtocol* oprot) const { uint32_t xfer = 0; xfer += oprot->writeStructBegin("test"); xfer += oprot->writeFieldBegin("id", ::apache::thrift::protocol::T_STRING, 1); xfer += oprot->writeString(this->id); xfer += oprot->writeFieldEnd(); if (this->__isset.name) { xfer += oprot->writeFieldBegin("name", ::apache::thrift::protocol::T_STRING, 2); xfer += oprot->writeString(this->name); xfer += oprot->writeFieldEnd(); } xfer += oprot->writeFieldStop(); xfer += oprot->writeStructEnd(); return xfer; }
コードはいかにもプログラムが生成したコードという感じがします。なるほどこんな感じなのか〜と眺めていると、フィンガープリント*9が出てきますが、利用されている形跡がありません。test クラスは他のクラスを継承しているわけでもないので、Thrift の何らかの機能を使うとこれが利用されるのかもしれません。
test::write() はシンプルですが、test::read() はそれなりに面倒なことをやってくれています。thrift ファイルで required とか optional として指定したかどうかで振る舞いが変わることが見て取れます。あと、途中で知らないフィールドが出てきたらすっ飛ばすようです。
RPC は大抵シンプルなリクエスト・レスポンスで完結するのでこれで十分な気がしますが、接続を確立したまま相互にメッセージをやりとりし、かつ複数種類のメッセージが順序が入り乱れているような場合、Thrift IDL をどうやって使うべきなんだろう。こういう場合、私はよく TLV*10 メッセージを使います。T と L は固定長で、V が可変長になっていて、T でメッセージを識別し、L で V の長さを知り、V の解釈は T 毎に用意したものにまかせる、というやりかたです。V の終端が何らかの形で定義できる場合、TV でも十分です。なんで、TL または T を struct として定義しておけば十分そうですが・・・。
とりあえず今日はここまでにしておこう。
*3:Simplified Wrapper and Interface Generator
*4:Web サービスはひとつの言語にしぼったとしても、RPC サービス、Web ブラウザ向けクライアント、Ajax クライアント、iOS App クライアント、Android クライアント・・・などを考えると、そんな気がします
*6:Yet Another Compiler-Compiler
*7:Lexical Analyzer Generator
*8:ダウンロードには curl を使うこともありますが、facebook といえば wget ですよね
yamake is makefile generator from YAML
「yaml って Makefile を書くのに向いてるんじゃないか?」で書いたものを試しに作ってみた。
scons がお気に入りなので、Python で書いた*1。今のところ YAML から scons のスクリプトを出力することができるだけ。
これが・・・
project: name: hello type: executable src: - helloworld.c - helloearth.c - hellouniverse.c lib: - foo incdir: - contrib/include libdir: - contrib/lib --- platform: linux src: - hello_linux.c --- platform: darwin src: - hello_darwin.c --- platform: win32 src: - hello_win32.c
こうなる。
import os def append_items(target, items): for item in items: target.append(item) env = Environment() platform = env['PLATFORM'] sources = ['helloworld.c','helloearth.c','hellouniverse.c',] cpppath = ['contrib/include',] libpath = ['contrib/lib',] libs = ['foo',] if os.environ.has_key('CFLAGS'): env.Append(CFLAGS = os.environ['CFLAGS']) if platform == 'posix': append_items(sources, ['hello_linux.c',]) append_items(cpppath, []) append_items(libpath, []) append_items(libs, []) elif platform == 'darwin': append_items(sources, ['hello_darwin.c',]) append_items(cpppath, []) append_items(libpath, []) append_items(libs, []) elif platform == 'win32': append_items(sources, ['hello_win32.c',]) append_items(cpppath, []) append_items(libpath, []) append_items(libs, []) env.Program( target = 'hello', LIBS = libs, LIBPATH = libpath, CPPPATH = cpppath, source = sources, )
ソースコードは割とひどいが、少しずつ整理してオレオレ make 環境を整えていこう。簡単に TODO を書いておく。
- オプションの強化
- 出力フォーマットの強化
- Android.mk が出力できるようにする
- Visual Studio の .vcproj ファイルを出力できるようにする
- nmake 用のメイクファイルを出力できるようにする