[FreeBSD] メガアップデート

ソフトウェア更新作業にはたまに膨大なパッケージの交換を要するときがあり、 そんなことを mega updates ともいうそうです*1。 最近 Unicode を扱う基幹ライブラリ ICU が更新され、 FreeBSD の port でも 4.6 版に改められました。 私のマシンに入れている 1172 個の ports のうちこの port に依存する ports は 370 ほどもありました。 貧弱なマシンにとって交換作業は荷が重すぎます。
devel/icu の更新はインストールするライブラリのファイル名を libicui18n.so.38 から libicui18n.so.46 に変えたので、 これに頼る ports のうち旧名で呼び起こそうとする依存プログラムはすべてビルドしなおし再インストールが必要です。 *2 しかしなかにはアイコンファイルを入れるだけのパッケージなどもあり、 それらは更新の必要がありません。 またライブラリ依存しているプログラムのなかには libicui18n.so に依存となっているものもあり、 これらの ports は更新が必要ではありませんでした。 これらを除外すればかなり手数を減らせるはずです。

作業の手順

更新の必要があるかどうかを判定するには ELF バイナリファイルを調べて名前が決め打ちになっているものを探し、 そのファイルをインストールしている ports だけを更新すればよいと考えました。 判定には file コマンドと ldd コマンドを利用します。
FreeBSDports 管理者が提供する自動化スクリプトもうまく使えばこの作業を軽減できるはずなのですが、 マシンが貧弱なため利用を諦めました。 たとえば、 毎日 ports の情報を更新するたびに走らせている portversion コマンドはデータベースを更新するのにかなり時間を費します。 インストールした ports の数が多すぎるためなのかもしれません。 portupgrade のような自動化スクリプトも同様に各 port の更新のたびにデータベースを書き換えるので負担が大き過ぎ、 時間がいくらあっても足りません。
したがって手作業で順に更新をするので、 自作したスクリプト で約 370 の ports の依存関係を示す表を作ります。 INDEX ファイルに似ています。 これはその抜粋です。

...
 libgnomecups-0.2.3_4,1||print/libgnomecups|
 libgnomeprint-2.18.8||print/libgnomeprint|
 xfce4-print-4.6.1_6||print/xfce4-print| libxfce4gui-4.6.4 libxfce4util-4.6.2 xfce4-conf-4.6.2
 dirmngr-1.1.0_3|1.1.0_1|security/dirmngr| libksba-1.1.0
 gnupg-2.0.16_4|2.0.16_2|security/gnupg| libksba-1.1.0
 gpgme-1.3.0_1|1.3.0|security/gpgme| gnupg-2.0.16_4 libksba-1.1.0
 libksba-1.1.0|1.0.8|security/libksba|
 brasero-2.32.1||sysutils/brasero| desktop-file-utils-0.15_2 gnome-desktop-2.32.1 nautilus-2.32.2 totem-pl-parser-2.32.1
 lxterminal-0.1.9||sysutils/lxterminal|
 thunar-volman-0.3.80_2||sysutils/thunar-volman-plugin| Thunar-1.0.2 desktop-file-utils-0.15_2 libexo-0.3.107 libxfce4gui-4.6.4 libxfce4util-4.6.2 xfce4-conf-4.6.2 xfce4-panel-4.6.4
...

"|" 記号で区切られた第 1 項は port 名、 第 2 項は旧版番号、 第 3 項は port のパス、 最後の第 4 項が先に更新が必要な依存先の ports です。 たとえば /usr/ports/security/libksba に入って libksba-1.0.8 を libksba-1.1.0 に更新すれば、 そのあとに /usr/ports/security/gnupg に移って gnupg-2.0.16_2 が gnupg-2.0.16_4 に更新できるというわけです。 また lxterminal-0.1.9 は版番号を改めないまま再インストールが必要かもしれないという意味です。
更新を行なったらついでに /var/db/pkg/ 以下に記録されている ports のうち今回更新した port に依存している port を検索し、 その +CONTENTS ファイルに記されたバージョン情報を、 旧版番号から新版番号に改めておきます。 こうすると pkgdb -Fi の時間が短縮できるのです。 更新後は port 名をこの表から削除してゆきます。 第 4 項が空になった ports をつぎに更新することになります。 どんどん表が小さくなってゆくのを見るとかなり満足できます。
さて今回は版番号を改めない ports のうち再ビルドが必要ないものを判定するつもりでいます。 上の表では第 2 項が空のものを調べます。 つぎのようなスクリプトで更新が必要かどうかを決めました。

#! /bin/sh

usage()
{
	cat <<EOF
Usage: `basename $0` [OPTION] [PORTNAME]
Option:
	[-v|--verbose] : show results of \`ldd -a\`
Portname:
	It should be a full name.  If you give a portion,
	this script gives some suggestion.
EOF
	exit $1
}

if test $# -eq 0; then
	usage 1 1>&2
fi

verbose=false

while test $# -gt 1; do
	case "$1" in
	-v|--verbose)
		verbose=true
		;;
	esac
	shift
done
dir=$1

contfile=/var/db/pkg/${dir}/+CONTENTS
if [ ! -e ${contfile} ]; then
	candidate=`(cd /var/db/pkg/; ls -d ${dir}*)`
	if [ "" != "${candidate}" ]; then
		echo "Which port?: " ${candidate}
	fi
	exit
fi

base=`head ${contfile} | grep @cwd | sed 's,@cwd ,,'`
for item_file in `grep -v @ ${contfile}`
	do if [ "" != "`file ${base}/${item_file} | grep ELF`" ]; then
		blame=`ldd ${base}/${item_file} | grep "not found" | sort | uniq`
		if [ "" != "${blame}" ]; then
			if [ "${verbose}" = "true" ]; then
				printf "########## ${item_file}\n"
				#printf "\e[36m%s\e[0m\n" "${item_file}"
				#printf '(\[$(tput md)\]%s \[$(tput me)\])\n' "${item_file}"
				ldd -a ${base}/${item_file} | grep -E "^/usr/|not found" \
				| grep -B1 "not found"
			else
				echo ${item_file}:${blame}
			fi
		fi
	fi; done

要するにこのスクリプトは、 インストールされているファイルに ELF バイナリがあれば ldd コマンドで中を見て、 依存関係が壊れているものを表示します。 依存関係に何も問題なければ上の表からその port 名を除去してゆきます。

Qt は問題ないが gnome 系は全滅

glib から更新が必要になりました。 dbusgnome 関係や gstreamer 系列やらが軒並み更新の荒波に洗われてゆきました。 一方でほとんどの Qt4 ライブラリやアプリケーションは依存関係が壊れずに済んでいることがわかりました。 Xorg のライブラリも問題ありませんでした。 これは助かります。 この記事を書いている時点で残り 125 余りとなりました。 しかし webkit-gtk2 の再ビルドという大仕事があるのでまだまだ先は長そうです。

*1:うろ覚えなので mega upgrades だったかもしれません。

*2:公式ルールではこのような ports はそれぞれの PORTREVISION 番号が増加されるので、 更新スクリプトを使えば作業は自動化できます。 しかし今回はまだその措置が取られていないので自力で調べて作業する必要がありました。