🪺

nest | artifact bundleを用いてSwift製のCLIツールを爆速でインストールする

2024/10/20に公開

ざっくり要約

Swift製のCLIツールをインストールする方法として nestを開発しました。
このツールはGitHub Releaseにartifact bundleがあればそれをダウンロードしてインストールします。
もしGitHub Releaseにartifact bundleがなければリポジトリをクローンしてビルドしてインストールします。
artifact bundleがある場合、mintなどの他のツールと比べて、ビルドが不要なのでインストールが早くなります。
swiftlintの場合、4分ほどかかっていたイントールの時間が、わずか3秒になります。

$ nest install realm/SwiftLint 
📦 Found an artifact bundle, SwiftLintBinary-macos.artifactbundle.zip, for SwiftLint.
🌐 Downloading the artifact bundle of SwiftLint...
✅ Success to download the artifact bundle of SwiftLint.
🪺 Success to install swiftlint.

はじめに

Swift製のCLIツールをインストールする選択肢として、Mint、Swift Packageのexperimental-installなどが挙げられます。
しかし、これらの方法は全てローカルでビルドすることが前提の設計になっています。

例えばswiftlintをMintでインストールする場合、swiftlintをcloneしてビルドする必要があるのですが、swiftlintのビルドにはとても長い時間がかかります。(自分の手元のmacだと4分弱かかります)
Xcode Cloudのようなキャッシュのコントロールが難しい環境だと、毎回ビルドする必要がありCPU時間を消費してしまうという課題があります。

Artifact Bundle

Swiftにはartifact bundleという仕様が存在します。 (SE-0305)
artifactbundleは複数のアーキテクチャに対するバイナリを仕様通りにまとめたディレクトリ構造です。
そのディレクトリ構造を一つのzipファイルにまとめることで、SwiftPMからbinary targetとして読み込むことができます。

SwiftLintSwiftGenSwiftFormatなどはGitHubのリリースでこのartifact bundleを配布しています。


SwiftLintのGitHubリリース

nest

この仕様を利用したnestというツールを開発しました。

このnestはGitHub Releaseにartifact bundleがあれば、それをダウンロードしてインストールします。
もしGitHub Releaseにartifact bundleがなければリポジトリをクローンしてビルドしてインストールします。

artifact bundleがある場合、ビルドするのと比べて、zipファイルをダウンロードして解凍するだけなので、高速にインストールすることができます。
swiftlintの場合、4分かかっていたインストールの時間がたった3秒になります。

仮にartifact bundleがない場合でもクローンしてビルドする他のツールと同様のパフォーマンスを発揮することができます。

またバイナリを直接ダウンロードするのではなく、artifact bundleという形式でダウンロードするのもポイントです。
Xcode CloudなどではまだIntelのmacが動いていますが、artifact bundleの形式でダウンロードすることで、IntelのmacでもApple siliconのmacでも同じインターフェースでパッケージのインストールができ、CPUアーキテクチャの違いを開発者が意識しなくてすみます。

nestの使い方

ここからはnestの使い方を見ていきます。
nestのインターフェースはmintとほぼ同様のものになっています。

nestのインストール

以下のスクリプトを実行することで、インストールすることができます。

curl -s https://raw.githubusercontent.com/mtj0928/nest/main/Scripts/install.sh | bash

このスクリプトは以下のことをします。

  1. GitHubのリポジトリにあるリリースのartifact bundleをダウンロードする
  2. そのartifact bundleの中にあるnestを使って、nestをインストールする
  3. 最初にダウンロードしたnestを削除する

nestを使ってnestをインストールしています。nestがネストしてますね。
こうすることでnestの管理下でnest自身を管理できるようになり、nest自体のアップデートもnestでできるようになります。

インストールされたパッケージは~/.nest/binに格納されますので、そちらにPathを通してください。

パッケージのインストール

installコマンドにリポジトリの情報とバージョンを渡すことでインストールできます。バージョンの情報がないときは最新のリリースが使われます。

$ nest install realm/SwiftLint # 最新のリリースがインストールされる
$ nest install realm/SwiftLint 0.57.0 # 0.57.0がインストールされる
$ nest install https://github.com/realm/SwiftLint 0.57.0 # URL形式での指定も可能

また、GitHubのリポジトリを指定する以外にも、artifact bundleのzipのURLを直接指定することも可能です。

$ nest install https://github.com/realm/SwiftLint/releases/download/0.57.0/SwiftLintBinary-macos.artifactbundle.zip

パッケージのアンインストール

uninstallコマンドにアンインストールしたいコマンド名を渡すことでアンインストールできます。バージョン情報が渡されていればそのバージョンだけアンインストールされます。何も渡されなければ全てのバージョンがアンインストールされます。

$ nest uninstall swiftlint # 全てのバージョンのswiftlintがアンインストールされます
$ nest uninstall swiftlint 0.57.0 # 0.57.0のswiftlintだけがアンインストールされます

インストールされているパッケージの一覧を出力する

$ nest list 

インストールしたパッケージのバージョンの切り替え

複数のバージョンのパッケージがインストールされている時、switchコマンドでバージョンを切り替えることができます。

$ nest switch swiftlint 0.56.0 # 0.56.0のswiftlintが選択される

nestfile

nestはyaml形式の設定ファイルをサポートして、複数のパッケージの情報をまとめておくことができます。

generate-nestfileコマンドを使うことで雛形を出力できます。

$ nest generate-nestfile

nestfile.yamlというyamlファイルが生成されるので、そこにパッケージの情報を追加します。

nestPath: ./.nest
targets:
- reference: realm/SwiftLint # リポジトリを指定するだけで最低限の動作をします
- reference: krzysztofzablocki/Sourcery 
  version: 2.2.5 # バージョン情報
  assetName: sourcery-2.2.5.artifactbundle.zip # GitHubのリリースの中のartifact bundleの名前
  checksum: 875ef49ba5e5aeb6dc6fb3094485ee54062deb4e487827f5756a9ea75b66ffd8
# Artifact bundleのZIPのURLを直接指定することも可能
- zipURL: https://github.com/SwiftGen/SwiftGen/releases/download/6.6.3/swiftgen-6.6.3.artifactbundle.zip

そしてbootstrapコマンドにこのファイルを渡すことで、複数のパッケージを一括してインストールすることができます。

$ nest bootstrap nestfile.yaml

またyamlの中でnestPathが指定されている場合、~/.nest/binではなく、nestPathに指定されたディレクトリの中にbinディレクトリが作成され、そこにバイナリがインストールされます
グローバルの環境を汚すことなく、ローカルのプロジェクトに必要なパッケージをインストールできます。

nestfileのメンテナンス

nestfileには上で見たようにバージョン情報やアセットの名前、checksumを記述することができます。
これらはオプションですが、記述することでさまざまなメリットがあります。
例えば、バージョンを記述することで複数人で開発での環境の差を減らしたり、checksumを記述することでzipファイルの改竄を防げたり、などです。
一方、これらをメンテナンスすることは難しくはないですが面倒です。

そこでnestではupdate-nestfileresolve-nestfileの二つのコマンドを提供しています。
update-nestfileは渡されたnestfileの中のパッケージを全て最新にし、アセット名やchecksumを自動で埋めます。

$ nest update-nestfile nestfile.yaml

resolve-nestfileupdate-nestfileとほぼ同じですが、nestfileの中でバージョンが指定されている時、そのバージョンは更新しません。

$ nest resolve-nestfile nestfile.yaml

先ほど上で定義したnestfileに対して、resolve-nestfileした結果は以下のようになります。checksumの面倒な計算をしてくれるのが嬉しいです。

nestPath: ./.nest
targets:
- reference: realm/SwiftLint
  version: 0.57.0
  assetName: SwiftLintBinary-macos.artifactbundle.zip
  checksum: a1bbafe57538077f3abe4cfb004b0464dcd87e8c23611a2153c675574b858b3a
- reference: krzysztofzablocki/Sourcery
  version: 2.2.5
  assetName: sourcery-2.2.5.artifactbundle.zip
  checksum: 875ef49ba5e5aeb6dc6fb3094485ee54062deb4e487827f5756a9ea75b66ffd8
- zipURL: https://github.com/SwiftGen/SwiftGen/releases/download/6.6.3/swiftgen-6.6.3.artifactbundle.zip
  checksum: caf1feaf93dd32bc5037f0b6ded8d0f4fe28ab5d2f6e5c3edf2572006ba0b7eb

おわりに

新しいnestというCLIツールを開発したので、その紹介でした。
クローンしてビルドするのと比べて爆速でインストールできるので、非常に気に入って使っています。
もしよかったら、使ってみてください。

なぜnestという名前なのか?

Swiftのロゴが鳥なので、その鳥の制作物(artifacts)を保管する場所として巣を意味するnestという名前にしました。
幸運なことに巣に関する絵文字は🪹と🪺の2つがあり、artifactsの有無を絵文字で表現できました。

Discussion