Make & Task Runners
Overview
Every project needs a way to run common commands (build, test, lint, deploy) without remembering the exact incantation. Make, Just, and Task provide a consistent make build or just test interface regardless of the underlying tech stack.
Tool Comparison
| Feature |
GNU Make |
Just |
Task |
| File |
Makefile |
Justfile |
Taskfile.yml |
| Language |
Make DSL |
Custom DSL |
YAML |
| Platform |
Everywhere |
Cross-platform binary |
Cross-platform binary |
| Dependencies |
Target-based |
Recipe-based |
Task-based |
| Variables |
$(VAR) |
{{var}} |
{{.VAR}} |
| Shell |
sh by default |
sh/bash/pwsh |
sh/bash/pwsh |
| Arguments |
Limited |
First-class |
First-class |
| Install |
Pre-installed on Unix |
cargo/brew/choco |
go install/brew/choco |
GNU Make
Makefile Anatomy
A Makefile consists of targets, prerequisites, and recipes. Recipes must be indented with a tab (not spaces).
target: prerequisites
recipe-command
Use .PHONY to declare targets that don't represent files:
.PHONY: build test lint clean dev
Example Makefile
A polyglot project with both Node.js and .NET:
.PHONY: build test lint clean dev
build:
npm run build
dotnet build
test:
npm test
dotnet test
lint:
npm run lint
dotnet format --verify-no-changes
clean:
rm -rf dist/ bin/ obj/ node_modules/
dev:
npm run dev
Variables
APP_NAME := myapp
VERSION := 1.0.0
build:
docker build -t $(APP_NAME):$(VERSION) .
Automatic Variables
| Variable |
Meaning |
$@ |
The target name |
$< |
The first prerequisite |
$^ |
All prerequisites |
Include Other Makefiles
include common.mk
Conditional Logic
ifeq ($(OS),Windows_NT)
SHELL := powershell.exe
endif
Just
Justfile Syntax
Just is simpler than Make — no tab requirement, first-class arguments, and OS-conditional recipes:
default:
@just --list
build:
npm run build
dotnet build
test *args:
npm test {{args}}
dotnet test {{args}}
lint:
npm run lint
dotnet format --verify-no-changes
dev port="3000":
PORT={{port}} npm run dev
# Run in PowerShell on Windows
[windows]
clean:
Remove-Item -Recurse -Force dist, bin, obj, node_modules
[unix]
clean:
rm -rf dist/ bin/ obj/ node_modules/
Why Just over Make
- No tab sensitivity — indentation uses spaces, not tabs
- Recipe arguments —
just test --verbose passes --verbose to the recipe
- OS-conditional recipes —
[windows] and [unix] annotations for cross-platform support
- Dotenv loading —
set dotenv-load to automatically load .env files
- Better error messages — clearer feedback when recipes fail
Task (Taskfile)
Taskfile.yml Syntax
version: '3'
tasks:
build:
cmds:
- npm run build
- dotnet build
test:
cmds:
- npm test
- dotnet test
lint:
cmds:
- npm run lint
- dotnet format --verify-no-changes
dev:
cmds:
- npm run dev
env:
PORT: 3000
clean:
cmds:
- rm -rf dist/ bin/ obj/ node_modules/
Why Task
- YAML — familiar syntax for anyone working with CI/CD, Kubernetes, or Docker Compose
- Cross-platform — single binary, works on Windows, macOS, and Linux
- Task dependencies —
deps: [build] ensures prerequisites run first
- Conditional execution — skip tasks based on file changes or environment
- Dotenv support — automatically loads
.env files
- Watch mode —
task --watch test re-runs on file changes
When to Use Which
| Scenario |
Recommendation |
| Already have a Makefile |
Keep Make |
| New project on a mixed team |
Just or Task |
| YAML-familiar team |
Task |
| Need recipe arguments |
Just or Task |
| Windows-first team |
Just or Task |
| CI/CD scripts |
Make — most compatible |
Best Practices
- Put a Makefile, Justfile, or Taskfile in every project root — it serves as the entry point for all commands
- Use it as the single interface for all project operations — build, test, lint, format, deploy, clean
- Document available commands with
make help / just --list / task --list so new developers can discover what's available
- Keep recipes simple — delegate to real build tools (npm, dotnet, cargo) rather than encoding complex logic in the task file
- Use task runners for developer experience, not as a replacement for language-specific build systems (Gradle, webpack, MSBuild)
- Commit the task file to version control and document it in the README so the whole team uses the same commands
1---2name: make-33description: Use when automating build, test, and development tasks with language-agnostic task runners. Covers GNU Make, Just, and Task (Taskfile) — tools that provide a consistent interface for project commands across any tech stack. USE FOR: Make, Makefile, Just, Justfile, Task, Taskfile, task runners, build automation, project scripts, developer experience, cross-language build commands, phony targets DO NOT USE FOR: language-specific build systems (Gradle, MSBuild, webpack — use language-specific skills), CI/CD pipeline configuration, container orchestration4license: MIT5---6
7# Make & Task Runners
8
9## Overview
10
11Every project needs a way to run common commands (build, test, lint, deploy) without remembering the exact incantation. Make, Just, and Task provide a consistent `make build` or `just test` interface regardless of the underlying tech stack.
12
13## Tool Comparison
14
15| Feature | GNU Make | Just | Task |
16|---------|----------|------|------|
17| File | `Makefile` | `Justfile` | `Taskfile.yml` |
18| Language | Make DSL | Custom DSL | YAML |
19| Platform | Everywhere | Cross-platform binary | Cross-platform binary |
20| Dependencies | Target-based | Recipe-based | Task-based |
21| Variables | `$(VAR)` | `{{var}}` | `{{.VAR}}` |
22| Shell | sh by default | sh/bash/pwsh | sh/bash/pwsh |
23| Arguments | Limited | First-class | First-class |
24| Install | Pre-installed on Unix | cargo/brew/choco | go install/brew/choco |
25
26## GNU Make
27
28### Makefile Anatomy
29
30A Makefile consists of **targets**, **prerequisites**, and **recipes**. Recipes must be indented with a **tab** (not spaces).
31
32```makefile
33target: prerequisites
34 recipe-command
35```
36
37Use `.PHONY` to declare targets that don't represent files:
38
39```makefile
40.PHONY: build test lint clean dev
41```
42
43### Example Makefile
44
45A polyglot project with both Node.js and .NET:
46
47```makefile
48.PHONY: build test lint clean dev
49
50build:
51 npm run build
52 dotnet build
53
54test:
55 npm test
56 dotnet test
57
58lint:
59 npm run lint
60 dotnet format --verify-no-changes
61
62clean:
63 rm -rf dist/ bin/ obj/ node_modules/
64
65dev:
66 npm run dev
67```
68
69### Variables
70
71```makefile
72APP_NAME := myapp
73VERSION := 1.0.0
74
75build:
76 docker build -t $(APP_NAME):$(VERSION) .
77```
78
79### Automatic Variables
80
81| Variable | Meaning |
82|----------|---------|
83| `$@` | The target name |
84| `$<` | The first prerequisite |
85| `$^` | All prerequisites |
86
87### Include Other Makefiles
88
89```makefile
90include common.mk
91```
92
93### Conditional Logic
94
95```makefile
96ifeq ($(OS),Windows_NT)
97 SHELL := powershell.exe
98endif
99```
100
101## Just
102
103### Justfile Syntax
104
105Just is simpler than Make — no tab requirement, first-class arguments, and OS-conditional recipes:
106
107```just
108default:
109 @just --list
110
111build:
112 npm run build
113 dotnet build
114
115test *args:
116 npm test {{args}}
117 dotnet test {{args}}
118
119lint:
120 npm run lint
121 dotnet format --verify-no-changes
122
123dev port="3000":
124 PORT={{port}} npm run dev
125
126# Run in PowerShell on Windows
127[windows]
128clean:
129 Remove-Item -Recurse -Force dist, bin, obj, node_modules
130
131[unix]
132clean:
133 rm -rf dist/ bin/ obj/ node_modules/
134```
135
136### Why Just over Make
137
138- **No tab sensitivity** — indentation uses spaces, not tabs
139- **Recipe arguments** — `just test --verbose` passes `--verbose` to the recipe
140- **OS-conditional recipes** — `[windows]` and `[unix]` annotations for cross-platform support
141- **Dotenv loading** — `set dotenv-load` to automatically load `.env` files
142- **Better error messages** — clearer feedback when recipes fail
143
144## Task (Taskfile)
145
146### Taskfile.yml Syntax
147
148```yaml
149version: '3'
150
151tasks:
152 build:
153 cmds:
154 - npm run build
155 - dotnet build
156
157 test:
158 cmds:
159 - npm test
160 - dotnet test
161
162 lint:
163 cmds:
164 - npm run lint
165 - dotnet format --verify-no-changes
166
167 dev:
168 cmds:
169 - npm run dev
170 env:
171 PORT: 3000
172
173 clean:
174 cmds:
175 - rm -rf dist/ bin/ obj/ node_modules/
176```
177
178### Why Task
179
180- **YAML** — familiar syntax for anyone working with CI/CD, Kubernetes, or Docker Compose
181- **Cross-platform** — single binary, works on Windows, macOS, and Linux
182- **Task dependencies** — `deps: [build]` ensures prerequisites run first
183- **Conditional execution** — skip tasks based on file changes or environment
184- **Dotenv support** — automatically loads `.env` files
185- **Watch mode** — `task --watch test` re-runs on file changes
186
187## When to Use Which
188
189| Scenario | Recommendation |
190|----------|----------------|
191| Already have a Makefile | Keep Make |
192| New project on a mixed team | Just or Task |
193| YAML-familiar team | Task |
194| Need recipe arguments | Just or Task |
195| Windows-first team | Just or Task |
196| CI/CD scripts | Make — most compatible |
197
198## Best Practices
199
200- Put a Makefile, Justfile, or Taskfile in every project root — it serves as the entry point for all commands
201- Use it as the single interface for all project operations — build, test, lint, format, deploy, clean
202- Document available commands with `make help` / `just --list` / `task --list` so new developers can discover what's available
203- Keep recipes simple — delegate to real build tools (npm, dotnet, cargo) rather than encoding complex logic in the task file
204- Use task runners for developer experience, not as a replacement for language-specific build systems (Gradle, webpack, MSBuild)
205- Commit the task file to version control and document it in the README so the whole team uses the same commands