WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Homebrew の複数運用支援ツール「Maltybrew」を公開しました

既に何回か書いていますが、私はオープンソースなソフトウェアを開発環境 (Mac) にインストールするとき、 Homebrew などのパッケージ管理ツールを使わずに自前でビルドしていました(MySQL とか、 nginx とか、 Tornado とか)。フリーで仕事をしている関係上、基本システムはなるべく素の状態のままにしておきたいからです。

でも、実は Homebrew ってひとつのマシンに複数インストールしたりもできるんですよね。それなら複数の Homebrew を切り替えて使えば万事解決なんじゃないか・・・なんて以前から考えていたことをエイヤッとやってみたのが、本日ご紹介する「Maltybrew」です。なんという安直なネーミング。

rvm (rbenv) とか virtualenv とか nvm とか使うまでもなく、 Homebrew の環境ごと一気に切り替え。そんなドライなお付き合いをしたい方にお勧めです。

Homebrew とは

おそらく皆さんご存知かと思いますが、 Homebrew についても一応説明しておきます。 Homebrew は、 OS X への各種オープンソースソフトウェアのインストール・管理を支援するパッケージマネージャです。 Linux でいう RPM や APT の Mac 版といったところですね。 Homebrew さえ入れておけば、例えば以下のコマンドひとつで MySQL をインストールできます。

brew install mysql

Homebrew 自体のインストール方法や使い方は今回の趣旨ではないので省きますが、検索すれば多くの解説が見つかります。軽く探した中では、以下のページがとてもよくまとまっていました。

Mac デ Homebrew ノススメ | Kitchen Garden Blog

他のパッケージマネージャにない Homebrew の特徴のひとつとして、インストール場所が固定されていないということがあります。任意のディレクトリに Homebrew 本体をインストールすれば、後にインストールするパッケージも含めて、全てのファイルがそこに収まります。このため、複数のディレクトリに別々の Homebrew 環境をインストールすることも可能なのです。

しかし、そうして複数の Homebrew 環境を用意しても、実行パスやライブラリパスなどを適切に設定しないと実際に使うことはできません。そうした設定を自動化するのが、 Maltybrew の目的です。

Maltybrew のインストール方法

Maltybrew は Github で公開しています。といっても中身はシェルスクリプトがひとつだけです。

#! /bin/bash

# This is an utility shell-script to manage multiple homebrew installations.
# You can install new homebrew environment named "dev" by:
#
#   maltybrew new dev
#
# Then switch to this environment:
#
#   maltybrew switch dev
#
# Now you are in "dev" environment. You can install any formura without
# affecting your default system.
#
#   brew install ruby
#
# To back to the original environment, simply exit the current shell by
# exit command or Ctrl-D.
#
# note:
# maltybrew modifies only PATH and DYLD_LIBRARY_PATH by default. you can add
# additional initialization/cleanup process in .maltybrew/<env-name>/maltyrc.

if [ -z $MALTYBREW_HOME ]; then
    MALTYBREW_HOME=$HOME/.maltybrew
fi

function maltybrew_new {
    local ROOT=$MALTYBREW_HOME/$1

    if [ -z $1 ]; then
        return 1
    elif [ -e $ROOT ]; then
        return 2
    fi

    mkdir -p $MALTYBREW_HOME && mkdir $ROOT
    if [ $? -ne 0 ]; then
        return 3
    fi

    curl -L https://github.com/mxcl/homebrew/tarball/master | tar xz --strip 1 -C $ROOT
    if [ $? -ne 0 ]; then
        return 4
    fi

    $ROOT/bin/brew update

    cat <<EOF > $ROOT/maltyrc
# -*- mode:shell-script -*-

# \$1: enter or exit
# \$2: ~/.maltybrew/<env-name>

if [ \${1%%-*} == enter ]; then

    :
    # Put additional initializations here.
    #
    #export PATH=\$2/share/npm/bin:\$2/share/python:\$2/Cellar/ruby/1.9.3-p362/bin:\$PATH
    #
    #export ORIGINAL_LANG=\$LANG
    #export LANG=C

elif [ \${1%%-*} == exit ]; then

    :
    # Recover the original environment.
    #
    ## PATH and DYLD_LIBRARY_PATH are recovered implicitly.
    #
    #if [ \$ORIGINAL_LANG ]; then
    #    export LANG=\$ORIGINAL_LANG
    #else
    #    unset LANG
    #fi
    #unset ORIGINAL_LANG

fi
EOF

    return 0
}

function maltybrew_init {
    local ROOT=$MALTYBREW_HOME/$1

    if [ -z $1 ]; then
        return 1
    elif [ -d $ROOT ]; then

        export MALTYBREW_ORIGINAL_PATH=$PATH
        export MALTYBREW_ORIGINAL_LIBRARY_PATH=$DYLD_LIBRARY_PATH

        export MALTYBREW_NAME=$1
        export DYLD_LIBRARY_PATH=$ROOT/lib:$DYLD_LIBRARY_PATH
        export PATH=$ROOT/bin:$PATH

        if [ -f $ROOT/maltyrc ]; then
            . $ROOT/maltyrc enter $ROOT
        fi

    else
        return 5
    fi

    return 0
}

function maltybrew_cleanup {
    local ROOT=$MALTYBREW_HOME/$MALTYBREW_NAME

    if [ $MALTYBREW_NAME -a -d $ROOT ]; then

        if [ -f $ROOT/maltyrc ]; then
            . $ROOT/maltyrc exit $ROOT
        fi

        export PATH=$MALTYBREW_ORIGINAL_PATH

        if [ $MALTYBREW_ORIGINAL_LIBRARY_PATH ]; then
            export DYLD_LIBRARY_PATH=$MALTYBREW_ORIGINAL_LIBRARY_PATH
        else
            unset DYLD_LIBRARY_PATH
        fi

        unset MALTYBREW_ORIGINAL_PATH
        unset MALTYBREW_ORIGINAL_LIBRARY_PATH
        unset MALTYBREW_NAME

    else
        return 6
    fi

    return 0
}

function maltybrew_list {
    for name in $MALTYBREW_HOME/* ; do
        if [ -d $name ]; then
            echo ${name##*/}
        fi
    done
    return 0
}

function maltybrew_error {
    case $1 in
        0 ) return 0;;
        1 ) echo "Illegal envname." >&2 ;;
        2 ) echo "$MALTYBREW_HOME/$2 is already exist." >&2 ;;
        3 ) echo "mkdir $MALTYBREW_HOME/$2 is failed." >&2 ;;
        4 ) echo "Failed to install homebrew into $MALTYBREW_HOME/$2." >&2 ;;
        5 ) echo "$MALTYBREW_HOME/$2 is not exist nor a directory." >&2 ;;
        6 ) echo "You are not drunk." >&2 ;;
        * ) echo "Unknown error." >&2 ;;
    esac
    if [ -z $3 ]; then
        exit $1
    fi
}

function maltybrew_help {
    cat <<EOF
Create new homebrew installation named "dev":
  maltybrew new dev

Switch to "dev" environment:
  maltybrew switch dev

List all homebrew installations:
  maltybrew list

EOF
}

if [ -z $1 ]; then

    maltybrew_help

elif [ $1 == n -o $1 == new ]; then

    maltybrew_new $2
    maltybrew_error $? $2

elif [ $1 == s -o $1 == switch ]; then

    if [ $MALTYBREW_NAME ]; then
        maltybrew_cleanup
    fi
    maltybrew_init $2
    maltybrew_error $? $2

    $SHELL -i

    if [ $MALTYBREW_NAME ]; then
        maltybrew_cleanup
    fi

elif [ $1 == l -o $1 == list ]; then

    maltybrew_list
    maltybrew_error $? $2

elif [ $1 == switch_inplace ]; then

    if [ $MALTYBREW_NAME ]; then
        maltybrew_cleanup
    fi
    maltybrew_init $2
    maltybrew_error $? $2 no

else

    maltybrew_help

fi

このファイルをパスの通ったディレクトリに保存して chmod a+x すれば、インストールは終了です。例えば、 ~/bin に実行パスが通っているなら、以下のコマンドで OK です。

cd ~/bin
curl -o maltybrew https://raw.github.com/webos-goodies/Maltybrew/master/maltybrew.sh
chmod a+x maltybrew

ただし、 Homebrew の動作には Xcode のコマンドラインツールが必要なので、なければそれも入れておいてください(方法は前述のサイトで解説されています)。

使い方

使い方は単純で、 3 つのサブコマンドがあるだけです。

書式機能
maltybrew new <名前>新しい Homebrew のインストール
maltybrew switch <名前>指定した Homebrew への切り替え
maltybrew listインストールされている Homebrew のリストアップ

例えば、下のコマンドを実行すると「dev」という名前で新しい Homebrew をインストールします。以降、 Maltybrew でインストールした Homebrew を「Homebrew インスタンス」と呼びます。

maltybrew new dev

<名前> はなんでもかまいませんが、スクリプトの作りこみが甘いので空白や記号を入れるとまずいです。たぶん。英数字とアンダーバー、ハイフンのみが無難です。実際にインストールされるパスは ~/.maltybrew/<名前> になります。

Homebrew インスタンスのインストールが終了したら、 switch サブコマンドでその環境に切り替えることができます。

maltybrew switch dev

サブシェルが起動し、実行パスやライブラリパスなどが適切に設定されます。 brew コマンドも使えるようになるので、それを使ってパッケージのインストールや管理が可能です。

brew install mysql
# ... インストールのメッセージ ...
mysql --version
-> mysql  Ver 14.14 Distrib 5.5.29, for osx10.8 (i386) using readline 5.1

パッケージは現在の Homebrew インスタンス内にインストールされるので、外部の環境や他の Homebrew インスタンスには影響しません。当然、サブシェルを抜ければ使えなくなります。

exit
mysql --version
-> -bash: mysql: command not found

基本、これだけです。 Homebrew インスタンスを削除したいときは、単純に rm -Rf ~/.maltybrew/<名前> してください。

maltyrc ファイル

Homebrew のパッケージの中には、追加の設定を必要とするものがあります。例えば Homebrew で Ruby をインストールした場合、 gem の bin ディレクトリを実行パスに含めなければなりません。こうした追加の設定を行うために、 ~/.maltybrew/<名前>/maltyrc というファイルを用意しています。初期の内容は概ね下のようになっているはずです(コメントは変更しています)。

if [ $1 == enter ]; then
    # この Homebrew に切り替えたときに実行されるコード
    :
else
    # Homebrew 環境から出るときに実行されるコード
    :
fi

見ての通り Bash のシェルスクリプトで、該当する Homebrew インスタンスに switch したとき、もしくはそこから抜けるときに実行されます。 $1 には、前者の場合なら "enter" が、 後者なら "exit" が設定されます。 $2 には ~/.maltybrew/<名前> のパスが入っています。したがって、以下のように変更することで、 gem の bin ディレクトリに実行パスを通すことができます。

if [ $1 == enter ]; then
    # この Homebrew に切り替えたときに実行されるコード
    export PATH=$2/Cellar/ruby/1.9.3-p362/bin:$PATH
else
    # Homebrew 環境から出るときに実行されるコード
    :
fi

PATH (と DYLD_LIBRARY_PATH)は maltybrew が暗黙的に復元するので else 以降にはなにも書いていません。しかし、他の環境変数や環境変数以外のなんらかの設定を変更した場合は、元の値を退避しておき、 else 以降の部分で復元してください。例えば、 Homebrew 環境内でのみ LANG=C を設定したい場合は以下のようにします。

if [ $1 == enter ]; then
    # この Homebrew に切り替えたときに実行されるコード
    export PATH=$2/Cellar/ruby/1.9.3-p362/bin:$PATH
    export ORIGINAL_LANG=$LANG
    export LANG=C
else
    # Homebrew 環境から出るときに実行されるコード
    if [ $ORIGINAL_LANG ]; then
        export LANG=$ORIGINAL_LANG
    else
        unset LANG
    end
    unset ORIGINAL_LANG
fi

ちょっと面倒ですね。このあたりうまく自動化したかったのですが、良い方法が思いつきませんでした。アドバイス等募集中です。

その他

標準インストールの Homebrew との共存

私自身は標準インストール(/usr/local 以下)の Homebrew は入れていないのですが、あっても大きな問題はないはずです。 maltybrew switch した後はその環境にインストールしたものが優先され、なければ標準インストールのものが使われるはず。

とはいえ、パッケージによっては誤動作するかもしれないので、できればアンインストールして Maltybrew に一本化するほうが安全ではあります。

ログインシェルで特定の Homebrew インスタンスを使う

ログインしたときから(というか、ターミナルを起動したときから)特定の Homebrew インスタンスを使いたい、ということもあるでしょう。例えば「main」という名前のインスタンスを使う場合は、.bash_profile に以下の行を追加します。

. /path/to/maltybrew switch_inplace main

この場合、 maltybrew_cleanup というコマンドで素の環境に戻すことが可能です。

maltybrew_cleanup

これらの機能(switch_inplace と maltybrew_cleanup)は取り敢えずの実装なので、今後仕様を変えたりするかもしれません。その際はご容赦ください。

以上が Maltybrew の機能ですが、いかがでしょうか。常に複数環境が必要な場合は稀でしょうが、既存の環境を変えずに新しいパッケージを試してみたいということは多いのではないでしょうか。そんなときにご活用いただければと思います。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles