CircleCIに最新のSQLite3をインストールした状態でsqlite3 gemを使う

ちょっと複数の問題が混ざっていて難儀したけど、切り分けたうえでタイトルの話が解決したのでナレッジをシャアさせていただきます。

まとめ

かっこつけて Tl; dr とか書く必要なくね? まとめでよくね?

  • ruby の native extension は gem のバージョンの他に build 前のソースのバージョンにも気をつける
  • ssh や伝統的なコンパイルの方法1とか知ってると便利!
  • CI の整備をするのってやっぱアプリの開発とは違うノウハウが要るんだなぁ〜

じゃあ何が起きて何が分かったか順にいきましょう。れっつらごー。

activerecord-importを使ったらCircleCIでSyntax Error

activerecord で bulk insert をやりたくて activerecord-import を使ったんですよ。

ActiveRecord のバージョンによって対応してるバージョンが違うので、それに気をつけてインストールしましょうって程度で、開発自体は特に難しくない。サクサクと進んでテストも済んで、よしよしと思っていたんだけど、よしじゃあ pull-req ってみますかねと思ったところで、

ここで生成されるSQL

INSERT INTO "tables" ("id", column_name1, column_name2, ...)
            VALUES (NULL, value1, value2, ...)

が SyntaxError になってしまうという問題に遭遇。

しかも CircleCI でだけ。うへえ。

SQLite 3.7.11のinsert文拡張

SQLite Release 3.7.11 On 2012-03-20

ここに書かれている

  • Enhance the INSERT syntax to allow multiple rows to be inserted via the VALUES clause.

がポイント。つまり CircleCI の SQLite3 が 3.7.11 より古い可能性がある。2

2015-12時点でCircleCIはUbuntu 12.04

CircleCI の環境はサイト上に記載されている。

Base

Our base image uses Ubuntu 12.04, with the addition of many packages commonly used in web development. Some specifics:

  • Architecture: x86_64
  • Username: ubuntu
  • Ubuntu 12.04 (precise)
  • Kernel version: 3.2
  • git 1.8.5.6
  • gcc 4.6
  • g++ 4.6
  • GNU make 3.81

via. https://circleci.com/docs/environment

各種言語のバージョン情報などはたんまりあるが、残念ながら SQLite3 に関する情報はない。はてどうするか。

CircleCIのbuild環境にsshでログインする

ここから急にサーバ管理方面の知識が必要になる。イマドキ(小規模な) Web アプリの開発や deploy に ssh は不要だと思ってたけど、CI のおまかせセットから外れるには力が必要なのだ。

CircleCI には build 環境に ssh で繋ぐ方法があり、それはちゃんとドキュメントに書かれている。

SSH access to builds - CircleCI

2015-12-18時点でまだサービスのデザイン変更にドキュメントが追従していないが、まぁやり方は一緒である。

ここで接続には GitHub に登録してある鍵を利用する のがポイント。

ssh 接続の際に何が起きてるかよく分からないという場合は -v オプションを重ね打ちするとよいぞ。今は Windows の人も openssh が使えるらしいので、ナレッジが共有できて便利ですね。

で、

sqlite3 --version

と叩くと 3.7.9 と返ってくる。間違いない、こいつだ。

そもそも native extension とは

SQLite3 に限らないんだけど、native extension とはざっくり言うと

Ruby と、 C などの Ruby の外の世界を繋いでいるもの

である。

この繋ぎ方に作法があり、SQLite3 で言うと

  1. SQLite3 の C言語のヘッダやコンパイル済みのライブラリを準備しておき
  2. `gem install` の際に Ruby とリンクする

という作業を行う。よく CentOS など yum の世界では

yum install sqlite-devel

Debian / Ubuntu 系の世界では

apt-get install libsqlite3-dev

しておけと StackOverflow などで Answer が出ているが、これは上の 1 の準備をしているという意味である。

つまり sqlite3 gem で言うと

  • sqlite3 gem のバージョンの他に SQLite3 本体のバージョンがある
  • CircleCI では SQLite3 本体のバージョンが古い
  • CircleCI の SQLite3 のバージョンを上げなきゃならない

Ubuntu 12.04のSQLite3を新しくするには

大きく二つ方法がある。

Debian 系の distro では apt でインストール可能なパッケージの配布元の設定をいじることで distro 標準のバージョンよりも新しいパッケージを入れることができる。これが一つ。

もう一つは cache と野良ビルドを使う 方法だ。

CircleCI でも apt-key から sources.list を更新してパッケージ全体を更新するというのはできなくもなさそうなのだが、そこ突っ込んで失敗すると時間の損失がでかそうだし、そもそも今やりたいのは CircleCI で使っている Ubuntu 全体の管理を刷新するということではなく、単に SQLite3 のバージョン上げろってことなので、ドキュメントや情報の揃っている、後者の方法を採用することにした。

誰か apt から新しいバージョンのインストールした方が簡単だよという手順を確立したら教えてください。

※ 古い話だと distro は Debian stable を入れておきながら一部のパッケージだけ testing から持ってくる、みたいなテクニックがあるのだが、そもそも Ubuntu って Debian testing ベースだし、いやいや、Ubuntu LTS ってもう次の 14.04 が出てからずいぶん経ってるよね、みたいなことを思いながら、粛々と古式ゆかしい作業に突入していくのであった。

CircleCIでSQLite3を野良ビルドする

ものによって気をつけるべきポイントは違うと思うが、SQLite3 の場合は面倒な依存もないのでとっても簡単。

https://www.sqlite.org/2015/sqlite-autoconf-3090200.tar.gz

からソースを取って来てビルドするだけ。こんな感じだ。

wget https://www.sqlite.org/2015/sqlite-autoconf-3090200.tar.gz
tar zxf sqlite-autoconf-3090200.tar.gz

cd sqlite-autoconf-3090200
./configure
make

コーヒーを抽出する前に終わる。

configure とか make とかイマドキだとやったことのない人も多いかもしれない。Web アプリ作るだけなら実際必要ない知識になりつつあるけど、こうして必要になるシーンもあったりするので、若い人も時間を見つけて「とりあえずコンパイルしてみた」みたいな経験をするのもよいことなのかもしれない。3

逆に年寄りでサーバ分かるよと言いたい人はこういう形で経験を生かせるとより全体の幸福に貢献できるので、積極的にイマドキの環境を試すのがよいと思う。

make installとgem install

さて、上の手順では普通やるはずの作業を一つやってない。

make install

だ。これは文字通り今コンパイルしたツールを利用可能な状態にインストールする作業で、普通は root 権限が必要なので

sudo make install

と実行する。

結論から言うと結局この通りに実行するのだが、考えなければいけないのは

今やろうとしていることはbundle install

だということ。

というのも、実は bundle install に当たって以下のことを考える必要がある。

  1. CircleCI ではすでに SQLite 3.7.9 のライブラリがインストールされている
  2. gem install の際に標準ではない依存ライブラリの場所を指定する必要がある
  3. gem install のオプション指定を bundler に教えてやる必要がある
  4. その指定したい場所にヘッダとライブラリを置く必要がある
  5. gem install の際には gem install で標準的に想定しているライブラリやヘッダの配置があり、それに従っていないとすごく面倒

ふー。やっぱ野良は面倒だ。

/usr/localに入れる

前述の 1 と 4, 5 の課題を一気に解決するのは

sudo make install

すること。

Debian 系では apt でインストールするパッケージは /usr 以下に、野良で入れるものは /usr/local 以下に、というルールがあり、何も考えずに make install すると /usr/local 以下にさっきコンパイルした SQLite がインストールされる。4以下のような感じだ。

/usr/local/include/sqlite3.h
/usr/local/lib/x86_64-linux-gnu/libsqlite3.a
/usr/local/lib/x86_64-linux-gnu/libsqlite3.la
/usr/local/lib/x86_64-linux-gnu/libsqlite3.so

ということで /usr 以下に SQLite 3.7.9 が、 /usr/local 以下に 3.9.2 がインストールされる。問題なく同居できる。

で、くり返すが大事なのは gem install 時にもこのような

{prefix}/include/
{prefix}/lib/

といった配置が期待されているということ。だからあとは bundle install 時に /usr 以下ではなく /usr/local 以下の SQLite3 をリンクしてくれるように教えてあげることができればオッケーだ。

gemに/usr/local以下のSQLite3を教える

ここが分かりにくい。native extension の作り方を知ってしまえばいいのだろうが、普通は native extention は作りたいんじゃなくて使いたいのだ。

とりあえず sqlite3 gem では `tasks/vendor_sqlite3.rake` の中にある `–with-opt-dir`が使える。具体的には

gem install sqlite3 -- --with-opt-dir=/usr/local

とすると目的を達成できる。

試しに上を実行して本当に新しいバージョンの SQLite3 を使っているかどうかは

ruby -e 'require "sqlite3"; p SQLite3::libversion'

で確認できる。

bundlerに/usr/local以下のSQLite3を教える

実際には CircleCI で動かすには普通は bundle install を利用して gem を入れているはず。gem コマンドに与えるオプションを bundle から指定するには

bundle config

コマンドを使う。

bundle config build.sqlite3 --with-opt-dir=/usr/local

とすると sqlite3 を build する際のオプションを指定できる。

CircleCIで野良ビルドしたアプリを効率的に利用する

さて、ここまでの処理を circle.yml から呼び出せるようにすると、

  • CircleCI で build する際に自動的に最新の SQLite3 をインストールして
  • それを利用して bundle install して
  • テストを実行する

ことができるようになるわけだが、普通に考えて すごく重たい ことは想像に難くない。

そこで cache を利用する。

cacheの指定とpre build処理

CircleCI の build 時に事前の処理と、その際生まれた成果物を再利用できるように cache するディレクトリを指定することができる。

具体的にはこんな感じ。

circle.yml

dependencies:
  cache_directories:
    - "~/sqlite3"
  pre:
    - ./_circle_script_install_sqlite3

注意しなければいけなのは ~ による $HOME の展開を YAML 上で期待する場合は quote する必要がある点。ちゃんと本家のドキュメントでは quote されているのだが、日本語のブログエントリを探してると、ここを検証せずに quote なしの記述を載せている場合がある。

本家のドキュメントを当たるのが何よりも基本

なので、ちゃんとドキュメント探そう。ついでに sqlite3 のインストールと bundle config 用のスクリプトも載せておく。

_circle_script_install_sqlite3

#! /bin/sh

if [ ! -e ~/sqlite3 ]; then
    mkdir -p ~/sqlite3
    cd ~/sqlite3

    wget https://www.sqlite.org/2015/sqlite-autoconf-3090200.tar.gz
    tar zxf sqlite-autoconf-3090200.tar.gz

    cd sqlite-autoconf-3090200
    ./configure
    make
fi

cd ~/sqlite3/sqlite-autoconf-3090200
sudo make install

bundle config build.sqlite3 --with-opt-dir=/usr/local

cache を使うので sqlite3 内の make の成果は消えない。つまり、コンパイルに掛かる時間はゼロになる。ただし、ライブラリのインストールは毎回行っている。

わざわざ毎回 make install と bundle config を叩いているのは、どうにも build が安定しなかったためで、本当は要らないかもしれない。

これで快適CircleCI + 最新SQLite3生活のできあがり

というわけで無事 CircleCI で SQLite 3.9.2 を使って activerecord-import を使ったコードのテストが通るようになりました。長かった。

ということでもう一度まとめ。

gem について

  • native extension は gem のバージョンだけでなく、利用しているソースのバージョンも大事

CircleCI について

  • build 環境に ssh でログインできる
  • 独自アプリのインストールができる
  • cache をうまく使えば独自アプリインストールをスキップして build 時間を削減できる

その他

  • C で書かれたプログラムの手動コンパイルの方法、Un*x系 のディレクトリの配置ルールなど古い知識も役に立つ
  • build 環境もコードで制御するとさらに幸せな CI 生活を送れる

おっさんの経験が不要になる環境は揃ってきているが、おっさんはおっさんで貢献できる。ちゃんと「枯れる」ノウハウ、スキルを身につけていきたいもんだなと改めて思ったのでした。

  1. ./configure; make; make install 

  2. ここにそんなすぐたどり着けるかというと、そこは微妙。いろいろ試行錯誤は必要だと思う。 

  3. Make を知っておくとイマドキのタスクランナーの話題を理解しやすいかも 

  4. この辺は distro というか OS ごとにルールが決まっている。 

More