Vimプラグインを作っていると、特定の機能がどのバージョンで実装されたか調べるのが難しいことがあります。
ヘルプ (:h vim7
, :h vim8
) や git log
のコミットメッセージから探せば多くのケースでは見つかるのですが、うまく検索できなくて見つからないこともあります。
また、急にテストが古いVimで落ちるようになったときに、どの機能が原因で落ちているのかつかめないこともあります。
確実な方法は git bisect
すれば良いのですが、これには2つの問題があります。
後は純粋な興味もあって、古いバージョンのビルド済みバイナリーを作っておこうと思い立ちました。 しかし1パッチごとにビルドするのはあまりに大変です。 そもそも当時のコンパイルでもビルドできず、その後のパッチで直ったりするバージョンがあるので、どうしてもビルドできないバージョンは存在します。 後はディスク容量の問題もあります。
まずは以下のようなスクリプトで100パッチごとにビルドしてみることにしました。 macOS・clang 12.0.0 (clang-1200.0.32.27) で v7.3.000 から v8.2.2000 までの103バージョンをビルドすることができました。
vim-backcompile.sh
#!/bin/bash set -euxo pipefail if ! command -v gsed >/dev/null; then echo 'gsed required' >&2 exit 1 fi if [[ ! -d /tmp/vim ]]; then git clone https://github.com/vim/vim.git /tmp/vim fi cd /tmp/vim backup_dir=/tmp/vim-backup mkdir -p "$backup_dir" read -r -d '' -a tags < <(git tag --sort=-committerdate | grep -E '^v\d[.]\d(?:[.]\d(?:\d*00)?)?$' && printf '\0') for tag in "${tags[@]}"; do git restore . git switch --detach "$tag" gsed -i '/sizeof(uint32_t) != 4/,+1s/exit/return/' src/auto/configure # v8.2.1119 gsed -i '/if (\(p\|res\|e1.*\) [!=]= NUL)/s/NUL/NULL/g' src/*.c # v8.0.0046 gsed -i '/#define VV_NAME/s/, {0}$//' src/eval.c # v7.4.1648 gsed -i '/static struct vimvar/,+4s/dictitem_T/dictitem16_T/;/char\s*vv_filler.*/d' src/eval.c # v7.4.1648 if ! git grep -q 'struct dictitem16_S' src/structs.h; then gsed -i '/typedef struct dictitem_S dictitem_T;/a struct dictitem16_S {\n typval_T di_tv;\n char_u di_flags;\n char_u di_key[17];\n};\ntypedef struct dictitem16_S dictitem16_T;' src/structs.h # v7.4.1648 fi gsed -i 's/__ARGS(\(.*\))/\1/' src/proto/os_mac_conv.pro # v7.4.1202 gsed -i 's/!builtin_first == i/(!builtin_first) == i/' src/term.c # v7.4.914 gsed -i '/extern int sigaltstack/s/^/\/\//' src/os_unix.c # v8.0.1236 gsed -i '/+multi_byte/,+6s/FEAT_BIG/FEAT_NORMAL/' src/feature.h # v7.3.968 if ! ./configure; then make distclean || : rm -f auto/config.cache ./configure fi make -j8 src/vim --version major=$(./src/vim --version | head -n 1 | awk '{ print $5 }') minor=$(./src/vim --version | grep patch | awk '{ print $3 }' | sed -e 's/^[0-9]*-//g' || :) cp src/vim "$backup_dir/$(printf "vim-%s.%04d" "$major" "$minor")" done
- ビルドが通っても起動できない事があったので、
src/vim --version
で起動チェックを行う - 変わっていくソースコードにパッチファイルは使いにくいので、今のコンパイラでコンパイルできない箇所は
sed
で雑にパッチを当てる。
実行ファイルのサイズが少しずつ増える様子が分かりました。
Vimのサイズ pic.twitter.com/NcOMxzOQIJ
— (っ=﹏=c) .。o○ (@itchyny) November 12, 2020
今は上のスクリプトを少し変えて、25パッチごとにビルドして実行ファイルを保存しています。
ビルドできなかったのは 8.0.0750
(8.0.0751
で修正), 7.4.1625
(7.4.1626
で修正) のみでした。
ビルド済み実行ファイルが揃ったので、次はこれを使ってbisectします。
vim-bisect.sh
#!/bin/bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" read -r -d '' -a VERSIONS < <(cd "$script_dir" && ls vim-[0-9]* | sort --version-sort --field-separator=. && printf '\0') find_index() { for i in "${!VERSIONS[@]}"; do if [[ "${VERSIONS[$i]}" = "$1" ]]; then echo "$i" fi done } exec_command="${1:?specify bisecting command}" low_version="${2:-${VERSIONS[0]}}" high_version="${3:-${VERSIONS[${#VERSIONS[@]}-1]}}" if [[ ! "$low_version" =~ ^vim- ]]; then low_version="vim-$low_version" fi if [[ ! "$high_version" =~ ^vim- ]]; then high_version="vim-$high_version" fi low_index="$(find_index "$low_version")" high_index="$(find_index "$high_version")" test_version() { env VIM="$script_dir/$1" bash -c "$exec_command" } if test_version "$high_version"; then echo "$high_version is good" exit 1 fi if ! test_version "$low_version"; then echo "$low_version is bad" exit 1 fi bad_version="$high_version" while [[ "$low_index" -lt "$high_index" ]]; do mid_index="$(((low_index + high_index) / 2))" version="${VERSIONS[$mid_index]}" if test_version "$version"; then echo "$version: good" low_index="$((mid_index + 1))" else echo "$version: bad" high_index="$((mid_index))" bad_version="$version" fi done echo "$bad_version is the bad version"
$VIM
という環境変数に実行ファイルのパスを入れるので、 vim-bisect.sh '! env THEMIS_VIM=$VIM /tmp/vim-themis/bin/themis --reporter spec >/dev/null'
のようなコマンドで vim-themis を使ったテストを回すことができます。
すべてのパッチバージョンに対する実行ファイルを用意できるわけではないので、git bisect
みたいにこのパッチがbad commitというところまではいけませんが、50パッチや25パッチの範囲くらいならコミットログに全部目を通すことはできます。
ビルド済みbisectはめちゃくちゃ便利なので、ぜひ試してみてください。
おしまい