cargo watch で .env ファイルの変更も監視する
TL;DR
.env と .trigger を .gitignore に追加して、以下のスクリプトを cargo run
の代わりに実行する。
#!/bin/bash
set -e -u -o pipefail
# kill all background child processes when exit.
trap "exit" INT TERM
trap "kill 0" EXIT
realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE:-$0}")")"
cd "${SCRIPT_DIR}"
# .trigger ファイルがないときは作成する
if [ ! -f .trigger ]; then
touch .trigger
fi
cargo watch -x fmt -x build -s "touch .trigger" &
cargo watch --no-ignore -w .env -s "touch .trigger" &
cargo watch --no-ignore -w ".trigger" -x run
cargo-watch について
Rust で Web サーバーを開発する際、ソースコードの変更を検知して再ビルド & サーバー再起動ができると便利です。
cargo-watchを使用するとそれが実現できます。
具体的には、 cargo-watch をインストールして以下のようにコマンドを実行すると、
cargo watch -x fmt -x run
ソースコードを変更したタイミングで自動で cargo fmt と cargo run コマンドを実行してくれるようになります。
.env ファイルを使用するプロジェクト
さて、サーバー開発をする際、環境変数の変更に伴ってサーバーを再起動したいことがあります。
たとえば、以下のような内容で各開発者のローカル環境向けの環境変数を .env ファイルとして定義しておき、
GREETING_TEXT="Good morning"
PORT=3030
rust のソースコード側では dotenv クレートを使用して、サーバー起動時に環境変数を読み込んでいるという構成を考えます。
use dotenv::dotenv;
use std::env;
use warp::Filter;
#[tokio::main]
async fn main() {
dotenv().ok(); // .env ファイルから環境変数を読み込む
let greeting_text = env::var("GREETING_TEXT").unwrap();
let port = env::var("PORT").unwrap().parse::<u16>().unwrap();
let greeting =
warp::path!("greeting" / String).map(move |name| format!("{}, {}!", greeting_text, name));
warp::serve(greeting).run(([127, 0, 0, 1], port)).await;
}
この構成のとき、ソースコードが変更された場合はもちろん、 .env ファイルが変更された場合もそれを検知してサーバーが再起動されると嬉しいです。
ただし、 .env ファイルのような各開発者向けの情報が含まれたファイルは普通は .gitignore で無視されるようになっているはずです。 cargo-watch は .gitignore で無視されたファイルをデフォルトでは監視対象にしないため、そのままでは .env ファイルの変更が検知できません。
また、 .env ファイルを監視したとしてもそのファイルが変更されたタイミングでわざわざ fmt, build 処理を呼び出すのは無駄になります。
これに対処するのが記事の冒頭の run.sh スクリプトです。
run.sh スクリプトについて
run.sh スクリプトの肝になっているのは次の3行です
cargo watch -x fmt -x build -s "touch .trigger" &
cargo watch --no-ignore -w .env -s "touch .trigger" &
cargo watch --no-ignore -w ".trigger" -x run
1行目では cargo-watch の -s
オプションを使用して、 fmt, build 処理に次いで .trigger ファイルの日付を更新しています。ここでは監視対象のファイルを特に指定していないため、 .gitignore
に含まれないようなローカルのすべてのファイルが監視対象になります。
2行目では -w
オプションを使用して .env ファイルの変更を監視し、同じく .trigger ファイルを更新しています。ここで --no-ignore
オプションを設定しているのは、 .env のような .gitignore ファイルで無視されているファイルを監視対象にするためです。
3行目では1, 2行目で更新される .trigger ファイルを監視して、変更があった場合にサーバーを起動するようにしています。
基本的な仕組みは以上ですが、この処理だけをスクリプトとして実装すると、スクリプトを終了させてもバックグラウンドで起動している1, 2行目の処理が動き続けたままになってしまって不便です。
そのため、 run.sh スクリプトではスクリプトの終了処理をトラップして、バックグラウンドで実行している1, 2行目の処理も一緒に終了させる仕組みを入れています。
Discussion