ひよっこ。

I want to…

Posts Tagged ‘bzr’

bazaarのプラグインを作る(2)

Posted by hikaruworld : 2011 12月 26

前回の続きです。

hello_world.pyだと色々とやりづらいので、
plugins以下にhello_worldという形でモジュール化しておきます。

$ ls ~/.bazaar/plugins
hello_world.py
$ cd ~/.bazaar/plugins
$ mkdir hello_world
$ mv hello_world.py hello_world/__init__.py

2-4.helloコマンドに引数を与える

bzr stなどにLOCATIONを指定できるように、
自分で作ったプラグインにもコマンドライン引数を設定する事が可能です。
というわけで、helloコマンドにユーザ名を指定して
hello usernameという形に出力するようにプラグインを拡張してみます。

引数を指定したい場合はtakes_argsというプロパティをクラス内に定義して、
取得したい要素を配列で定義します。そしてそこで定義した値をrun関数の引数で受け取る事が可能です。

# __init__.py
# クラス部分だけ抜粋
class cmd_hello(Command):
    takes_args = ["username?"]
    
    def run(self, username=None):
        print "Hello %s" % username

末尾に?をつけると任意要素に、つけない場合は必須要素になります。
他に末尾に指定できる項目は、?, *, +, $あたりがあるようですが詳しくは追っていません。
bzlib.command.pyの_match_argformを見る感じだと、+だと最低1つは存在し、かつ以降の値をまとめるとかできる感じですね。
詳しくはTODOにして後で検証してみます。
必須項目にしていた場合に設定せずにコマンド起動すると、ちゃんとbzrが叱ってくれます。

bzr: ERROR: command ‘hello’ requires argument USERNAME

2-5.オプションの設定

bzrのコマンドには引数ではなく、各種Optionの設定を行う事が出来ます。
どのコマンドでも利用できる-vや-hと言ったオプションと、例えばremoveコマンド独自の–forceといったような2つに大別されます。
これらのオプションはtakes_optionsをtakes_argsと同じように設定する事で利用が可能になります。

但し、前者(-vや-h)はそのオプション名を文字列で指定すればよいのですが、
独自オプションを作る場合は、個別にOptionクラスRegistryOptionを定義する必要があります。

TODO RegistryOption周りの説明

ここでは、verboseというどのコマンドでも利用可能オプションと、
このコマンド特有のhideというオプションの設定を行います。

# __init__.py
from bzrlib.commands import Command, register_command
from bzrlib.option import Option

class cmd_hello(Command):

    takes_args = ["username"]
    takes_options = [Option("hide", "username print asterisk"), "verbose"]

    def run(self, username, hide=False, verbose=False):
        print "hello %s" % ("*" * len(username) if hide else username)

これで–hideというオプションを設定した場合は出力が***になります

$ bzr hello hogepiyo
hello hogepiyo
$ bzr hello hogepiyo --hide
hello ********

2-6. プラグインのテスト

プラグインのテストはselftestというコマンドで実行する事が可能です。
但し、testtoolsに依存していますので、
存在していないと、こんな感じで怒られます。

bzr: ERROR: No module named testtools
You may need to install this Python library separately.

pipなりeasy_installなりでインストールしておきます。

selftestの仕組みはプラグインにtest_suite関数が定義されていれば自動でテストを実行してくれます。
なので、こんな感じで関数を定義しておきます。

# from hello_world.py
# 対象箇所のみ抜粋
def test_suite():
	pass
    from bzrlib.tests.TestUtil import TestLoader
    import tests.test_hello
    from unittest import TestSuite

    result = TestSuite()

    result.addTest(TestLoader().loadTestsFromModule(tests.test_hello))

これだけだと、テストの実体がないのでテストを読み込んで実行するようにします。
以下のように構成されているとします。

$ cd ~/.bazaar/plugins/hello_world
$ mkdir tests
$ touch tests/__init__.py tests/test_hello.py
$ tree .
.
├── __init__.py
└── tests
    ├── __init__.py
    └── test_hello.py

test_hello.pyを以下のように記述します。

# test_hello.py
from bzrlib.tests import TestCase

class TestHelloWorld(TestCase):
    def test_hello(self):
    	# とりあえず失敗させる
        self.assertEqual(1, 0)

__init__.py側でも該当のモジュール読み込むようにします。
# この辺りはbzrtools辺りのtest_suiteの実装を参考にしています。

# __init__.py
def test_suite():
    from bzrlib.tests.TestUtil import TestLoader
    from unittest import TestSuite

    import tests.test_hello

    result = TestSuite()
    result.addTest(TestLoader().loadTestsFromModule(tests.test_hello))

    return result

pythonのunittest.TestSuiteにaddTestしているだけですね。
TestUtil.TestLoaderはunittest.TestLoaderを拡張しているので基本は同じはず…。

これでselftestを実行してみます。

$ bzr selftest hello_world
FAIL: bzrlib.plugins.hello_world.tests.test_hello.TestBase.test_hellost_hello                                                                                                       
    Empty attachments:
  log

Traceback (most recent call last):
  File "~/.bazaar/plugins/hello_world/tests/test_hello.py", line 6, in test_hello
    self.assertEqual(1, 0)
AssertionError: not equal:
a = 1
b = 0

======================================================================                                                                                                              
FAIL: bzrlib.plugins.hello_world.tests.test_hello.TestBase.test_hello
----------------------------------------------------------------------
_StringException: Empty attachments:
  log

Traceback (most recent call last):
  File "~/.bazaar/plugins/hello_world/tests/test_hello.py", line 6, in test_hello
    self.assertEqual(1, 0)
AssertionError: not equal:
a = 1
b = 0

----------------------------------------------------------------------
Ran 1 test in 0.154s

FAILED (failures=1)

テストが成功するように修正して実行してみます。

# test_hello.py
#self.assertEqual(1, 0)
self.assertEqual(0, 0)
$ bzr selftest hello_world
bzr selftest: /usr/local/Cellar/bazaar/2.4.2/libexec/bzr
   /usr/local/Cellar/bazaar/2.4.2/libexec/bzrlib
   bzr-2.4.2 python-2.6.1 Darwin-10.8.0-i386-64bit

----------------------------------------------------------------------                                                                                                              
Ran 1 test in 0.154s

ok

何も出ませんが、成功しています。
どのテストが実行されたかは、-vをつけると分かります。

running 1 tests...
bzr selftest: /usr/local/Cellar/bazaar/2.4.2/libexec/bzr
   /usr/local/Cellar/bazaar/2.4.2/libexec/bzrlib
   bzr-2.4.2 python-2.6.1 Darwin-10.8.0-i386-64bit

bzrlib.plugins.hello_world.tests.test_hello.TestBase.test_hello                                                                                                       OK        2ms
----------------------------------------------------------------------
Ran 1 test in 0.156s

OK

3. 補足

3-1. プラグインロード時の問題

bzrは起動時にこれらのコマンドを読み込むため、__init__.pyにいわゆる『重い』処理を書いておくと
最初のbzrの起動がとても重くなってしまうそうです(2回目以降はキャッシュされますが…)。
そのための回避策としてチュートリアルには2つ方法が提示されています。

初期の読み込みを最小限にする

bzrの起動時に読み込まれるのが最初の起動ファイル?だけのようです。
ここで記述されている必要があるのがCommandクラスの定義とコマンドの登録のみなので、
それ以外を全て別のファイルに追い出してしまうという方法のようです。

遅延読み込みの実装を行う

lazy_import.lazy_importを利用する方法です。
この方法はコマンドが起動されるまで依存するモジュールの読み込みを遅延できるようです。
ドキュメントやビルドインコマンドでは以下のように読み込まれる事が確認できます。

from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
from bzrlib import (
    branch as _mod_branch,
    option,
    workingtree,
    )
""")

ただ、一部のプラグインは、commands.plugin_cmds.register_lazyを利用して読み込んでいるものもありました。
この辺はまだ調べていないのでTODOで。

3-2. 参考にしたサイト

Developing a plugin

以上です。大体こんな感じでプラグインが作成できます。
簡単ですね。あとはIntegrating with Bazaarあたりを参考にいじっていけると思います。

以上です。

広告

Posted in program | タグ: , , , | Leave a Comment »

bazaarのプラグインを作る(1)

Posted by hikaruworld : 2011 12月 25

bazaarのよいところの一つはpythonで簡単にプラグインを書ける事です。
知ってはいたんですが、作った事なかったのでHelloWorldしてみました。

1.基本的なルール

1-1.プラグインのインストール場所

bzrのプラグインは$HOME/.bazaar/plugins以下に配備すると自動でモジュールをロードしてくれます。
実際にどんなプラグインをインストールしているかは、

bzr plugins

とコマンドを打つことで確認できます。

1-2.参考にする実装

bzrは起動時にビルドインコマンドを読み込みます(bzr infoとかです)。
そのコマンド自体はbzrlib/builtins.pyで定義されているのでプラグインを作成するときには参考にしましょう。

2.helloプラグインの実装

2-1.プラグインの作成

プラグイン自体をbzrに認識させる方法は3つありますが、1つはビルドインコマンドなどが使う方法なので、通常は$HOME/.bazaar/pluginsに配備する方法で実現します。

1つ目のもっとも単純な方法は、plugins以下に任意のpythonファイルを置くことです。
例えば今回の場合であれば、以下のように配置し、bzr pluginsで確認してみると

$ cd ~/.bazaar/plugins
$ touch hello.py

hello
(no description)

という風に認識してくれます。

もう一つは、任意のディレクトリを作成して、__init__.pyを置くことです。
こちらの方が主流ですね(って、スクリプトだけのプラグインってみたことないですが)。

$ cd ~/.bazaar/plugins
$ mkdir hello
$ touch hello/__init__.py

但し、この状態ではプラグインのコマンドなどは定義されていないので、

$ bzr hello

とかやっても、こんな感じで怒られます。

invalid syntax (, line 1)
Unable to load ‘.hello’ in ‘~/.bazaar/plugins’ as a plugin because the file path isn’t a valid module name; try renaming it to ‘_hello’.
bzr: ERROR: unknown command “hello”

2-2.helloコマンドの作成

今回の目的はbzr helloとコマンドを打つと、
標準出力にHello Worldと出力するプラグインなので、
helloコマンドを新しく定義します。

bzrのコマンド(infoやbranchのようなもの)は
bzrlib.commands.Commandクラスを継承して実装する必要があります。
今回はhelloコマンドなので定義だけならこんな感じになります。
Defining a new commandに書いてあるままです。

from bzrlib.commands import Command, register_command

class cmd_hello(Command):
     pass

register_command(cmd_hello)

注意点は、クラス名のprefixとしてcmd_が付与されること、
アンダースコア(_)でつないだ場合にはコマンド時にはハイフンつなぎになる事くらいです。
# cmd_hoge_piyoはhoge-piyoというコマンドになります。

但し、このままではhelloコマンドを実行してもエラーになってしまいます。

bzr: ERROR: exceptions.NotImplementedError: no implementation of command ‘hello’

新しいコマンドを作成した場合は、run関数を実装してあげる必要があります。

from bzrlib.commands import Command, register_command

class cmd_hello(Command):
    def run(self):
        print “Hello World”

register_command(cmd_hello)

これでbzr helloと実行するとHelloWorldと表示してくれるプラグインが出来上がります。

$ bzr hello
Hello World

2-3.helloコマンドのプラグイン情報の記述

bzr pluginsやbzr help XXX(プラグインのコマンドとか)をしてみるとわかりますが、
プラグインにはさまざまなメタ情報が設定されています。

launchpad 2.4.1
Launchpad.net integration plugin for Bazaar.

この設定を行います。

バージョンの記述

バージョンの記述はプラグインを定義しているファイルにversion_infoという値をタプルで定義します。

version_info  = (0, 1, 2, "dev", 0)

上記のように定義してbzr pluginsで確認してみると0.1.2devという風に表示されます。
5番目の要素に0を指定した場合は空気読んで表示されないみたいですね。

プラグインの概要を記述する

bzr pluginsを実行したときにそのプラグインに関する概要が表示されると思います。
この設定もプラグインファイルの冒頭にコメントを記述することで表記可能です。

""" This plugin description"""

複数行書いても概要で表示されるのは1行目だけです。

これでHelloWorldプラグインは完成です。
こんな感じになります。

"""This plugin is print HelloWorld
"""
from bzrlib.commands import Command, register_command

class cmd_hello(Command):
    def run(self):
        print “Hello World”

version_info = (0, 1, 2, "dev", 0)

register_command(cmd_hello)

長くなってきたのでに続きます…。

Posted in program | タグ: , , | Leave a Comment »

MacOSXでbzrのbash-completionを利用する

Posted by hikaruworld : 2011 12月 23

MacのTerminalをbash環境で利用している場合に、bzrのbash-completionが欲しくなります。
bash-completionとは何かはこの辺りが参考になるかと。

bash-completionをhomebrewで

自分はbash-completionをhomebrewでインストールしたので、こんな感じ。

$ brew install bash-completion

で言われるがままに、~/.bashrcにこれを追記するとコード補完が有効になります。

if [ -f `brew --prefix`/etc/bash_completion ]; then
    . `brew --prefix`/etc/bash_completion
fi

bazaarのbash-completionを有効に

残念ながらそのままではgitやsvnは補完してくれますが、
bazaarは補完してくれません(Macの場合)。
bazaarを補完してもらうためには、bzr bash-completionをインストールします。

通常のプラグインと同じように、~/.bazaar/plugins以下にbranchを取得します。

bzr branch lp:bzr-bash-completion bash-completion

有効化するためにREADMEにあるように、~/.bashrcに以下を追記。

# read bash_completion for bzr
if [ -f $HOME/.bazaar/plugins/bash_completion/lazy.sh ]; then
    . $HOME/.bazaar/plugins/bash_completion/lazy.sh
fi

eval “`bzr bash-completion`”でもよいようですが、遅延初期化の方が無駄な待ちがないので。

これで、bzr sとか入れてTABを押すとこんな感じで補完されます。

bash-3.2$ bzr s
s-c              server           shelve           st
selftest         shelf1           shelve1          stat
send             shell            sign-my-commits  status
serve            shell-complete   split            switch

これで、変なタイポを減らせます。
# なぜかuncommitのコマンドばかり間違える私もこれでさよなら。

Posted in program | タグ: , , | Leave a Comment »

bzr qresolveなどで競合した場合に混乱したこと。

Posted by hikaruworld : 2011 12月 5

基本的には競合したファイルが一覧に表示されるので、
対象のファイルを右クリックして『競合をマージする』を行う。

すると設定されているマージツールが起動して競合を手動で解決することが出来る。
選択できるマージツールはwinmergeやkdiff3などの有名ところが用意されているが、
選択しているマージツールの右側に、
『External merge tool kdiff3 is not available』と表示されている場合、
右クリックしても『競合をマージする』が有効にならない。

これはPATHが通っていないことが原因のよう。
PATHを設定したくない場合qconfigやbazaar.confから
bzr.mergetool.XXX を新規に設定することで有効にすることが可能。
# XXXは任意の名前

例えば、自分の環境ではkdiff3を使っているのでqconfigで以下のように設定すると、

bazaar.confではこんな感じになる。

bzr.default_mergetool = kd3
bzr.mergetool.kd3 = D:\programs\KDiff3\kdiff3.exe {base} {this} {other} -o {result}

ちなみに、パスがProgram Filesのように半角スペースが含まれる場合は、ダブルクォートで
囲みましょう。
ただ、こんな罠もあるので注意。

Merge tools: can’t specify path includes whitespace

qconfig実行するとダブルクォートが増えるとか…

最後に、UIについて。

qconfigでデフォルトで提供されているマージツールにパスを設定できないとか、
qresolve上で利用できないマージツールが表示されないとか初心者に優しくないなーと思ったりした。

Posted in program | タグ: , | Leave a Comment »

bzrでマージしたらrevertとかが出来なくなった

Posted by hikaruworld : 2011 6月 16

リポジトリ構成は、セントラルがSVNで、そこにbzr-svnでつないでる状態。

svn ------ これがサーバ上のセントラルリポジトリ
↓
server-trunk ---- これはローカル上のSVNのミラー。bzr co
↓
trunk-dev -------- これはローカル上のブランチ。server-trunkからbzr branchした奴。

今回はtrunk-devの修正をserver-trunkからmerge trunk-devして、
server-trunkでrevertしたらこんなエラーが発生した。

bzr: ERROR: This tree contains left-over files from a failed operation.
Please examine D:/hoge/server-trunk/.bzr/checkout/limbo to see if it contains any files you wish to keep, and delete it when you are done.

bzr commit は通るんだけど、server-trunkをホストにmergeもrevertも通らない状況に。

取りあえず、警告に従って、limboを削除。
するとこんなエラーに変化

bzr: ERROR: This tree contains left-over files from a failed operation.
Please examine D:/hoge/server-trunk/.bzr/checkout/pending-deletion to see if it contains any files you wish to keep, and delete it when you are done.

言われるがままに、今度はpending-deletionを削除。

すると、無事解決しました。

取りあえず解決したんだけどなんか気持ち悪い。
何ぞこれ。

そういえば、削除したlimboは空だったけど、
pending-deletionは消していないのに消されていたステータスになってたファイルが残ってた。
うーん。

Posted in program | タグ: , , | Leave a Comment »