[Rust] 自作CLIツールをGitHubとcrate.ioで配布するまでの記録
はじめに
opという自作の個人的なCLIツール(元々はGo製でした)をRustで配布する為に行った記録を書きます。
RustでCLIツールを作った時、GitHubのリリースページとcrate.ioで公開すれば、色々な人が手軽にインストールしてみることができるようになるので良いと思います。(このツールは本当に個人的なので、ちょっと私以外に使う方がおられるのか謎ですが…笑)
このツール自体は、中々にザツなノリの開発でやってますので、記事に書いている手法はそこそこアナログです。以下のような方法でGitHubとcrate.ioでリリース配布してますので、1つの方法として参考にしてみてください。
最近はgo-releaserがRust対応したりとしていますので、色々ともっと良い方法が取れると思いますが、日記だと思って見てもらえればと思います。
環境構築
ビルドとはあまり関係ありませんが、環境構築について記載します。
基本はdevContainer環境でローカルでちょっとテストしたいこともあったので、DockerfileではDebian系のイメージを入れ、musl-tools
とmingw-w64
を入れました。
これにより、LinuxかつDebian系の環境で、そのまんまx86_64-unknown-linux-musl
, x86_64-pc-windows-gnu
が作成できるようになります(ホストマシンがamd64の場合)。
これらのターゲットは、Alpine(と言うかスタティックビルドされているのでLinuxならほぼなんでも)とWindows 10~の環境でほぼっほぼ間違いなく動くことが期待できます。
Dockerfile
FROM rust:1.84.1-slim-bullseye
# Create the user
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME
# Install Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
mingw-w64 \
musl-dev \
musl-tools \
git \
zip \
unzip
USER $USERNAME
WORKDIR /workspaces
ENV SHELL=/bin/bash
CMD ["/bin/bash"]
最近はZigという便利なコンパイラも出てきてるのでcargo-zigのようなツールを使うのも有益だと思います。
また、rust-toolchain.toml
を書いておくと、使うバージョン・ターゲットをビルドする時に勝手に落としてくれるようになりますので、それも書いてます。
rust-toolchain.toml
[toolchain]
channel = "1.84.1"
components = ["rustfmt", "rustc", "cargo"]
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl",
"x86_64-pc-windows-gnu",
"aarch64-apple-darwin",
]
profile = "minimal"
GitHub Release Pageでのリリース
GitHub Actions経由で、「バージョンタグのタグプッシュがされたらリリース」ということにします。
複数ターゲットの対応はcrossというツールとマトリクスでのMac OSイメージを使いました。
release.yml
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
release:
name: Release
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- name: Set Environment Variables -- APP_VERSION
run: |
echo "APP_VERSION=${{ github.ref_name }}" >> "$GITHUB_ENV"
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Setup cross
run: cargo install cross
if: startsWith(matrix.os, 'ubuntu')
- name: Test
run: |
cargo test --release
rm -rf target
# see https://github.com/cross-rs/cross
- name: Build By Cross
if: startsWith(matrix.os, 'ubuntu')
run: |
targets=(
"x86_64-unknown-linux-gnu"
"x86_64-unknown-linux-musl"
"aarch64-unknown-linux-musl"
"x86_64-pc-windows-gnu"
)
for target in "${targets[@]}"; do
cross build --release --target $target
done
# Build with MacOS images since Mac does not support cross by default
- name: Build on macOS
if: startsWith(matrix.os, 'macos')
run: cargo build --release --target aarch64-apple-darwin
- name: Archive Artifact To dist Folder
run: bash scripts/publish.sh
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
dist/*.zip
dist/*.tar.gz
generate_release_notes: true
draft: true
crossはDockerイメージを使って良い感じにクロスビルドができるツールです。普通にCargoからインストールでき、GitHub Actionsでそのまんま動くのでとても良いです。
ところが、Mac OSはデフォルトで対応してません(もっとも、Mac OSは公証セキュリティが厳しいのでゴリ押ししないと使えないかもしれないんですが…笑)。この対処方法は色々あると思うのですが、単純にMac OSイメージで別でビルドするようにしました。
jobs:
release:
name: Release
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Setup cross
run: cargo install cross
if: startsWith(matrix.os, 'ubuntu')
# 省略
# see https://github.com/cross-rs/cros
- name: Build By Cross
if: startsWith(matrix.os, 'ubuntu')
run: |
targets=(
"x86_64-unknown-linux-gnu"
"x86_64-unknown-linux-musl"
"aarch64-unknown-linux-musl"
"x86_64-pc-windows-gnu"
)
for target in "${targets[@]}"; do
cross build --release --target $target
done
# Build with MacOS images since Mac does not support cross by default
- name: Build on macOS
if: startsWith(matrix.os, 'macos')
run: cargo build --release --target aarch64-apple-darwin
targetフォルダからアーカイブする
ビルドしたバイナリはtarget
フォルダにあり、それぞれのターゲット名を示したフォルダの中に入っています。
これとREADME.md
などのファイルをアーカイブした形で配布したかったので、スクリプトによってゴリ押しで対応しています。
以下のBash用スクリプトは、dist
フォルダおよびその中にアーカイブしたいフォルダを作成し、target
フォルダからバイナリをコピー、および一部ドキュメントをコピーしてzip
ないし.tar.gz
にアーカイブします。
publish.sh
#!/bin/bash
set -e
APP_NAME=op
test -n "$APP_VERSION" || APP_VERSION="v0.0.0"
rm -rf dist
mkdir -p dist
TARGET_DIR="target"
ROOT_DIR="$(pwd)"
DIST_DIR="$(pwd)/dist"
# Collect binaries for each target folder and move them to the dist folder.
# Also copy documents to that folder.
cd $TARGET_DIR
for dir in */; do
dir=${dir%/}
[[ "$dir" == "CHACHEDIR.TAG" ]] && continue
binary_path="$dir/release/${APP_NAME}"
[[ -f "$binary_path.exe" ]] && binary_path+=".exe"
[[ -f "$binary_path" ]] || { echo "Binary not found in $dir"; continue; }
target_dir="${DIST_DIR}/$dir"
mkdir -p "$target_dir"
cp "$binary_path" "$target_dir/"
cp "${ROOT_DIR}/README.md" "${ROOT_DIR}/CREDITS" "$target_dir/"
done
# Archive each target folder
cd $DIST_DIR
for dir in */; do
dir=${dir%/}
archive_name="${APP_NAME}_${dir}_${APP_VERSION}"
if [[ "$dir" == *windows* ]]; then
zip -r "${archive_name}.zip" -j "$dir"
else
tar -czf "${archive_name}.tar.gz" -C "$dir" .
fi
done
最後にactions-gh-releaseを使ってリリースをすれば、完了です。
crate.ioにアップロードする
crate.ioへのアップロードについて記載します。
Cargo.tomlの精査
Cargo.toml
をチェックします。特に重要になるのはpackage.name
です。既にop
というパッケージは先に作られている方がいらっしゃったので、別の名前で対応しています。
その場合、バイナリ名は違う名前にしたいよと言う時は、[[bin]]
キーを設定します。
[package]
name = "kawana77b-op"
version = "1.1.0"
edition = "2021"
description = "Open the file path or web address in the prescribed file explorer or browser"
authors = ["shimarisu_121"]
license = "MIT"
repository = "https://github.com/kawana77b/op"
readme = "README.md"
keywords = ["open", "explorer"]
categories = ["command-line-utilities"]
autotests = false
rust-version = "1.84.1"
include = ["/Cargo.toml", "/LICENSE", "/README.md", "/src/**"]
[[bin]]
name = "op"
path = "src/main.rs"
create.ioのAPIキー作成
crate.ioにGitHubアカウントで登録し、メール認証をします。
APIトークンを作ります。この時、publish-new
, publish-update
を設定します。また、トークンの期限をお好みで設定します。
多くの方はご承知だと思いますが、トークンは2度と見れないのでパスワード管理ソフトなどにメモします。
GitHub Actionsからのアップロード
アップロードはGitHub Actionsから行うこととしました。
GitHubリポジトリの設定ページからSecretsを設定します。APIキーを値として設定します。
今のところリリースとは別個に手動で行うようにしています。そのyamlを以下に示します。
name: crate-io
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- run: cargo publish --token ${CRATES_TOKEN}
env:
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
これでディスパッチすれば、crate.io
にアップロードされて、Rust環境がある人なら手軽にインストールできるようになります。
終わりに
Rust自体は正直あまり触れてませんので、もうちょっと色々知識が深まったら方法の改善など色々するかもしれません。以上、2025年2月現在の現状の記録でした。
皆様のご参考になれば幸いです。
Discussion