はじめに
俺は非IT系のJapanese Traditional big Companyでエンジニアをしている。
業務で遭遇した課題にインスパイアされてJTCMakeという汎用的なビルドツールを2022年の7月頃からプライベートで開発してきた。そろそろ安定してきたので宣伝させてほしい。
機能概要
JTCMakeは構造的タスク管理および差分ビルドのためのツールである。当初想定していた用途は機械学習パイプラインの管理だが、より一般にファイル生成タスクの自動化に使うことができる。
基本的な機能はMakefileのようにファイル作成のルールの集合を定義して差分ビルドをすることだが、さらに以下のような特徴がある。
- 入力ファイルの内容にもとづいてタスクの実行要否を判定可能(入出力ファイルのタイムスタンプベースでの判定も可能)
- ルールはPythonで記述
- 出力ファイルのディレクトリ階層構造をタスクが持つ階層構造と対応させてシンプルに扱うことができる
- MypyやPyright等の型検査器を使ってルールの記述を検査できる
- ファイルやルールの依存関係や出力ディレクトリツリーを可視化する便利ツールを標準搭載
ビルドルールをPythonで記述するのでシェルスクリプトと比較して複雑な処理が書けるほか、Windowsでも使えるというメリットがある1。さらに2〜5(特に4の型検査)の性質により大規模なプログラムをあまり苦労なく書くことができる。
3は何を言ってるかわかりづらいと思うが、例えば機械学習というタスクは前処理・訓練・評価という3つのサブタスクからなり、さらに評価は推論、性能指標の計算、エラー分析のための可視化などのサブタスクからなる、という具合に階層構造を持っており、各タスクにおいて生成されるファイルはタスク階層に類似したディレクトリ構造で管理されるのが一般的である。その性質をソースコード上でも自然かつ非冗長な形で表現する仕組みがあるくらいの意味。
モチベーション
俺は大学時代に機械学習絡みの研究のようなことをしていてその当時からデータの前処理やモデル訓練、評価結果の集計などを差分ビルドできたらなあという気持ちがあった。具体的にはJupyter Notebook上でパイプラインの一部をちょっと変えた後で更新の必要なセルだけ手動で選んでちまちま実行するのが疲れるとか、コマンドラインフラグに応じて特定のルーチンだけ実行するオレオレ部分実行機能を備えたシェルスクリプトを書くのがしんどい等の問題意識があった。そもそもシェルスクリプトで制御構文を書きたくないし、どうせ機械学習でPythonを使うのでワークフローも可能な限りPythonで管理してシェルスクリプトは最小限にしたいとも考えていた。
Makefileを使えばよいという意見はあって実際たまに使っていたが、$<
、$$
などの記法の可読性の悪さ、シェルスクリプトの表現力が小ささ、生成物がMakefileから遠い場所にある場合のパス記述の冗長さ等々であまり実用的だと思えなかった。JTCに就職してからは開発環境がWindowsになってそもそもMakefileが使えなくなった2。
仕事で某古の統計的音声認識ツールキットを触ったことも動機のひとつである。そのツールはデータの入出力がファイルベースになっており便利なPythonラッパーはない。さらに統計的音声認識の性質上、学習や推論のコンポーネントが複雑で入出力ファイルの管理が煩雑になりやすい。そのため学習・推論の全工程を宣言的に記述してファイルや処理をノードとする計算グラフを可視化できると良いと思われた。
以上の経緯があって何かよいツールはないかと探してみた。ビルドツールというとCMakeやBazel、Rakefile等が有名だし機械学習のワークフロー管理についてもMLflowなどいくつか知られたものがあるが俺のニーズとマッチしていない気がしたので結局自分で作ることにした。車輪の再発明ではあるがJTCMakeがフィットするようなケースが少しはあると思う。
大変だったこと
Pythonの型ヒントのエコシステムに手こずらされた。
JTCMakeはビルドルールの記述にDSLを用いず通常のPythonの構文で行う仕様になっている。これにより開発者はパーサ作成の手間を省くことができ、ユーザにとってはDSLの学習コストが発生せず、Pythonの静的型検査を応用してルールの記述を検証できるというメリットが有る。一方でPythonの型システムが未成熟なこともあり、型ヒントが活きるような(≒なるべくAnyを避ける)APIを設計する難易度が高く、これを考えるのが開発において最も大変だった。例えばTypescriptのIntersection型やMapped Type、Conditonal Typeに相当するものが無くて型パズルの自由度が低かったりtyping_extensionsにしかない型はどれか調べる必要があったり、Pyright3は対応しているがMypyは対応していない型への対応方法を考えなければならなかったりして消耗した。
終わりに
まだコードは荒削りなところがあるしAPIも改善の余地があるかもしれないが最低限の完成度にはなったと感じている。自分以外のユーザがいないツールをミリ改善することにあまりモチベーションがわかなくなったのでこの記事を以て一旦成仏とする。質問や改善要望が来たら対応します。
-
Windows云々は所属組織固有のニーズであり一般的ではないという認識はあります ↩
-
NMAKEというVisual Studio版のMakefile的なものはありますが使い方を覚える気が起きませんでした。ソフトウェアのビルドに使う想定なのであまり複雑な処理の記述には向いていないという点はMakefileと同じだと思います。 ↩
-
自分はVimにcoc-pyrightというプラグインを入れて開発していますが、利用者の多いVSCodeのPython拡張も裏はPyrightらしいのでMypyとの不整合は問題になるんじゃないかと思います(があまり聞かない)。なおPyrightの方が最新の型に対応していて型検査が賢い気がしているのでJTCMake本体の型検査はPyrightのみでやっています。 ↩