moonを使って多言語monorepoを扱ってみた

RubyとJSが混ざるmonorepoに何を使ったらよいのか問題

  • monorepo 向けのツールって JavaScript runtime が基本になりがち 、それ以外は Bazel とかすごく大掛かりなツールになりがち
  • JavaScript ベースの monorepo ツールは package と workspace の話になりがち

で、なんかちょっと違うんだよなぁとずっと悶々としていて、先日ようやく Ruby 向けの monorepo ツールを見つけた。

kjellberg/monoz: Command line tool for managing ruby monorepos.

これはこれで Ruby に閉じてるならアリかもと思った。特に複数のプロジェクトの状況を俯瞰しやすいのはありがたい。

ただ、monoz の実行自体に Ruby の runtime が必要になってしまうのでゼロからのセットアップはやや重い。あと CI どうすんのかな問題がある。特定のプロジェクトだけ実行する –filter 機能はあるんだけど、CI 上で該当プロジェクトをどうやって特定するのか?という問題。適切に特定できれば CI を効率的に利用できるが、そうでない場合は 全プロジェクトに渡ってビルド、テストするのか? 何も変更がないのに? ということになる。monorepo にするということは基本的にはリポジトリ内のプロジェクトは増えていくので、この支援がないと(CI で利用できるリソース制限的に)どんどん厳しくなってくることを意味する。

調べるとどうもこの辺は CI 側に機能があったりするらしいんだけど、

Railsのマイクロサービスアーキテクチャで構成されたアプリをモノレポ構成に移行した話 - Sansan Tech Blog

マイクロサービスというやや大きめのプロダクトではなく、もうちょっとカジュアルに増減するような monorepo の場合、CI 側の設定でまかなうのもちょっと重いなぁと感じる。できれば monorepo を管理するツールそのものにそういう支援があると嬉しい。

欲しい要素

ということで整理すると

  • JS package の workspace とは異なる概念で monorepo を管理できる
  • そもそもセットアップに手間の掛かるスクリプト言語ベースのツールはちょっと違うかも
  • 特定のプロジェクトにだけ CI が動作するような支援がある

てな感じで AI と会話しながら要件を絞っていった。

moon

で、見つけたのがこれ。

moon - A task runner and monorepo management tool for the web ecosystem, written in Rust | moonrepo

Rust でできているので、実行に JS Runtime や Ruby Runtime は必要ない。いろいろできるんだけど、今回自分の欲しいと思ったことと、実際に触ったり読んだりしてみて「へー」と思ったことを挙げると以下のような機能がある。

  • project の定義
  • project ごとの task の定義
  • project の role の定義
  • project の依存関係の定義
  • tier にしたがった言語サポート
  • キャッシュやコミットログから特定のプロジェクトに対して動作する

サポートの厚い(tierの高い)言語

言語サポートに Tier という概念があり、Tier の高い言語だと

  • 全体的にセットアップの自動化ができる

ようになる。

サポートの乏しい言語 ( 例えば Ruby )

  1. rbenv の ruby をそのまま使おうと思えば使える
  2. 一応 proto 経由でインストールやバージョン指定の支援は受けられるが、普通に使うと rbenv と proto は競合する(shell の拡張で自動的にパスを通して Ruby を特定する機能がほとんど同じなので、後から有効になった方で上書きされる)

とりあえず試し始める場合は言語のセットアップ部分は moon や proto の支援は無視してこれまで使っていたツールをそのまま使うのでよいと思う。

いったんやってみる

インストール

最近いろいろ言われてますが、macOS だといったん

$ brew install moon

で入る。

基本的なセットアップ

$ moon init

すると以下のようなファイルができあがる。

.
├── .gitignore
└── .moon/
    └── workspace.yml
  1. リポジトリルートに .moon/ ディレクトリができる
  2. この中に workspace.yml が作られる
  3. workspace.yml の内容は以下のようになっている。
# https://moonrepo.dev/docs/config/workspace
$schema: './cache/schemas/workspace.json'

# extends: './shared/workspace.yml'

projects:
  - 'apps/*'
  - 'packages/*'

あーなるほど。monoz もこんな感じだった。これでアプリケーションは apps/ 以下に、ライブラリは packages/ 以下に作っていけという意思を感じる。

本当はここに toolchain.yml というファイルも置くようだが、デフォルトでは生成されないし、Ruby を中心に世界を見ているので、今回は toolchain 周りは無視することとする。

基本的なコマンド

(あとで足すかも)

  • moon query projects 一覧
    • moon project <project id> 詳細
  • moon query tasks 一覧
    • moon task <task id> 詳細
  • moon [run] <task_id> タスクの実行

上の init のままだとすると、これで apps/, packages/ 以下の情報を漁って管理できるようにしてくれる。

各プロジェクトはどんな感じか。

.
├── .gitignore
├── .moon/
│   └── workspace.yml
└── apps/
    └── ruby-app1/
        └── moon.yml

上のように ruby-app1/ というディレクトリを掘って moon.yml を置く。ruby-app1 とは言っているが、別に中身はなくてよい。moon.yml はこんな感じ。

tasks:
  dev:
    command: ./bin/dev

query projects

$ moon query projects

を実行すると以下のような出力を得られる。

╭──────────────────────────────────────────────────────────────────────╮
│Project         Source               Toolchains                       │
│──────────────────────────────────────────────────────────────────────│
│ruby-app1       apps/ruby-app1       system                           │
╰──────────────────────────────────────────────────────────────────────╯

一応プロジェクトの体を成している。Toolchains が system になるのは仕方ないので諦めよう。Tier の高い言語以外はここは役に立たない。

query tasks

$ moon query tasks

を実行するとこんな感じ。

╭──────────────────────────────────────────────────────────────────────╮
│Task                Command         Toolchains                        │
│──────────────────────────────────────────────────────────────────────│
│ruby-app1:dev       ./bin/dev       system                            │
╰──────────────────────────────────────────────────────────────────────╯

project

$ moon project ruby-app1

はこんな感じ。Root は絶対パスになっていて長いので省略。

About ───────────────────────────

  Project: ruby-app1
  Source: apps/ruby-app1
  Root: ./apps/ruby-app1
  Toolchain: system
  Language: unknown
  Stack: unknown
  Layer: unknown
  Depends on: —

Configuration ───────────────────

  Inherits from: —
  Tags: —
  Environment variables: —

Tasks ───────────────────────────

  dev:
    › ./bin/dev

ここで(Rubyプロジェクトだと)

  • language
  • layer

辺りは設定しておいていいかも。layer は apps, packages 以外に何があるの?と思ったけど

moon.{pkl,yml} | moonrepo

  • application
  • automation
  • configuration
  • library
  • scaffolding
  • tool
  • unknown

があるらしい。ついでに stack も

moon.{pkl,yml} | moonrepo

見てみると、

  • frontend
  • backend
  • infrastructure
  • systems
  • unknown

とある。これもなるほどなぁという感じ。微妙な繋ぎのコードが必要になる場合もあるので、それなりに便利そう。

今回はいったんここまで。

More