はじめに
開発環境には、次の2つを明記しておくと便利です。
-
flake.nix: 開発に必要なツール -
Makefile: プロジェクトで使うコマンド
人間もAIコーディングエージェントも、同じ環境で同じコマンドを実行できます。
この記事では、Pythonで挨拶を表示するだけの小さなプロジェクトを例にします。
なお、この記事は flake.nix と Makefile を組み合わせる有用性に焦点を当てています。Nix言語の文法やMakefileの詳しい書き方については扱いません。
サンプルプロジェクト
サンプルコードは、次のGitHubリポジトリで公開しています。
構成は次のとおりです。
flake-sample/
├── flake.nix
├── flake.lock
├── Makefile
├── AGENTS.md
├── src/
│ └── flake_sample/
│ ├── __init__.py
│ ├── __main__.py
│ └── greeting.py
└── tests/
└── test_greeting.py
アプリケーションは、受け取った名前を整形して挨拶を返します。
def greeting(name: str) -> str:
normalized_name = name.strip()
if not normalized_name:
raise ValueError("name must not be empty")
return f"Hello, {normalized_name}!"
Pythonの外部パッケージは使用していません。環境とコマンドの管理方法に集中できる最小構成です。
flake.nixでツールを揃える
サンプルの flake.nix では、Python、Ruff、GNU Makeを開発環境へ追加しています。起動時のメッセージを表示する shellHook を省略すると、中心部分は次のとおりです。
{
description = "flake.nix と Makefile を使った最小の Python 開発環境";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
python313
ruff
gnumake
];
};
}
);
}
開発者ごとにPythonやRuffを個別にインストールする必要はありません。flake.lock もコミットすれば、参照するnixpkgsのリビジョンも共有できます。
次のコマンドを実行すると、宣言したツールを利用できます。
nix develop
direnvを使う場合は、.envrc を1行用意します。
use flake path:.
初回に direnv allow を実行すれば、次回からはプロジェクトへ移動するだけで開発環境が読み込まれます。
Makefileでコマンドを揃える
ツールが揃っても、実行方法が人によって異なると再現性が下がります。そこで、プロジェクトの操作を Makefile にまとめます。サンプルから主なターゲットを抜粋します。
.DEFAULT_GOAL := help
NIX := nix develop --command
.PHONY: help run lint format-check test ci
help:
@echo " run サンプルアプリを実行"
@echo " lint Ruff で静的解析"
@echo " format-check コードが整形済みか確認"
@echo " test ユニットテストを実行"
@echo " ci 全検証を一括実行"
run:
@$(NIX) env PYTHONPATH=src python -m flake_sample
lint:
@$(NIX) ruff check src tests
format-check:
@$(NIX) ruff format --check src tests
test:
@$(NIX) env PYTHONPATH=src python -m unittest discover -s tests -v
ci: lint format-check test
nix develop --command をMakeターゲット内で使っているため、dev shellの外からでも同じコマンドを実行できます。
make run
make ci
make run の出力は次のとおりです。
Hello, Nix!
メリット
flake.nix と Makefile の役割を分けると、プロジェクトの暗黙知を減らせます。
| ファイル | 役割 |
|---|---|
flake.nix |
使用するツールと開発環境を宣言する |
flake.lock |
Nixの依存リビジョンを固定する |
Makefile |
実行、静的解析、テストの入口を統一する |
AGENTS.md |
AIエージェントへ作業手順を伝える |
AIがコマンドを推測しなくてよい
AIエージェントへの指示を、最小限にできます。
1. 最初に `make help` を確認する。
2. 実行、静的解析、テストにはMakeターゲットを使う。
3. 変更後は `make ci` を成功させてから完了を報告する。
利用可能なコマンドは make help、完了条件は make ci と決まっています。AIはREADMEや既存スクリプトを調べて、テスト方法を推測する必要がありません。
ツールのバージョンもNixで揃うため、AIの環境だけでコマンドが失敗する状況を減らせます。
開発メンバーが増えても手順を共有しやすい
新しい開発メンバーは、Nixを導入してリポジトリを取得すれば、既存メンバーと同じツールを利用できます。
- PythonやRuffを個別にインストールしなくてよい
-
make helpでプロジェクトの操作を確認できる - 開発者ごとのコマンドの違いを減らせる
- ローカルでもCIでも
make ciを完了条件にできる
セットアップ手順を人ごとに説明する代わりに、flake.nix と Makefile を更新すれば、プロジェクト全体へ共有できます。
デメリット
Nixの学習コストは高い
Nix言語には独特の構文があり、複雑なパッケージ定義やビルドまで扱おうとすると学習コストが高くなります。
ただし、プロジェクト単位のFlakesで開発環境を用意するだけなら、最初は devShell の packages に必要なツールを並べるところから始められます。今回のサンプル程度であれば、Nixのすべてを理解する必要はありません。
初回のインストールと環境構築が重い
最初にNix本体をインストールする必要があります。また、初回の nix develop ではPythonやRuffなどをダウンロードするため、時間とディスク容量を使います。
2回目以降はNixのキャッシュを利用できるため速くなりますが、小さなプロジェクトでは初回コストを大きく感じる場合があります。
まとめ
-
flake.nixにPython、Ruff、GNU Makeを宣言する -
flake.lockで開発ツールの参照先を固定する -
Makefileにプロジェクトの実行方法を集約する - 変更後は人間もAIも
make ciを実行する - プロジェクト単位の小さなFlakesから始める
小さなプロジェクトでも、環境とコマンドの入口を揃えておくと、開発手順を再現しやすくなります。
ちなみに、この flake.nix と Makefile は、私もCodexやClaude Codeに書かせています。笑