Build and Tooling
リポジトリ: biomejs/biome 分析日: 2026-03-09
概要
Biome は Rust + TypeScript のモノリポで、justfile タスクランナー・xtask コード生成・30 以上の GitHub Actions ワークフロー・changesets によるリリースフローを組み合わせた高度なビルドパイプラインを構築している。特に注目すべきは、コード生成(AST・アナライザ・設定・スキーマ・バインディング)をビルドプロセスの中核に据え、just ready コマンドで CI と同等のチェックをローカルで再現可能にしている点、および 8 プラットフォーム向けクロスコンパイル + WASM ビルド + npm パッケージ自動生成を単一リリースフローで実現している点である。
背景にある原則
Cargo エイリアスによるコマンド抽象化:
.cargo/config.tomlでcargo lint、cargo format、cargo codegen等のエイリアスを定義し、長い clippy/rustfmt/codegen コマンドを短縮名で隠蔽している。これにより justfile・CI ワークフロー・開発者の手動実行のすべてで同じコマンド名を使え、実装詳細の変更が呼び出し側に波及しない(.cargo/config.toml:1-38)ローカルとCIの等価性:
just readyがgen-all→documentation→lint→test→test-docを順に実行し、前後でgit diff --exit-code --quietを挟むことで「生成物が最新か」を検証する。CI で実行されるチェックをローカルで一発で再現でき、PR 前の確認コストを最小化する(justfile:272-281)Feature フラグによるコード生成の段階的分離: xtask_codegen は
schema、configuration、license等の Cargo feature でモジュールを条件コンパイルし、不要な依存を排除している。フル生成(all)と個別生成(analyzer、schema等)を単一バイナリで実現しつつ、ビルド時間を削減する(xtask/codegen/src/main.rs:1-9)変更検出による CI 実行の最適化: PR ワークフローは
pathsフィルタで Rust コード変更時のみ実行し、ベンチマークワークフローはクレート単位の paths で絞り込む。加えてconcurrency+cancel-in-progressで同一 PR の古いジョブを自動キャンセルする(.github/workflows/pull_request.yml:9-21)
実例と分析
justfile のタスク階層設計
justfile は 3 層構造でタスクを組織している:
- プリミティブタスク:
format,lint,testなど単一操作 - 複合タスク:
gen-analyzer(gen-rules→gen-configuration→gen-migrate→gen-bindings→lint-rules→format)のように複数のプリミティブタスクを順序付きで実行 - エイリアス:
f := format,t := test,r := readyで頻用コマンドを短縮
特筆すべきは ready タスクの設計。先頭と末尾に git diff --exit-code --quiet を配置し、コード生成前後でワーキングツリーがクリーンであることを保証する。これにより「生成コードをコミットし忘れた」問題を防止する。
Cargo エイリアスと justfile の役割分担
Cargo エイリアス(.cargo/config.toml)は Rust ツールチェーン固有の操作を抽象化し、justfile はプロジェクト全体のワークフロー(Rust + TypeScript + TOML フォーマット)を統合する。
Cargo エイリアス justfile
├── lint (clippy) ←── lint (cargo lint を呼ぶ)
├── format (cargo fmt) ←── format (cargo format + pnpm format)
├── codegen (xtask) ←── gen-rules (cargo run -p xtask_codegen)
├── biome-cli-dev ←── (package.json scripts から利用)
└── documentation (doc) ←── documentation (cargo documentation)コード生成パイプライン(xtask)
xtask ディレクトリは 4 つのサブプロジェクトで構成される:
- codegen: AST ノード・シンタックスファクトリ・フォーマッタ・アナライザルール・設定スキーマ・TypeScript バインディング等を生成
- coverage: Test262 適合率テスト
- glue: ファイル操作・プロジェクトルート解決等の共通ユーティリティ
- rules_check: ルールのドキュメント・テストケースの検証
codegen は update() 関数(xtask/codegen/src/lib.rs:58-78)で「ファイル内容が変わった場合のみ書き込む」パターンを使用し、不要な再コンパイルを回避する。Mode::Verify モードでは書き込みせず差分検出のみ行い、CI で「生成コードが最新か」を検証できる。
新ルール追加の自動化
just new-js-lintrule <name> は以下を一括実行する:
xtask_codegenでルールのボイラープレートを生成gen-analyzerでアナライザ登録コードを再生成- 設定・マイグレーション・バインディング・ドキュメントを更新
- フォーマット適用
言語ごとに new-{lang}-lintrule / new-{lang}-assistrule のバリエーションがあり、ルール追加の手順がテンプレート化されている。
リリースフローの多段化
Biome は 4 種類のリリースチャネルを運用する:
- preview: スケジュール実行(火・土の UTC 0:00)+ 手動トリガー。
pkg-pr-newで npm に公開し Discord に通知 - beta: 手動
workflow_dispatchでバージョン指定。npm publish --tag beta - stable (release.yml): main への push 時に changesets が PR を作成 → マージで CLI・JS API を別パイプラインで公開 → GitHub Release + Docker イメージ + Web サイト更新を
repository_dispatchで連鎖 - release_cli.yml: 緊急リリース用の手動ワークフロー
クロスプラットフォームビルド戦略
8 ターゲットを並列ビルドし、GNU/Linux 向けは Debian bullseye コンテナで古い glibc との互換性を確保する:
x86_64-pc-windows-msvc,aarch64-pc-windows-msvcx86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu(bullseye コンテナ)x86_64-unknown-linux-musl,aarch64-unknown-linux-muslx86_64-apple-darwin,aarch64-apple-darwin
加えて WASM を bundler / nodejs / web の 3 ターゲットで出力。リリース時に generate-packages.mjs がバイナリをプラットフォーム別 npm パッケージにコピーし、バージョンを同期する。
Clippy カスタムルールによるメモリアロケーション防止
clippy.toml で str::to_ascii_lowercase 等のメソッドを禁止し、メモリアロケーションを避ける独自の biome_string_case クレートの使用を強制している。禁止理由を reason フィールドに記載することで、代替手段が自動的にエラーメッセージに表示される。
コード例
// .cargo/config.toml:36
lint = "clippy --workspace --all-features --all-targets -- --deny warnings"// xtask/codegen/src/lib.rs:58-78
pub fn update(path: &Path, contents: &str, mode: &Mode) -> Result<UpdateResult> {
match fs2::read_to_string(path) {
Ok(old_contents) if old_contents == contents => {
return Ok(UpdateResult::NotUpdated);
}
_ => (),
}
if *mode == Mode::Verify {
anyhow::bail!("`{}` is not up-to-date", path.display());
}
eprintln!("updating {}", path.display());
if let Some(parent) = path.parent()
&& !parent.exists()
{
fs2::create_dir_all(parent)?;
}
fs2::write(path, contents)?;
Ok(UpdateResult::Updated)
}// crates/biome_cli/src/lib.rs:43-46
pub(crate) const VERSION: &str = match option_env!("BIOME_VERSION") {
Some(version) => version,
None => env!("CARGO_PKG_VERSION"),
};# clippy.toml:1-6
allow-dbg-in-tests = true
disallowed-methods = [
{ path = "str::to_ascii_lowercase", reason = "Avoid memory allocation. Use `biome_string_case::StrOnlyExtension::to_ascii_lowercase_cow` instead." },
{ path = "std::ffi::OsStr::to_ascii_lowercase", reason = "Avoid memory allocation. Use `biome_string_case::StrLikeExtension::to_ascii_lowercase_cow` instead." },
{ path = "str::to_lowercase", reason = "Avoid memory allocation. Use `biome_string_case::StrOnlyExtension::to_lowercase_cow` instead." },
]# justfile:272-281
ready:
git diff --exit-code --quiet
just gen-all
just documentation
#just format # format is already run in `just gen-all`
just lint
just test
just test-doc
git diff --exit-code --quietパターンカタログ
Template Method パターン (分類: 振る舞い)
- 解決する問題: 新しいルール追加時に必要なボイラープレートの一貫性確保
- 適用条件: 同種の成果物(lint ルール、formatter ルール等)を繰り返し追加するプロジェクト
- コード例:
xtask/codegen/src/generate_new_analyzer_rule.rs:1-60 - 注意点: テンプレートの変更時は既存の生成済みコードとの整合性を
gen-allで再検証する必要がある
Strategy パターン (分類: 振る舞い)
- 解決する問題: codegen の
Mode::Overwrite/Mode::Verifyで書き込みと検証を同一コードで切り替え - 適用条件: ローカル開発(Overwrite)と CI 検証(Verify)で同じ生成ロジックを共有したい場面
- コード例:
xtask/codegen/src/lib.rs:58-78 - 注意点: Verify モードは差分のみ報告し修正は行わないため、開発者が修正方法を知っている前提
- 解決する問題: codegen の
Good Patterns
- 環境変数によるバージョン注入:
option_env!("BIOME_VERSION")でビルド時にバージョンを注入し、未設定時はCARGO_PKG_VERSIONにフォールバックする。リリースビルドでは CI が環境変数を設定し、開発ビルドでは Cargo.toml のバージョンが使われる。バージョンのシングルソースを npm のpackage.jsonに統一しつつ、Rust バイナリにも反映できる
// crates/biome_cli/src/lib.rs:43-46
pub(crate) const VERSION: &str = match option_env!("BIOME_VERSION") {
Some(version) => version,
None => env!("CARGO_PKG_VERSION"),
};- changesets の fixed グループ: CLI 本体と全プラットフォームパッケージ・WASM パッケージを fixed グループにまとめ、バージョンを常に同期させる。ユーザーが
@biomejs/biomeをインストールすると optionalDependencies でプラットフォーム別パッケージが解決されるため、バージョン不一致は致命的になる
// .changeset/config.json:5-19
"fixed": [
[
"@biomejs/biome",
"@biomejs/cli-win32-x64",
"@biomejs/cli-darwin-arm64",
"@biomejs/wasm-bundler",
...
]
]autofix CI ワークフロー: PR に対してコード生成 + フォーマットを実行し、差分があれば自動コミットする
autofix-ci/actionを使用。コントリビューターがコード生成を忘れても CI が修正を提案するため、レビュー負荷を軽減する(.github/workflows/autofix.yml)開発プロファイルの最適化:
[profile.dev]でdebug = "line-tables-only"を設定し、依存クレートはdebug = falseにすることでデバッグビルドの速度と出力サイズを改善。WASM クレートのみopt-level = "s"で最適化し、開発中も実用的な速度を確保する
# Cargo.toml:261-269
[profile.dev]
debug = "line-tables-only"
[profile.dev.package."*"]
debug = false
[profile.dev.package.biome_wasm]
opt-level = "s"
debug = trueAnti-Patterns / 注意点
- コード生成後のフォーマット忘れ: コード生成ツールが出力するコードはフォーマット済みとは限らない。
gen-allは末尾でjust formatを実行するが、個別のgen-rulesを単独で実行するとフォーマットされない生成コードがコミットされる可能性がある
# Bad: 個別生成のみ実行
just gen-rules
git add -A && git commit
# Better: gen-analyzer が後処理を含む、または手動でフォーマット
just gen-analyzer # gen-rules + gen-configuration + ... + format- CI ワークフローの paths 漏れ: ベンチマークワークフローは特定クレートの
.rsファイルのみを paths に列挙しているが、共通クレート(biome_rowan等)の変更が見落とされる可能性がある。paths フィルタは保守的(広め)に設定すべき
# Bad: 依存クレートの変更を見落とす可能性
paths:
- 'crates/biome_js_formatter/**/*.rs'
# Better: 共通クレートも含める
paths:
- 'crates/biome_js_formatter/**/*.rs'
- 'crates/biome_formatter/**/*.rs'
- 'crates/biome_rowan/**/*.rs'導出ルール
[MUST]コード生成とフォーマットは単一コマンドで連続実行し、生成後のフォーマット漏れを防ぐ- 根拠: Biome の
gen-allは最後にjust formatを実行し、readyは前後でgit diff --exit-codeを挟んで生成物の整合性を保証している(justfile:26-30, 272-281)
- 根拠: Biome の
[MUST]リリース対象パッケージ群のバージョンは fixed グループ等で同期し、プラットフォーム別パッケージとのバージョン不一致を防ぐ- 根拠: Biome は changesets の
fixedで CLI + 8 プラットフォームパッケージ + 3 WASM パッケージのバージョンを統一し、optionalDependencies 解決時の互換性を保証している(.changeset/config.json:5-19)
- 根拠: Biome は changesets の
[SHOULD]タスクランナーのコマンドを「プリミティブ→複合→エイリアス」の 3 層で構成し、個別実行と一括実行の両方を可能にする- 根拠: Biome の justfile は
format/lint/testのプリミティブ、gen-analyzer/readyの複合、f/t/rのエイリアスで構成され、開発フェーズに応じた粒度の操作を提供している(justfile:1-8, 47-53, 272-281)
- 根拠: Biome の justfile は
[SHOULD]Rust プロジェクトでは.cargo/config.tomlのエイリアスでツールチェーン操作を抽象化し、justfile/Makefile/CI から統一的に呼び出す- 根拠: Biome は
cargo lint、cargo format、cargo codegen等のエイリアスを定義し、clippy のフラグ詳細を呼び出し側から隠蔽している(.cargo/config.toml:1-38)
- 根拠: Biome は
[SHOULD]コード生成ツールに「書き込み」と「検証のみ」の 2 モードを持たせ、CI では検証モードで生成コードの鮮度をチェックする- 根拠: Biome の
update()関数はMode::Overwrite(ローカル)とMode::Verify(CI)を切り替え、同一ロジックで生成と検証を行う(xtask/codegen/src/lib.rs:58-78)
- 根拠: Biome の
[SHOULD]CI ワークフローにはconcurrency+cancel-in-progressを設定し、同一 PR の古いジョブを自動キャンセルする- 根拠: Biome の PR ワークフローは
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}で PR 単位のグループを作り、更新時に前回実行をキャンセルしてリソースを節約している(.github/workflows/pull_request.yml:19-21)
- 根拠: Biome の PR ワークフローは
[SHOULD]clippy のdisallowed-methodsでパフォーマンス上問題のある標準ライブラリメソッドを禁止し、代替手段をreasonに記載する- 根拠: Biome は
str::to_ascii_lowercase等のアロケーションを伴うメソッドを禁止し、Cow を返す独自実装への移行を強制している(clippy.toml:2-6)
- 根拠: Biome は
[AVOID]リリースビルドでcodegen-units = 1を使用する場合、開発ビルドにまで適用しない。開発ビルドではコンパイル速度を優先する- 根拠: Biome はリリース CI でのみ
RUSTFLAGS: "-C strip=symbols -C codegen-units=1"を設定し、開発プロファイルではdebug = "line-tables-only"で速度を優先している(.github/workflows/release.yml:163-165,Cargo.toml:261-262)
- 根拠: Biome はリリース CI でのみ
適用チェックリスト
- [ ] タスクランナー(just/make)にプリミティブタスクと複合タスクの階層があるか
- [ ] 「ローカルで CI と同等のチェックを実行するコマンド」(
ready相当)が定義されているか - [ ] コード生成後にフォーマットが自動適用される仕組みがあるか
- [ ] コード生成ツールに検証モード(diff のみ検出)があり、CI で使われているか
- [ ] CI ワークフローに
pathsフィルタとconcurrency+cancel-in-progressが設定されているか - [ ] リリース対象のパッケージ群のバージョンが同期される仕組み(changesets fixed 等)があるか
- [ ] Rust プロジェクトの場合、
.cargo/config.tomlのエイリアスでコマンドが抽象化されているか - [ ] clippy の
disallowed-methodsでプロジェクト固有の禁止パターンが定義されているか - [ ] 開発プロファイル(
profile.dev)のデバッグ情報レベルが適切に調整されているか - [ ] クロスプラットフォームビルドで古い OS との互換性(glibc バージョン等)が考慮されているか