Contents
1. 開発環境の整備
| ツール | 用途 | インストール方法(2024‑05) |
|---|---|---|
| rustup | Rust コンパイラ・標準ライブラリのマルチツールチェーン管理 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh |
| VS Code + rust-analyzer & CodeLLDB | 高速なコード補完・インライン診断・デバッグ | VS Code の拡張マーケットプレイスで Rust Analyzer と CodeLLDB を検索してインストール |
| cargo‑generate | テンプレートからプロジェクト雛形を作成 | cargo install cargo-generate(初回のみ) |
| cross (任意) | Docker 上でのクロスコンパイル支援 | cargo install cross |
ポイント
-rustup default stableで安定版をデフォルトにし、必要時だけrustup toolchain install nightlyとして Nightly を追加します。
- VS Code の設定例は次節で紹介します。
VS Code 推奨設定
|
1 2 3 4 5 6 |
{ "rust-analyzer.cargo.allFeatures": true, "rust-analyzer.checkOnSave.command": "clippy", "lldb.executable": "rust-lldb" } |
これにより Clippy によるコードチェックが保存時に自動実行され、デバッグは CodeLLDB が利用できるようになります。
2. プロジェクトの雛形を作成する
公式テンプレートは cargo‑generate が提供している「rust-cli-template」をベースにしています。以下のコマンドでプロジェクトを生成します。
|
1 2 3 4 5 |
cargo generate \ --git https://github.com/cargo-generate/rust-cli-template \ --name my_tool cd my_tool |
生成されたディレクトリ構成(抜粋)
|
1 2 3 4 5 6 7 8 9 10 |
my_tool/ ├─ Cargo.toml # パッケージ情報・依存関係 ├─ src/ │ ├─ main.rs # バイナリのエントリポイント (src/bin/ に分割可) │ └─ lib.rs # ビジネスロジック(テストしやすいように切り出す) ├─ tests/ # 統合テスト └─ .github/ └─ workflows/ └─ ci.yml # 後述の CI 設定ファイル |
なぜ src/lib.rs にロジックを置くか
- ユニットテストは cargo test --lib だけで高速に走らせられる。
- バイナリ固有のコード(引数解析等)は main.rs に限定でき、ライブラリとして再利用しやすくなる。
3. CLI 本体の実装 ― clap v4 + エラーハンドリング
3‑1. 引数解析は clap::Parser マクロで完結
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(name = "addpath")] #[command(author = "Your Name <you@example.com>")] #[command(version = env!("CARGO_PKG_VERSION"))] #[command(about = "PATH 環境変数へディレクトリを追加するツール")] struct Cli { /// 追加したいディレクトリ(絶対パスまたは相対パス) #[arg(short, long)] path: String, #[command(subcommand)] command: Option<Commands>, } #[derive(Subcommand)] enum Commands { /// 現在の PATH 内容を表示 Show, /// すべての設定をリセット Reset, } |
cargo run -- --help を実行すると、上記属性から自動生成されたヘルプが表示されます。
3‑2. エラーハンドリングは thiserror と anyhow の併用
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
use thiserror::Error; #[derive(Error, Debug)] pub enum AddPathError { #[error("指定したパスが存在しません: {0}")] NotFound(String), #[error("権限が不足しています")] PermissionDenied, #[error(transparent)] Io(#[from] std::io::Error), } |
アプリケーション層では anyhow::Result<()> を返すことで、スタックトレースや追加コンテキストを簡単に付与できます。
|
1 2 3 4 5 6 7 8 9 |
use anyhow::{Context, Result}; pub fn add_path(p: &str) -> Result<()> { let abs = std::fs::canonicalize(p) .with_context(|| format!("パスの正規化に失敗: {p}"))?; // OS 別ロジックは別モジュールへ委譲(省略) Ok(()) } |
3‑3. src/main.rs のエントリポイント例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
use my_tool::{cli::Cli, operations::add_path}; use clap::Parser; fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { Some(cli::Commands::Show) => show_path(), Some(cli::Commands::Reset) => reset_path(), None => add_path(&cli.path)?, } Ok(()) } |
4. テスト戦略と CI/CD 設定
4‑1. ユニットテスト・統合テストの書き方
ユニットテスト(src/lib.rs 内)
|
1 2 3 4 5 6 7 8 9 10 11 |
#[cfg(test)] mod tests { use super::*; #[test] fn normalize_path_removes_redundant_components() { let input = "src/../Cargo.toml"; assert_eq!(normalize(input).unwrap(), "Cargo.toml"); } } |
統合テスト(tests/cli.rs)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
use assert_cmd::Command; use predicates::str::contains; #[test] fn cli_shows_help() { Command::cargo_bin("addpath") .expect("binary exists") .arg("--help") .assert() .success() .stdout(contains("PATH 環境変数へディレクトリを追加")); } |
4‑2. GitHub Actions ワークフロー
以下は lint → test → cross compile を自動化した ci.yml の例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: lint-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Rust ツールチェーン(stable + clippy)をインストール - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Run Clippy run: cargo clippy --all-targets --all-features -- -D warnings - name: Run Tests run: cargo test --all-features --locked --verbose cross-build: needs: lint-test runs-on: ubuntu-latest strategy: matrix: target: [x86_64-pc-windows-gnu, x86_64-apple-darwin, aarch64-unknown-linux-gnu] steps: - uses: actions/checkout@v4 - name: Install cross run: | sudo apt-get update && sudo apt-get install -y qemu-user-static cargo install cross --locked - name: Cross compile for ${{ matrix.target }} run: | cross build --release --target ${{ matrix.target }} release: needs: cross-build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v3 with: path: ./artifacts - name: Create GitHub Release id: create_release uses: softprops/action-gh-release@v2 with: tag_name: v${{ github.run_number }} name: "Release v${{ github.run_number }}" files: artifacts/**/release/* - name: Update Homebrew tap (optional) run: | git clone https://github.com/yourname/homebrew-tap.git cd homebrew-tap cat > Formula/addpath.rb <<EOS class Addpath < Formula desc "PATH 環境変数へディレクトリを追加するツール" homepage "https://github.com/yourname/addpath-cli" url "https://github.com/yourname/addpath-cli/releases/download/v${{ github.run_number }}/addpath-${{ github.run_number }}-x86_64-apple-darwin.tar.gz" sha256 "<SHA256>" version "${{ github.run_number }}" def install bin.install "addpath" end end EOS git config user.name "github-actions" git add Formula/addpath.rb git commit -m "Release ${{ github.run_number }}" git push origin main |
主なポイント
| ステップ | 目的 |
|---|---|
cargo clippy |
コーディング規約・潜在的バグの検出 |
cargo test --locked |
Cargo.lock に固定された依存関係で再現性確保 |
cross build |
Docker 上でマルチプラットフォーム向けにビルド |
softprops/action-gh-release |
ビルド成果物を自動的にリリースへ添付 |
5. クロスコンパイルと配布
5‑1. cross の利用手順(Docker が必要)
|
1 2 3 4 5 6 7 8 9 10 |
# Windows (GNU) 用バイナリ cargo install cross --locked cross build --release --target x86_64-pc-windows-gnu # macOS (Intel) 用 cross build --release --target x86_64-apple-darwin # Linux (ARM64) 用 cross build --release --target aarch64-unknown-linux-gnu |
生成物は target/<triple>/release/ に格納されます。サイズ削減が必要な場合は strip コマンドでシンボル情報を除去できます。
5‑2. GitHub Releases と Homebrew tap の連携
上記 CI ワークフローの release ジョブで、ビルド済みバイナリを自動的に添付し、同時に Homebrew 用 Formula を更新します。
Homebrew に登録すれば macOS ユーザーは次のコマンドだけでインストールできます。
|
1 2 3 |
brew tap yourname/homebrew-tap brew install addpath |
6. ドキュメント生成と公開(任意)
- cargo doc →
cargo doc --no-deps --openでローカルに API ドキュメントを表示。 - GitHub Pages に自動デプロイしたい場合は
gh-pagesブランチへビルド成果物をプッシュする Action を追加すると便利です。
7. 参考情報
| 項目 | URL |
|---|---|
| Rust公式インストール手順 | https://www.rust-lang.org/tools/install |
| cargo‑generate リポジトリ | https://github.com/cargo-generate/cargo-generate |
| cross(クロスコンパイル) | https://github.com/cross-rs/cross |
| Qiita 記事「Rustで初めてのCLIツール開発 - addpath」 | https://qiita.com/Tao119/items/c31f0c6981fcf571e3d0 |
| clap v4 ドキュメント | https://docs.rs/clap/latest/clap/ |
| thiserror クレート | https://crates.io/crates/thiserror |
| anyhow クレート | https://crates.io/crates/anyhow |
8. まとめ
- rustup + VS Code で開発基盤を統一し、
cargo-generateの公式テンプレートからすぐにプロジェクト雛形を取得。 src/binとsrc/libに分割したモジュラー構成でテスト容易性と再利用性を確保。- clap v4 derive でシンプルに CLI を定義し、
thiserror + anyhowによる一貫したエラーハンドリングを実装。 - ユニットテスト・統合テストを書き、GitHub Actions で Clippy → Test → Cross‑Compile の自動化パイプラインを構築。
crossと Docker イメージで主要 OS 向けにビルドし、CI が作成したバイナリを GitHub Releases に添付、さらに Homebrew tap で macOS 配布まで完結。
このフローを踏めば、ローカル環境のセットアップから実際のリリースまで一貫した手順 が確立できます。まずは本稿の手順に沿って addpath のようなシンプル CLI を作り、GitHub にプッシュして CI が走ることを確認しましょう。その後、独自機能やライブラリ化を進めれば、実務レベルのツールへと自然にスケールアップできます。 Happy Rusting!