fc2ブログ

Entries

Bazaarの中央ブランチへのpushをHudsonのビルドトリガにしたい

タグ: Bazaar Hudson
# Subversionのpost-commitフック
REPOS="$1"
REV="$2"

SVNLOOK=/usr/bin/svnlook
CHANGED_DIRS=`$SVNLOOK dirs-changed -r $REV $REPOS`

# trunkにコミットされたらHudosnにビルド要求を出す
if echo $CHANGED_DIRS | grep "^trunk" > /dev/null
then
    wget -q -O /dev/null "http://localhost:8080/job/test/build"
fi

上記はSubversionのフックスクリプトだが、これをBazaarでやりたい。

リポジトリごとにフック置き場が用意されているSubversionとは違い、Bazaarのフックはプラグインの一種として「システム全体のプラグイン置き場」か「個人のプラグイン置き場」に置かなければならない。
それはつまりフックの中で「pushされたブランチの絶対パスを特定しなければならない」ということなので、まずは下記の様なフックを用い、ブランチの位置についてどのような情報を得られるのかを確認しておくことにする。

from bzrlib import branch

def print_branch(param):
    import os
    os.system('echo "%s" >> /var/tmp/branch.txt' % param.branch);

branch.Branch.hooks.install_named_hook('post_change_branch_tip', print_branch, 'print branch')

なお、このフックのトリガは「push」ではなく「tipの変更」である。よってcommitやuncommitでも動作する。
どちらかというとこちらの方が元々の用途にはあっているし、そもそもpushはクライアント側でしかフックできないため、pushされる側に置くことができない。

実行環境:

  • MacOS X 10.6.5
  • Python 2.6.1
  • Bazaar 2.2.0
$ mkdir -p /var/tmp/bzr/local
$ cd  /var/tmp/bzr/local
$ bzr init

まずはこうしてブランチを作って……

$ bzr commit --unchanged
BzrBranch7(file:///private/var/tmp/bzr/local/)

パスを省略したcommitの場合、パスはrealpathになる。

$ bzr commit --unchanged /var/tmp/bzr/local
BzrBranch7(file:///var/tmp/bzr/local/)

絶対パスを指定したcommitの場合、パスは指定した絶対パスになる。

$ bzr push bzr+ssh://idlysphere@localhost/var/tmp/bzr/remote
BzrBranch7(filtered-4319272656:///var/tmp/bzr/remote/)
RemoteBranch(bzr+ssh://idlysphere@localhost/var/tmp/bzr/remote/)

bzr+sshの場合、サーバ側のフック(前者)はプロトコル部がフィルタされた絶対パスになる。

以上のことから考えると、たとえば /var/bzr/central のブランチを監視したい場合は

from bzrlib import branch

def auto_build(param):
    import os
    from urlparse import urlparse
    if os.path.samefile(urlparse(param.branch.base).path, '/var/bzr/central'):
        import urllib
        urllib.urlopen('http://localhost:8080/job/test/build')

branch.Branch.hooks.install_named_hook('post_change_branch_tip', auto_build, 'auto build')

こんな風に書いておけば良さそう?

~/Applications

タグ: Mac SnowLeopard

Mac OS X 10.6.5でホームフォルダにApplicationsフォルダを作ったら

Applicationsフォルダを作る

アプリケーションフォルダのアイコンが付いた。

アイコンが付いた

フォルダをデスクトップにドロップしたら、普通のアイコンに変わった。

アイコンが消えた

デスクトップからホームフォルダに戻したら、またアプリケーションフォルダのアイコンに戻った。

またアイコンが付いた

もう一度フォルダをデスクトップにドロップし、新たにApplicationsフォルダを作ってみたところ、新しく作った方にはアプリケーションフォルダのアイコンは付かなかった。

アイコンが付かない

「ホームフォルダに作られたApplicationsフォルダは、最初に作られたものに限り、個人用のアプリケーションフォルダとして認識される」ということ?

最近見かけた酷いコード14

タグ: C++ 酷いコード VS2005

ぱっと見どこかに問題ありそうなのだが、その実なんとか動きそうで、でもやっぱり欠陥を含んでいるという、なかなか挑発的なコードに出会った。
開発環境はVC8。構造体アライメントは8バイト。バイトオーダーはリトルエンディアン。

unsigned char _buffer[27]; ///< データバッファ

まず27バイトのデータバッファがある。

/// データバッファ範囲
struct BufferRange
{
    size_t offset; ///< データバッファオフセット
    size_t size; ///< データサイズ
};

/// データバッファ格納データ範囲一覧
const BufferRange BUFFER_RANGES[] = {
    {  0, 10 }, // データHoge用バッファ範囲(0?9)
    { 10, 16 }, // データFuga用バッファ範囲(10?25)
    { 26,  1 }, // データPiyo用バッファ範囲(26)
};

/// データバッファ格納データインデックス一覧
enum BufferIndex {
    BUFFER_INDEX_HOGE = 0, ///< データHogeインデックス
    BUFFER_INDEX_FUGA, ///< データFugaインデックス
    BUFFER_INDEX_PIYO, ///< データPiyoインデックス
};

void Store(BufferIndex index, void* data)
{
    const BufferRange& range = BUFFER_RANGES[index];
    ::memcpy(_buffer + range.offset, data, range.size);
}

データバッファには

  1. データHoge(10バイト)
  2. データFuga(16バイト)
  3. データPiyo(1バイト)

の3つのデータを格納することになっている。

// データHoge構造体
struct Hoge
{
    long val1; // 4バイト
    long val2; // 4バイト
    short val3; // 2バイト
};

void StoreHoge(const Hoge& hoge)
{
    Store(BUFFER_INDEX_HOGE, &hoge);
}

StoreHoge関数は構造体Hogeの内容をデータHoge用バッファにコピーする。
構造体Hogeのサイズは4バイト・4バイト・2バイトに境界調整(JIS X 3014:2003 §3.9/5)用2バイトを含めて12バイト。
それに対しデータHoge用バッファのサイズは10バイトで、コピーするバイト数も10バイト。
一見、バッファにコピーするバイト数が足りていないように見える。
が、コピーされない2バイトは境界調整用の空き領域のため、最低限必要な情報量はコピーされる。

void LoadHoge(Hoge& hoge)
{
    const BufferRange& range = BUFFER_RANGES[BUFFER_INDEX_HOGE];
    ::memcpy(&hoge, _buffer + range.offset, sizeof(hoge));
}

LoadHoge関数はデータHoge用バッファから構造体Hogeにデータをコピーする。
この関数はStoreとは逆に2バイト余計に(データHoge用バッファから10バイト、データFuga用バッファから2バイト)コピーしているが、余計な2バイトは構造体末尾の境界調整用の空き領域にコピーされるため問題は起こらない。

// データFuga構造体
struct Fuga
{
    long val1; // 4バイト
    long val2; // 4バイト
    long val3; // 4バイト
};

void StoreFuga(const Fuga& fuga)
{
    Store(BUFFER_INDEX_FUGA, &fuga);
}

構造体Fugaのサイズ(12バイト)がデータFuga用バッファのサイズ(16バイト)より小さいという、Hogeの逆パターン。
一見、書き込み先の方が大きいためバッファオーバーフローの危険はないように見える。
が、逆に書き込み元が小さいためバッファオーバーリードが起こる。
今回書き起こした例は4バイトしかオーバーしないため問題が顕在化することはないかもしれない。
実際のコードでは30バイトほどオーバーしていたため、まれに読み込み可能領域外まで読みに行ってAccess Violationを起こすことがあった。

void LoadFuga(Fuga& fuga)
{
    const BufferRange& range = BUFFER_RANGES[BUFFER_INDEX_FUGA];
    ::memcpy(&fuga, _buffer + range.offset, sizeof(fuga));
}

Loadは問題ない。

// データPiyo列挙体(最大情報量1バイトの符号無し整数)
enum Piyo {
    PIYO1,
    PIYO2,
    PIYO3,
};

void StorePiyo(Piyo piyo)
{
    Store(BUFFER_INDEX_PIYO, &piyo);
}

列挙体Piyoのサイズは処理系依存(JIS X 3014:2003 §7.2/5)だが、今回の環境ではintと同じ4バイト。
それに対してデータPiyo用バッファのサイズは1バイトで、コピーするバイト数も1バイト。
一見、バッファにコピーするバイト数が足りていないように見える。
が、バイトオーダーがリトルエンディアンなので、「コピーする先頭1バイト=下位1バイト=データPiyoの全情報」となり、先頭1バイトだけで必要十分な量のコピーが行われることになる。

void LoadPiyo(Piyo& piyo)
{
    const BufferRange& range = BUFFER_RANGES[BUFFER_INDEX_PIYO];
    piyo = static_cast<Piyo>(_buffer[range.offset]);
}

データバッファの1バイトを列挙型Piyoにstatic_castする。
列挙型の列挙値の範囲にある場合、値は変換によっても変化しないが、そうでない場合の結果の列挙値は規定されていない(JIS X 3014:2003 §7.2/9)。
データバッファの型が符号付き型でかつ列挙型Piyoに128以上の値が与えられた列挙子があったら(符号付き型では負数になるため)問題になるところだが、幸いデータバッファの型は符号無し型なので、ここは特に問題にはならない。
ほかが酷いためつい警戒してしまうが、これはそんな目くじらを立てるようなコードでもないか。

……とまあ、こんな感じのコードが延々と続く。
環境依存であることを理解した上で敢えてこう書いているのか、はたまた「動くようにがんばったらこうなった」のか。
いずれにしろレビューではじくべきコードである。

最近見かけたCスタイルキャスト

タグ: C++ 酷いコード
// コンパイルオプション /DUNICODE 付き
HANDLE event = ::CreateEvent(NULL, FALSE, FALSE, (LPCTSTR)"HOGE");

最近というか、今年に入ってから書かれたコードでこの「マルチバイト文字列からUNICODE文字列ポインタへの強制キャスト」に2回も出会っている。
しかもレビューをくぐり抜け、結合段階まで行っている。
Cスタイルキャストの愛用者にはこのキャストが問題ないように見えるのだろうか?
いや、逆か。問題があることを理解しないからCスタイルキャストを使い続けているのか。

    Hoge* _hoge;

public:
    Fuga()
    {
        _hoge = new Hoge;
    }

    ~Fuga()
    {
        delete (Hoge*)_hoge; // 意味のないキャスト
    }

delete時にキャストする必要があるのは
「仮想デストラクタを持たない基底クラスのポインタから適切なポインタにダウンキャストしなければならない」

「void*ポインタ(あるいはそれに相当する整数値)から適切なポインタにキャストしなければならない」
という危険な設計の場合のみ。
それ以外の無意味なキャストは可読性と保守性を下げ、無駄にバグを埋め込める場所を増やす役割しか持たないことを理解していれば、こんなキャストを書く気にはならないはずなのだ。

ClearCaseに別れを告げるために

タグ: ClearCase Subversion

cc2svn 1.0.2
http://code.google.com/p/cc2svn/

ClearCaseで管理されているファイルを、更新履歴やラベル、ブランチ含めてSubversionのダンプファイルに書き出すツール。
使用言語はPython。どのバージョンのPythonを対象に書かれているのか明記されていないが、少なくとも2.6.5では動き、2.3.4では動かないらしい。
試しにPython 1.4で実行してみたが、当然のごとくエラーを返され動作しなかった。

% python
Python 1.4 (Feb 16 1998)  [GCC 2.7.2.3]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> ^D
% ./cc2svn.py
  File "cc2svn.py", line 216
    yield f.read(lastblock)
          ^
SyntaxError: invalid syntax

というわけでPython 2.7をインストール。
./cc2svn.py -helpくらいは動作することを確認したところで、設定ファイルconfig.pyの編集を開始。

CLEARTOOL = "/usr/atria/bin/cleartool"

cleartoolのパス。

CC_VOB_DIR = "/vobs/MY_VOB_DIR/path/to/root"

Subversionに変更履歴を引き継ぐルートディレクトリ。
ビューの指定も含めて "/view/my_view_tag/vobs/MY_VOB_DIR/path/to/root" と書いても良いが、その場合はcleartool catcscleartool setcsが正しく動作するよう、カレントディレクトリをmy_view_tag以下にする必要がある。

CACHE_DIR = "/var/tmp/cc2svn_cache"

キャッシュ作成先。

PUT_CCLINKS_TO_BRANCH = "main"

シンボリックリンクがどうのこうの。
ClearCaseでシンボリックリンクを使っていないのでよくわからない。

SVN_AUTOPROPS_FILE = THIS_FILE_DIR + "/config.autoprops"

~/.subversion/configのauto-propsと同じ。

CC_LABELS_FILE = THIS_FILE_DIR + "/labels.txt"

Subversionに引き継ぐラベル一覧ファイル。
この行をコメントアウトすると、ルートディレクトリに付けられた全てのラベルを引き継ぐことができる。

CC_BRANCHES_FILE = THIS_FILE_DIR + "/branches.txt"

Subversionに引き継ぐブランチ一覧ファイル。
CC_LABELS_FILE同様、コメントアウトすると全てのブランチを引き継げる。

CHECK_ZEROSIZE_CACHEFILE = True

サイズ0のファイルをキャッシュ内に見つけた時、ClearCaseから再度落としてくるか。

SVN_CREATE_BRANCHES_TAGS_DIRS = False

svndump.txtで/tagsと/branchesの作成も行うか。
Falseにした場合、リポジトリにsvndump.txtをsvnadmin loadする前に/tagsと/branchesを作っておく必要がある。

SVN_DUMP_FILE = "svndump.txt"

生成するSVNダンプファイルの名前。

HISTORY_FILE = "cchistory.txt"

生成するClearCase履歴ファイルの名前。

設定が完了したら./cc2svn.py -runで変換開始。

% ./cc2svn.py -run
2010/09/05 17:58:21: INFO: Loading CC history to /home/idlysphere/cc2svn/cchistory.txt
2010/09/05 17:59:11: INFO: Loading svn auto properties from /home/idlysphere/cc2svn/config.autoprops
2010/09/05 17:59:11: INFO: Processing ClearCase history, creating svn dump /home/idlysphere/cc2svn/svndump.txt
2010/09/05 17:59:12: ERROR: 'ascii' codec can't decode byte 0xbf in position 0: ordinal not in range(128)

ふむ……
ClearCaseの履歴をcchistory.txtに書き出したのち、EUCのコメントをASCIIコーデックでデコードしようとしてエラーになったようだ。
cchistory.txtは一度作られれば次回以降それを使いまわせるので、ひとまず自前で文字コードをEUCからUTF-8に変換し、cc2svn.pyでは素のまま扱うようにする。

197c197
<     return codecs.utf_8_encode(text)[0]
---
>     return text

再び実行開始。

2010/09/05 18:57:34: INFO: Processing ClearCase history, creating svn dump /home/idlysphere/cc2svn/svndump.txt
2010/09/05 19:17:12: INFO: Checking labels
2010/09/05 19:17:12: INFO: Checking LABEL1
2010/09/05 19:17:12: ERROR: Command failed: /usr/atria/bin/cleartool setcs /var/tmp/cc2svn_cache/label_config_spec_tmp_cc2svnpy
Command has non-empty error stream:
cleartool: Error: Operation stat, .: ファイルもディレクトリもありません。
cleartool: Warning: New config spec makes current working dir invisible.

「警告: 新しいconfig specは現在の作業ディレクトリを見えなくします」、か。
どれどれ……

% cleartool catcs
element * LABEL1

なるほど。config specを「LABEL1」だけにされては、上位ディレクトリpathやtoに「LABEL1」ラベルを付けていない /vobs/MY_VOB_DIR/path/to/root は見えなくなってしまう。
となると、多少大雑把ではあるが、ディレクトリについては全て可視にすることで対応してしまうのがいいか。

811c811
<                 file.write("element * " + label + "\n")
---
>                 file.write("element * " + label + "\n" + "element -directory * /main/LATEST\n")

三度実行。

2010/09/05 19:29:01: INFO: Processing ClearCase history, creating svn dump /home/idlysphere/cc2svn/svndump.txt
2010/09/05 19:49:17: INFO: Checking labels
2010/09/05 19:49:17: INFO: Checking LABEL1
2010/09/05 19:49:19: INFO: Checking LABEL2
……(略)……
2010/09/05 19:51:14: INFO: Completed

終わった\(^o^)/
こうして8083行のcchistory.txtから書きだされたsvndump.txtは、リビジョン数8033、サイズ約37MB(7z圧縮して約800KB)。それをsvnadmin loadしたSubversionリポジトリのサイズは、svnadmin packして100MB。
うーん……アトミックコミットではないから仕方ないとはいえ、変更量の割には巨大なリポジトリになってしまった。

ちなみに、試しにこのリポジトリをBazaarの2aリポジトリに変換してみたところ、obsolete_packsを除いたリポジトリのサイズは2MB程度に収まった。groupcompressの効果だろうか、Subversionと比べて大分小さい。
ただし、残念なことにこの流れ(ClearCase→Subversion→Bazaar)ではラベル(タグ)の引き継ぎがうまくいかない。cc2svnによって作成されたタグは「任意のリビジョンの別名」の体裁を取っていないため、

% bzr tags
LABEL1   ?
LABEL2   ?

このようにタグとリビジョンが紐付かなくなってしまう。

Appendix

タグ

Blog内検索