Homebrewで自作Formulaを作るときの落とし穴
naoty/todo という CLI ツールを Homebrew で配布しようとしたときにハマったことを書く。
naoty/todo は Go で書かれており、コンパイル済みのバイナリを GitHub Releases にアップロードしてそこから配信したいと思っていた。ドキュメント等を調べると以下のように formula を書くことでインストールが完了するものと思っていた。
class Todo < Formula
desc "A todo management tool just for myself"
homepage "https://github.com/naoty/todo"
url "https://github.com/naoty/todo/releases/download/0.2.0/todo.tar.gz"
sha256 "be20e4069c0ae49998dfc00a010ca8f5d49d26193bd0c3e8611a4bf53cac469d"
def install
bin.install "todo"
end
end
しかし、実際には Empty installation
というエラーが発生してインストールができない現象に遭遇した。ドキュメントを調べてみるも、なぜこれが失敗するのか突き止めることはできなかった。そこで、エラーメッセージを頼りに Homebrew のソースコードを読むことにした。
まず、 Homebrew のソースコードは /usr/local/Homebrew/Library/Homebrew
にある。そこで ag で Empty installation
というエラーメッセージを検索してみると、以下のようなコードを見つけることができた。
if !formula.prefix.directory? || Keg.new(formula.prefix).empty_installation?
raise "Empty installation"
end
ここからは pry
を使ってブレークポイントを貼りながら進めようと思った。 Homebrew はシステムの Ruby を使っているようなので、 システムの rubygems で pry をインストールし調査を続けた。
binding.pry
で調べたところ、 empty_installation?
が true
を返しているようだった。このメソッドの中身は以下のようになっていた。
def empty_installation?
Pathname.glob("#{path}/**/*") do |file|
next if file.directory?
basename = file.basename.to_s
next if Metafiles.copy?(basename)
next if %w[.DS_Store INSTALL_RECEIPT.json].include?(basename)
return false
end
true
end
さらにここでイテレーションされている file
を調べると formula でインストールした todo
と README
等のファイルが含まれていた。ここで何が原因か調べてみると、どうやら以下のように todo
が README や LICENSE といったメタファイルのひとつとして扱われていて、ここで true
が返っているようだった。
BASENAMES = Set.new %w[
about authors changelog changes copying copyright history license licence
news notes notice readme todo
].freeze
ということは、メタファイルではないものがひとつでも存在すれば true
が返るということなので、以下のような formula を定義して適当なファイルを置くことで、この問題を回避することができた。
def install
bin.install "todo"
# Avoid "Empty installation" error which will be caused when the only
# "todo" file is installed.
bin.install "empty"
end
この問題は license
や changelog
といった名前のパッケージを配布する場合でも起こる。ソースコードを読まないと原因が分からないので、同じ問題に直面した人は不運という感じがする。