共有ホームディレクトリ環境の管理方法

MacPorts や apt などのパッケージ管理システムでインストールできないアプリケーションやライブラリ、自分で書いたツールなどを、ホームディレクトリにインストールしたいことは良くある。
ホームディレクトリならroot権限が要らないし、rootを持っている場合でも思わぬ操作ミスや設定ミスのリスクを抑えられる利点がある。アンインストールもしやすい。gem や easy_install などのスクリプト言語の管理システムが、OS全体のパッケージ管理システムと競合してしまう問題も回避できる。


このようにホームディレクトリにアプリケーションをインストールするときに、複数のバージョンを同時にインストールしたいことがある。また、異種のOSやCPUアーキテクチャのマシンでホームディレクトリを共有したかったりする*1 *2
以上のような要求があるときに、ホームディレクトリ環境をどうのように構築し、PATH や LD_LIBRARY_PATH などの環境変数をどのように設定すれば良いか? ./configureするときに、いちいち -I や -L を指定するのも面倒くさい。

なかなか一筋縄では解決しない問題だが、ここでは私の管理方法を紹介する。

ホームディレクトリの構造

ホームディレクトリに ~/arch というディレクトリを作り、この中に アーキテクチャ固有のディレクトリ特定のホスト固有のディレクトリ を作る*3

~/arch/
~/arch/linux-x86_64/   # アーキテクチャ固有ディレクトリ
~/arch/freebsd-i386/   # アーキテクチャ固有ディレクトリ
~/arch/fcore/          # ホスト固有ディレクトリ
~/arch/ucore/          # ホスト固有ディレクトリ
~/arch/net/            # 全ホスト共有ディレクトリ(スクリプトなどを置く)

アーキテクチャ固有ディレクトリやホスト固有ディレクトリの構造は共通で、以下のようになっている:

~/arch/linux-x86_64/
~/arch/linux-x86_64/app/
~/arch/linux-x86_64/app/bin/       # ここに PATH を通す
~/arch/linux-x86_64/app/lib/       # ここに LD_LIBRARY_PATH を通す
~/arch/linux-x86_64/app/relink     # bin/ や lib/ にシンボリックリンクを作成するツール
~/arch/linux-x86_64/path.conf      # path.conf。設定ファイル
~/arch/linux-x86_64/ruby-1.8       # アプリケーションなど。ディレクトリの命名規則は:
~/arch/linux-x86_64/ruby-1.9       #    <パッケージ名>-<メジャーバージョン>.<マイナーバージョン>
~/arch/linux-x86_64/msgpack-0.5    # マイナーバージョンが変わったら上書きせずに別のディレクトリに分ける
~/arch/linux-x86_64/msgpack-dev    # 自分で開発中のライブラリは -dev にしておくなど
~/arch/linux-x86_64/.bashrc        # 固有bashrc


アプリケーションをインストールしたら、app/bin や app/lib にシンボリックリンクを張る。この作業は relink スクリプト(後述)で自動化する。
path.conf ファイルは relinkスクリプトの設定ファイルである。

使い分け

基本的にはアーキテクチャ固有ディレクトリにインストールしていく。


ディストリビューション固有の機能を使っていたり、特定の職場やノートPCなどの環境だけで使うツールは、ホスト固有ディレクトリに置く。
私の場合は、例えば Wi-Fiの設定ツールや、colorgcc などをホスト固有ディレクトリにインストールしている。サーバ用マシンには、システム運用のために開発用とは別バージョンの ruby をインストールしてあったりする。


特定のアーキテクチャやホストに依存しないスクリプトやドキュメントは、全ホスト共通ディレクトリに置く。例えば pdumpfsmplexkansit などは全ホスト共通ディレクトリに置いている。

その他に、コマンドプロンプト($PS1)はホスト固有の .bashrc で設定している。

path.conf

path.conf は、.bashrc と relink スクリプトの2つから読み込まれる設定ファイルである。インストールしたアプリケーションのディレクトリ名を書いていく。
内容は↓このようになる:*4

#!/usr/bin/env bash

# path_bin はコマンド用
path_bin ruby-1.8
path_bin ruby-1.9 19
path_bin gtest-1.5

# path_lib はライブラリやヘッダ用
path_lib libev-3.9
path_lib luxio-0.2
path_lib gtest-1.5
#path_lib mpio-0.3
#path_lib msgpack-0.5

# 自分で開発している場合はバージョンを -dev にしてみた
path_bin kumofs-dev
path_bin msgpack-idl-dev
path_lib mpio-dev
path_lib msgpack-dev
path_lib msgpack-rpc-dev


このファイルは、.bashrc と relink スクリプトのどちらから読み込まれたかによって挙動が異なる:

.bashrcから読み込まれた場合
$C_INCLUDE_PATH や $LIBRARY_PATH を設定する
relinkスクリプトから読み込まれた場合
app/bin/ や app/lib/ にシンボリックリンクを作成する

このように挙動を変える仕組みは、.bashrc と relink スクリプトで function bin_path や function lib_path の定義を変えることで実現している。

C_INCLUDE_PATH や LIBRARY_PATH は gcc で使われる環境変数で、これを設定しておくと毎回インクルードパス(-I)やライブラリパス(-L)を指定しなくて済む。


path_bin には、第二引数に suffix を指定することができる。例えば path_bin ruby-1.9 19 と書くと、ruby-1.9/bin/* 以下のコマンドに 19 という接尾辞が付く。
具体的には、例えば rails とコマンドを叩いたときは ruby-1.8/bin/rails が実行され、rails19 と叩いたときは ruby-1.9/bin/rails が実行されるようになる。

~/.bashrcの実装

~/.bashrc の主な仕事は、アーキテクチャやホスト名を判別することと、関数や変数を定義すること、そしてアーキテクチャやホスト固有の .bashrc を読み込むことである。

アーキテクチャの判別

アーキテクチャやホスト名を判別して、$archdir$hostdir 変数を定義する。
具体的には、$archdir は ~/arch/darwin-i386 や ~/arch/linux-x86_64 あるいは ~/arch/sunos-i86pc のようになる。

いろいろな方法を試したが、次の定義がポータブルで良さそうである:

kernel=$(uname -s | tr "[A-Z]" "[a-z]")

if [ $(uname -p | wc -c) -le $(uname -m | wc -c) ]; then
    processor=$(uname -p)
else
    processor=$(uname -m)
fi

export arch=$kernel-$processor
export host=$(hostname -s)
export archdir="$HOME/arch/$arch"
export hostdir="$HOME/arch/$host"
export netdir="$HOME/arch/net"
関数や変数の定義

アーキテクチャを定義したら、それにあわせて各種変数や path.conf 用の関数を定義する:

function dev_path() {
    C_INCLUDE_PATH="$1/include:$C_INCLUDE_PATH"
    CPLUS_INCLUDE_PATH="$1/include:$CPLUS_INCLUDE_PATH"
    #OBJC_INCLUDE_PATH="$1/include:$OBJC_INCLUDE_PATH"
    LIBRARY_PATH="$1/lib:$LIBRARY_PATH"
}

function net_bin_path()   { PATH="$netdir/$1/bin:$PATH";   }
function net_sbin_path()  { PATH="$netdir/$1/sbin:$PATH";  }
function net_dev_path()   { dev_path "$netdir/$1";         }
function arch_bin_path()  { PATH="$archdir/$1/bin:$PATH";  }
function arch_sbin_path() { PATH="$archdir/$1/sbin:$PATH"; }
function arch_dev_path()  { dev_path "$archdir/$1";        }
function host_bin_path()  { PATH="$hostdir/$1/bin:$PATH";  }
function host_sbin_path() { PATH="$hostdir/$1/sbin:$PATH"; }
function host_dev_path()  { dev_path "$hostdir/$1";        }

# path.conf用関数
function path_bin() {
    echo -n ""  # 何もしない
}
function path_lib() {
    arch_dev_path "$1"
}

# PATH と LD_LIBRARY_PATH
export PATH="$netdir/app/bin:$archdir/app/bin:$hostdir/app/bin:$PATH"
export LD_LIBRARY_PATH="$netdir/app/lib:$archdir/app/lib:$hostdir/app/lib"
path.confと固有bashrcの読み込み

最後に path.conf とアーキテクチャやホスト固有の .bashrc を読み込む:

if [ -f $netdir/path.conf ];then
    source $netdir/path.conf
fi
if [ -f $netdir/.bashrc ];then
    source $netdir/.bashrc
fi

if [ -f $archdir/path.conf ];then
    source $archdir/path.conf
fi
if [ -f $archdir/.bashrc ];then
    source $archdir/.bashrc
fi

if [ -f $hostdir/path.conf ];then
    source $hostdir/path.conf
fi
if [ -f $hostdir/.bashrc ];then
    source $hostdir/.bashrc
fi

export PATH
export C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH
export LIBRARY_PATH

relinkスクリプトの実装

#!/usr/bin/env bash

function r {
  echo "$@"
  "$@"
}

function path_bin() {
  if [ -z "$1" ]; then
    echo "usage: path_bin() <app> [suffix]"
    exit 1
  fi
  name="$1"
  suffix="$2"

  for f in "$archdir/$name/bin/"*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/bin/`basename $f`$suffix"
    to="../../$name/bin/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done

  for f in $archdir/$name/sbin/*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/bin/`basename $f`$suffix"
    to="../../$name/sbin/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done
}

function path_lib() {
  if [ -z "$1" ]; then
    echo "usage: path_lib() <app>"
    exit 1
  fi
  name="$1"

  for f in $archdir/$name/lib/*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/lib/`basename $f`$suffix"
    to="../../$name/lib/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done
}

source "$archdir/path.conf"

ホスト固有のrelinkは、$archdir を $hostdir に変える。

新しいアプリケーションをインストールする具体的な手順

何かアプリケーションをインストールするときは、./configure --prefix= でインストール先のディレクトリを指定する:

# 同じアーキテクチャのホストで共有したい場合
$ ./configure --prefix=$archdir/msgpack-0.5
$ make -j4 install

# # このホストだけにインストールしたい場合
$ ./configure --prefix=$hostdir/msgpack-0.5
$ make -j4 install

その後で path.conf を書き換え、relink スクリプトを実行する:

# path.conf を書き換える
$ echo "path_lib msgpack-0.5" >> $archdir/path.conf  # ライブラリの場合
$ echo "path_bin msgpack-0.5" >> $archdir/path.conf  # コマンドの場合

# シンボリックを張る
$ $archdir/app/relink

# ライブラリをインストールした場合は、シェルを再起動する($C_INCLUDE_PATH や $LIBRARY_PATH を設定し直すため)

異なるバージョンをインストールする具体的な手順

基本的には新しいアプリケーションをインストールする場合と同じだが、path.conf の設定方法が異なる。

コマンドの場合は複数のバージョンを共存させることができる。この場合は、path_bin の第2引数に suffix を指定する:

path_bin ruby-1.8
path_bin ruby-1.9 19

共存させる必要が無い場合は、旧バージョンをコメントアウトしておけば良い:

#path_bin ruby-1.8
path_bin ruby-1.9

ライブラリの場合は、新バージョンが後ろに来るように書く。

path_lib msgpack-0.4
path_lib msgpack-0.5

参考文献

まとめ

ホームディレクトリにアプリケーションやライブラリをインストールする際の環境構築方法について紹介した。アーキテクチャやカーネルが異なるホストでホームディレクトリを共有したり、root権限を行使せずにアプリケーションをインストールできるようにするために役立つ。
基本はアーキテクチャやバージョンごとにインストールディレクトリを分け、シンボリックリンク環境変数で選択するというシンプルな構造だが、その管理タスクを path.conf で自動化している点がミソである。
大学の計算機環境が Mac/Linux/Windows の混成で、友人の間でノウハウを共有しながら管理方法を模索していったところ、このような方法に落ち着いた。今回 カーネル/VM Advent Calendar というイベントがあったので、ちょぃとまとめてみた(長いけど)。
実は問題点は色々ある。読者の皆様のノウハウも共有していただけると幸いである。

Future work

MANPATH も何とかしたい。
path_lib と path_bin を書き分けるのは面倒なので、自動判別して欲しい。
ホームディレクトリにインストールしたサーバアプリケーションも自動起動/自動再起動したいときがある。upstartを使えばできそうだが、D-Busの設定が必要になるかもしれない。
github にあげて欲しい。


Enjoy hacking!!

*1:特に様々なCPUアーキテクチャのマシンを所有している読者の皆様においては、身近な問題だと思われる!

*2:最近は仮想マシンに各種OSを簡単にインストールできるし、i386x86_64のライブラリはリンクできなかったりするので、マシンが1台しかなくても出番は多い。

*3:昔は「グループ固有ディレクトリ」もあり、任意のグループごとに便利スクリプトや各種設定ファイルを共有できるようにしていた。しかし自宅ネットワーク内にいくつもクラスタがあったりするわけではないので、今ではアーキテクチャ固有ディレクトリを使うことにした。一般に、数が増えると管理が大変になる。

*4:path.conf の先頭には #! を書いているが、直接実行することはなく、他のファイルから source で読み込んで使う。エディタで開いたときに確実にシンタックスハイライトされるので付けているだけ。