どうも。日曜プログラマのUBinKitteです。
最近、自分のために画像のブックマークをするアプリを作ったのですが、
不意に、いくつか言語使って開発したらカッコよくねと思い立って、Javascript、PythonとRustが混在するプロジェクトを作ってしまいました。
こうなると手動でビルドするのは面倒です。そこで、色々とタスクランナーを調べたり、ChatGPTに聞いたりしました。
※この記事での「タスクランナー」はmakeの様なビルドの自動化をするものの事を指してるいます。
前提
- Windows環境 + MSYS2を使用したgccとrust
この記事では、例としてこんなのをビルドしてみます。
ディレクトリ構成
\---project
+---c
| main.c
|
+---dist
\---rust
| .gitignore
| Cargo.lock
| Cargo.toml
|
\---src
main.rs
また、中身は、
#include <stdio.h>
int main() {
printf("Hello world!\n");
}
use std::process::Command;
fn main() {
let _ = Command::new(r".\a.exe").spawn().expect("error!");
}
となっています。
a.exe
はmain.c
をgcc
で、rust.exe
はmain.rs
をcargo
でそれぞれコンパイルしたものです。
で、distに詰めます。
Rustのコードが甘過ぎで実行が終わったあとにHello, world!が出てきますが、細かいことは気にしないでください。
シェルスクリプトorバッチファイル
学習難易度が一番低いです。というか全員履修済みでは?
加えて、どの環境でも動きます。そういう所では最強じゃないかと...。
# ウチの環境ではcargoをmsysで動くようにしてないので動作未確認です。orz
mkdir ./dist
gcc ./c/main.c -o ./dist/a.exe
cargo build --release
cp ./rust/target/release/rust.exe ./dist/rust.exe
mkdir .\dist
gcc .\c\main.c -o .\dist\a.exe
cargo build --release --manifest-path .\rust\Cargo.toml
cp .\rust\target\release\rust.exe .\dist\rust.exe
メリット
- ほとんどの人がわかる + 学習難易度が低い
- 悩んでも情報量が多い
デメリット
- 記述が単調になりがち
- Windows環境とその他Unix環境で使い回せない -> MSYS2やCygwinなど選択肢は有る
- エラーに対してめっぽう弱い
まあ、あんまり嬉しいタスクランナーではないかもです。
ただ、小規模なプロジェクトならこれでいいでしょう。
makefile (GNU Make)
おなじみGNU Make。お恥ずかしながら私が唯一使えるものです(笑)。
圧倒的な知名度と歴史があるので、解説は省きます。各々調べてください。
CC = gcc
CFLAGS =
DIST_DIR = ./dist
C_SRC = ./c/main.c
RUST_MANIFEST = ./rust/Cargo.toml
all: $(shell (mkdir ./dist)) $(DIST_DIR)/main_c $(DIST_DIR)/main_rust
$(DIST_DIR)/main_c: $(C_SRC)
@$(CC) $(CFLAGS) $(C_SRC) -o $(DIST_DIR)/a
$(DIST_DIR)/main_rust: $(RUST_MANIFEST)
@cargo build --release --manifest-path $(RUST_MANIFEST)
@cp ./rust/target/release/rust.exe $(DIST_DIR)/rust.exe
.PHONY: all
見慣れすぎて特に感想がありません。
メリット
- ほとんどの人がわかる
- 悩んでも情報量が多い
-
make
と入力するだけで実行できる ChatGPT曰く、『伝統的なビルドツール』
デメリット
- 大規模なプロジェクトに使うには動作が遅め
- 移植性は少し低め
安心感強めのタスクランナーでしょうが、モダンなプロジェクト向きではないかもです。
というかMakefile
ってビルドツールなんですっけ...。
小回りの利く点では最強だと思います。
簡単なものならmake
だけで実行できる手軽さもいいですね。
スクリプト言語のライブラリとか
RubyのRake
やSCons
などありますが、ここではPythonとinvokeの例を紹介します。
from invoke import task
@task
def build(c):
c.run("mkdir dist")
c.run("gcc ./c/main.c -o ./dist/a")
c.run("cargo build --release --manifest-path ./rust/Cargo.toml")
c.run("cp ./rust/target/release/rust.exe ./dist/rust.exe")
invokeはpip
で入れてください。
py -m pip install invoke
また、実行するときは、
invoke build
です。
メリット
- 慣れ親しんだスクリプト言語の環境が使える
デメリット
- シェル語に依存するため、対応が必須
- 引数がうまく動作しないなど、各言語の抱える悩みがある
ChatGPT
に紹介されたので使ってみましたが、あんまり良くない気がします。
スクリプト言語が使える点は良いですが、特別な理由がなければこれを使う必要はなさそうです。
cargo-make
cargo-makeはRust製のタスクランナーです。Rustを想定した作りですが、他の言語で使うのもOKな感じです。
Githubのリリースページからダウンロードしてください。
[tasks.create_dist]
command = "mkdir"
args = ["-p", "./dist"]
[tasks.build_c]
command = "gcc"
args = ["./c/main.c", "-o", "./dist/a"]
# こういう書き方もできる
[tasks.build_rust]
script_runner = "@shell"
script = '''
cargo build --release --manifest-path ./rust/Cargo.toml
cp ./rust/target/release/rust.exe ./dist/rust.exe
'''
[tasks.build_all]
dependencies = ["create_dist", "build_c", "build_rust"]
command = "echo"
args = ["Builds completed successfully!"]
メリット
-
toml
で書ける - Rustと相性が良い
- makefileのように小回りが利く
- シェル語以外にも、
Python
やjs
、perl
まで召喚できる
デメリット
- 情報が少ない
-
toml
は複雑になると可読性が落ちる
RustのCargo.toml
の書き心地です。あれが好きな方にはおすすめ。
あの書き心地を求めていました私でしたが、引数の指定で泣きました。
args = ["./c/main.c", "-o", "./dist/a"]
ってなんだよ(憤慨)
また、script_runner
という機能でPythonやシェル語など他言語が呼べます。なぜ。シェル語が呼べるので、上の引数問題も解決します。どういうことだ。
名前こそcargo
ですが、tomlが読めるなら案外使いやすいです。
Taskfile (Task)
TaskfileはGoで実装された比較的新しいタスクランナーです。
Taskfile
は、makefile
のように独自の書き方をせず、yaml
を使用します。
インストールは各種パッケージマネージャーから、もしくはGithubのリリースページから。
# https://taskfile.dev/installation/ より一部
winget install Task.Task
brew install go-task
sudo snap install task --classic
そして、Taskfile.yml
を書きます。
version: '3'
tasks:
build_c:
cmds:
- gcc ./c/main.c -o ./dist/a
description: "Build C project"
build_rust:
cmds:
- cargo build --release --manifest-path ./rust/Cargo.toml
- cp ./rust/target/release/rust.exe ./dist/rust.exe
description: "Build Rust project"
build_all:
deps: [build_c, build_rust]
description: "Build both C and Rust projects"
yamlでタスクランナー系のものを書けるとは思いませんでしたね。
メリット
-
yaml
が使える -
makefile
のような小回りの良さ -
makefile
より記述が簡単
デメリット
- 大規模プロジェクトは厳しい
Taskのホームページには、
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make.
と書いてあり、GNU Make
をかなり意識していることが読み取れます。
yaml
での記述ができて管理しやすいですが、新しい故に使いにくい部分もありそうです。
Makefile
とDockerfileの中間みたいな感じです。yaml
わかる方なら簡単に馴染むと思います。
無理やりビルド
「他言語用のツールでビルドしよう」という企みです。
gradle
皆さんこ存じ「Gradle」はJavaなんかのビルドシステムですね。
ただ、ふつーにタスクランナーとして使えます。
プロジェクトのルートに、build.gradle
を作成します。
中身を、このように編集します。
task buildC(type: Exec) {
commandLine 'gcc', 'c/main.c', '-o', 'dist/a.exe'
}
task buildRust(type: Exec) {
commandLine 'cmd', '-c' 'cargo build --release --manifest-path rust/Cargo.toml'
}
stask copyRust(type: Copy) {
from file('rust/target/release/rust.exe')
into file('dist')
}
task buildAll {
dependsOn buildC, buildRust, copyRust
}
task createDistDir {
doLast {
mkdir 'dist'
}
}
buildAll.dependsOn createDistDir
依存関係の書いてないgradleファイルを見るのは初めてかもしれません。
gradle buildAll
で実行できます。
'cmd', '-c' 'cargo build --release --manifest-path rust/Cargo.toml'
のように指定するとなんとコマンドをそのまま使えるようです。
これを活かして、ちょっとこんなことをしてみました。
ext {
CC = 'gcc'
CFLAGS = ''
DIST_DIR = '-o ./dist'
C_SRC = './c/main.c'
}
task buildC(type: Exec) {
commandLine 'cmd', '-c', "${CC}' ${CFLAGS} ${C_SRC} ${DIST_DIR}"
}
パット見はmakefile
ですね(笑)。
task同士で依存関係も使えるので、makefile
の代替としてアリです。
もっと言えば、VSCodeの拡張機能を使えばGUI上でtaskの確認ができるので、makefile
よりも優秀かもしれません。
npm scripts
皆さんご存知、主にnode.js用のnpm scriptsです。
まず、プロジェクトのルートにpackage.json
を作ります。そして中身を、
{
"scripts": {
"build": "mkdir dist && gcc ./c/main.c -o ./dist/a && cargo build --release --manifest-path ./rust/Cargo.toml && cp ./rust/target/release/rust.exe ./dist/rust.exe"
}
}
こうしてから、npm run build
です。
「素直にコピーして貼れよ」って思いましたが、コピペできないCLI環境などでは使えるので、考えられる中で最も悪い選択肢としては有りかもしれないです。
まあシェル語っていう選択肢があるんですが...。
その他
いろいろ見つけましたが、ウチの環境ではうまく動かなかったものたちです。
msvc
を使わないようにしているので、大半動きませんでした。
まあ他の方が情報上げてるからいいかな...と。
結論
今回の例くらいの簡単なものならMakefile
でいいんじゃないでしょうか?保守性を考えるならtaskfile
な気がします。
大きくなってきたらBazel
やMeson
だったりをおすすめしたいです。今回は動きませんでしたが...。
おれはいつまでもMakefileをあいしているぜ!