Python製自作ツールをHomebrewでインストール可能にする


動機

たとえばrandstrというスクリプトをpythonで実装したとする.
動作としては,与えられた引数の長さだけランダムな文字列を出力する.

$ python ./randstr.py 5
4UJrG
$ python ./randstr.py 10
1sHGTmjiAf

これを最終的に以下でインストール・配布できるようにしたい.

$ brew tap scstechr/ranstr
$ brew install randstr

全体の流れ

本記事はMacを前提にしてますが,流れ自体は他OSでも変わらないと思います.

  1. pyinstallerで実行ファイル(バイナリ)作成
  2. gitレポジトリを作成しGitHubにpush
  3. 実行ファイルなどを含む.tar.gzを作成
  4. 作成した.tar.gzファイルを添付したリリースをパブリッシュ
  5. brew createFormulaを作成
  6. 作成したFormulaをGitHub上のhomebrew-<name>push

1. pyinstallerで実行ファイル(バイナリ)作成

実装

randstrは以下のソースコードで実装されている.

import random
import string
import click

def randomname(n):
   randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
   return ''.join(randlst)

@click.command()
@click.argument('length')
def main(length):
    try:
        length = int(length)
        randlst = [random.choice(string.ascii_letters + string.digits) for i in range(length)]
        print(''.join(randlst))
    except:
        print('./randstr [length]')


if __name__ == '__main__':
    main()

実行ファイルの作成

まずpyinstallerを使ってバイナリファイルを作成する.
pyinstallerが入っていなければpip install pyinstallerで入れる.

$ pyinstaller randstr.py --onefile

これでbuild/, dist/, randstr.specができる.

ファイル構成
.
├── .git/
├── README.md
├── build/
├── dist/
│   └── randstr
├── randstr.py
└── randstr.spec

distディレクトリには実行形式のrandstrが入ってるので試しに実行してみる.

$ ./dist/randstr 5
FJCgc
$ ./dist/randstr 10
38cuZYR9xF

2. gitレポジトリを作成しGitHubにpush

ローカル

まず,git initしてレポジトリを作成する.
次に.gitignoreを作成してrandstr.specがコミットに追加されないようにする.
任意だがdist/,build/も必要なければ.gitignoreに追記する.

.gitignore
.*
*.spec
build
dist

git add -f .gitignore.gitignoreを強制的にaddする.

ファイル構成
.
├── .git/
├── .gitignore
├── README.md
├── build/
├── dist
│   └── randstr
├── randstr.py
└── randstr.spec

次にgit add ., git commitcommitする.

リモート

GitHubにレポジトリを作成し,先ほど作成したものをpushする.

3. 実行ファイルなどを含む.tar.gzを作成

ここで作成するのは*.tar.gzという実行バイナリを含むtar.gzファイルである.
今回の例ではrandstr-v1.0.tar.gzを作成する.

まず, randstr-v1.0ディレクトリを作成して,dist/randstrREADME.mdcpする.
ほかにLICENCEなど入れたいファイルがあれば追加する.

.
├── .git/
├── .gitignore
├── README.md
├── build/
├── dist
│   └── randstr
├── randstr-v1.0/
│   ├── README.md
│   └── randstr
├── randstr.py
└── randstr.spec

これをtar.gzに圧縮する.

$ tar zcvf randstr-v1.0.tar.gz randstr-v1.0

ついでにtar.gz.gitignoreに追加しておく.

.gitignore
.*
*.spec
build
dist
*.tar.gz

バッチファイルの作成

ここまでの処理をbuild.shなどのスクリプトを書いておくと楽.

#!/bin/bash

printf '\e[34mmkdir release\e[m\n';
mkdir release;
printf '\e[34mcp README.md -> release\e[m\n';
cp README.md release;
printf '\e[34mpyinstaller gch\e[m\n';
pyinstaller randstr.py --onefile && mv dist/randstr release/
printf '\e[34mtar zcvf\e[m\n';
tar zcvf release.tar.gz release
printf '\e[34mrm -rf build, dist, release\e[m\n';
rm -rf build
rm -rf dist
rm -rf release
printf '\e[34mrm spec filest\e[m\n';
rm *.spec

上のbuild.shchmod u+x build.sh後に./build.shとすると
実行ファイルの作成,README.mddist/randstrrelease.tar.gzに圧縮するまでをやってくれる.
あとはmv release.tar.gz randstr-v1.0.tar.gzなどしてあげれば良い.

.
├── .git/
├── .gitignore
├── README.md
├── randstr.py
└── randstr-v1.0.tar.gz

4. 作成した.tar.gzファイルを添付したリリースをパブリッシュ

GitHubに戻って, Releaseをクリックし Create a new releaseを選択する.

リリースノードを作成し,先ほど作成したrandstr-v1.0.tar.gzを添付する.
Publish releaseをクリックすれば無事パブリッシュされる.

5. brew createFormulaを作成

まず, レポジトリhomebrew-<名前>(今回はhomebrew-randstr)をGitHubで作成する.
これを好きなところにgit cloneしておく. 今回は~/Works/cloneしたとする.

$ git clone https://github.com/Scstechr/homebrew-randstr
Cloning into 'homebrew-randstr'...
warning: You appear to have cloned an empty repository.
~/Works/homebrew-randstr
.
└── .git/

次に,先ほどパブリッシュしたリリースに添付した実行ファイル(今回の例ではrandstr-v1.0.tar.gz)のリンクをコピーする.

そして,brew create <コピーしたURL>をターミナルで実行する.

$ brew create https://github.com/Scstechr/Randstr/releases/download/v1.0/randstr-v1.0.tar.gz

これを実行すると, Formulaと呼ばれるものが/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/randstr.rbとして(細かいディレクトリは違うかもしれないが)作成される.
すぐに編集できるように自動的にVimが開くが,これ一旦:wqで抜ける.
作成されたrandstr.rb<先ほどgit cloneした先>/randstr.rbmvする.

mv /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/randstr.rb ~/Works/homebrew-randstr/

これで~/Works/homebrew-randstrのファイル構成が以下の通りとなる.

~/Works/homebrew-randstr
.
├── .git/
└── randstr.rb

改めて開いてみると,randstr.rbはこんなファイルのはずである.

randstr.rb
# Documentation: https://docs.brew.sh/Formula-Cookbook
#                https://rubydoc.brew.sh/Formula
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
class Randstr < Formula
  desc "Random String Generator"
  homepage ""
  url "https://github.com/Scstechr/Randstr/releases/download/v1.0/randstr-v1.0.tar.gz"
  sha256 "5c3b72ae94a11d85b4cf460595c64336ad9985dc976a9daf8dc33a0cbf5c9cb8"
  # depends_on "cmake" => :build

  def install
    # ENV.deparallelize  # if your formula fails when building in parallel
    # Remove unrecognized options if warned by configure
    system "./configure", "--disable-debug",
                          "--disable-dependency-tracking",
                          "--disable-silent-rules",
                          "--prefix=#{prefix}"
    # system "cmake", ".", *std_cmake_args
    system "make", "install" # if this fails, try separate make/make install steps
  end

  test do
    # `test do` will create, run in and delete a temporary directory.
    #
    # This test will fail and we won't accept that! For Homebrew/homebrew-core
    # this will need to be a test that verifies the functionality of the
    # software. Run the test with `brew test Randstr`. Options passed
    # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
    #
    # The installed folder is not in the path, so use the entire path to any
    # executables being tested: `system "#{bin}/program", "do", "something"`.
    system "false"
  end
end

上記のファイルを下記のように書き換えて自動生成されたコメントを消す.

randstr.rb
class Randstr < Formula
  desc "Random String Generator"
  homepage "https://github.com/Scstechr/Randstr"
  url "https://github.com/Scstechr/Randstr/releases/download/v1.0/randstr-v1.0.tar.gz"
  sha256 "5c3b72ae94a11d85b4cf460595c64336ad9985dc976a9daf8dc33a0cbf5c9cb8"

  def install
     bin.install 'randstr'
  end
end

ここで,bin.install 'randstr'はパスが通ってるディレクトリにシンボリックリンクを貼ってくれる.
細かいFormulaの書き方は他の記事を参照する.

6. 作成したFormulaをGitHub上のhomebrew-<name>push

git commitで変更を反映したあと,git pushでリモートレポジトリにpushする.

完成

ここまでくれば,まずbrew tab scstechr/randstrtap

$ brew tap scstechr/randstr
==> Tapping scstechr/randstr
Cloning into '/usr/local/Homebrew/Library/Taps/scstechr/homebrew-randstr'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
Tapped 1 formula (27 files, 23.5KB).

brew install randstrrandstrをインストールできる.

$ brew install randstr
==> Installing randstr from scstechr/randstr
==> Downloading https://github.com/Scstechr/Randstr/releases/download/v1.0/randstr-v1.0.tar.gz
🍺  /usr/local/Cellar/randstr/1.0: 4 files, 6.2MB, built in 5 seconds

また,どこでもrandstrが実行できるようになった.

$ randstr 5
FNsB3
$ randstr 10
Wtgk8d9ABb

以上より,下記のコマンドでrandstrが誰でも簡単にインストールできるようになった.

$ brew tap scstechr/ranstr
$ brew install randstr