Mục tiêu: gõ make test / just test thay vì nhớ chuỗi lệnh dài; CI và người mới chạy giống nhau. Đã chạy thử với GNU Make 4.3 và just 1.59 (uvx --from rust-just just nếu chưa cài). make có sẵn trên hầu hết Linux; just cú pháp gọn hơn, truyền tham số dễ hơn nhưng phải cài thêm.
Makefile mẫu
.PHONY: help install lint test run image deploy clean
.DEFAULT_GOAL := help
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
PY ?= uv run
IMAGE := netinv
TAG ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
SRC := $(wildcard app/*.py)
export APP_ENV ?= dev
help: ## Liệt kê target
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "%-12s %s\n", $$1, $$2}'
install: ## Cài dependency
uv sync
lint: ## ruff + mypy
$(PY) ruff check .
$(PY) mypy
test: lint ## Chạy test (sau lint)
$(PY) pytest -q $(ARGS)
run:
$(PY) fastapi dev app/main.py
image:
docker build -t $(IMAGE):$(TAG) .
deploy: test image
@echo "deploy $(IMAGE):$(TAG) env=$$APP_ENV"
clean:
-rm -rf .pytest_cache .ruff_cache
out/report.txt: $(SRC)
mkdir -p $(@D)
echo $^ > $@Dòng lệnh trong recipe phải bắt đầu bằng ký tự TAB, không phải dấu cách. Sai sẽ báo Makefile:N: *** missing separator. Stop. Kiểm tra bằng cat -A Makefile: TAB hiện thành ^I.
Cú pháp Make
| Mục đích | Cú pháp |
|---|---|
| Target không phải file (luôn chạy) | .PHONY: test lint |
Target mặc định khi gõ make |
.DEFAULT_GOAL := help (hoặc target đầu tiên trong file) |
Phụ thuộc: chạy lint trước test |
test: lint |
| Gán ngay / gán lười (đánh giá khi dùng) | IMAGE := netinv / IMAGE = netinv |
| Gán nếu chưa có (cho phép override từ env) | TAG ?= dev |
| Nối thêm | FLAGS += -v |
| Dùng biến Make / biến shell trong recipe | $(IMAGE) / $$HOME |
| Kết quả lệnh shell | $(shell git rev-parse --short HEAD) |
| Danh sách file | $(wildcard app/*.py) |
| Xuất biến ra env của recipe | export APP_ENV ?= dev |
| Không in lệnh trước khi chạy | @echo xong |
| Bỏ qua lỗi của một dòng | -rm -rf build |
| Biến tự động: target / prerequisite đầu / tất cả / thư mục target | $@ / $< / $^ / $(@D) |
| Dùng bash + dừng ngay khi lỗi | SHELL := bash và .SHELLFLAGS := -eu -o pipefail -c |
| Mỗi dòng recipe là một shell riêng | cd x && make phải viết trên cùng một dòng |
Lệnh make
| Mục đích | Lệnh |
|---|---|
| Chạy target | make test |
| Truyền / override biến | make test ARGS="-k vlan", make image TAG=v1.2.0 |
| In lệnh sẽ chạy, không chạy (dry-run) | make -n deploy |
| Chạy ở thư mục khác | make -C services/api test |
| Chạy song song | make -j4 lint image |
| Bắt buộc build lại mọi target | make -B |
| Xem giá trị biến sau khi parse | make -p | grep '^TAG ' |
| Tiếp tục các target khác khi một target lỗi | make -k |
justfile mẫu
set dotenv-load
set shell := ["bash", "-euo", "pipefail", "-c"]
image := "netinv"
tag := `git rev-parse --short HEAD 2>/dev/null || echo dev`
env := env("APP_ENV", "dev")
# Liệt kê recipe (recipe đầu tiên là mặc định)
default:
@just --list
# Cài dependency
install:
uv sync
# ruff + mypy
lint:
uv run ruff check .
uv run mypy
# Chạy test, truyền thêm tham số: just test -k vlan
test *args: lint
uv run pytest -q {{args}}
# Build image, tag tuỳ chọn: just image v1.2.0
image t=tag:
docker build -t {{image}}:{{t}} .
# Deploy, hỏi xác nhận trước
[confirm("Deploy lên production?")]
deploy: test (image tag)
echo "deploy {{image}}:{{tag}} env={{env}}"
# Script nhiều dòng chạy trong một shell
backup host:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p out
echo "backup {{host}}" > out/{{host}}.txtCú pháp và lệnh just
| Mục đích | Cú pháp / lệnh |
|---|---|
| Liệt kê recipe kèm comment phía trên | just --list (-l) |
| Chạy recipe / truyền tham số | just test -k vlan, just image v1.2.0, just backup pe1 |
| Tham số có mặc định / nhận nhiều giá trị | image t=tag: / test *args: (+args = ít nhất 1) |
| Dùng biến, tham số | {{tên}} |
| Biến từ lệnh shell | tag := `git rev-parse --short HEAD` |
| Biến từ env, có mặc định | env := env("APP_ENV", "dev") |
Tự nạp file .env |
set dotenv-load |
| Phụ thuộc, kèm tham số | deploy: test (image tag) |
| Hỏi xác nhận trước khi chạy | [confirm("Deploy lên production?")], bỏ hỏi: just --yes deploy |
| Script nhiều dòng trong một shell | Dòng đầu recipe là shebang #!/usr/bin/env bash |
| Dry-run | just -n test |
| Xem một recipe / giá trị biến | just --show image / just --evaluate tag |
| Kiểm tra / sửa format justfile | just --fmt --check / just --fmt |
Chọn cái nào
| Tiêu chí | make | just |
|---|---|---|
| Có sẵn trên server/CI | Gần như luôn có | Phải cài (uv tool install rust-just, apt/brew, binary) |
| Tab bắt buộc | Có | Không (thụt đầu dòng bất kỳ, nhất quán) |
| Tham số dòng lệnh | Qua biến VAR=... |
Tham số vị trí như hàm |
| Build theo timestamp file | Có (thế mạnh của make) | Không, mọi recipe luôn chạy |
| Mỗi dòng một shell riêng | Có | Có (trừ recipe shebang) |
Chỉ dùng như task runner thì liệt kê mọi target trong .PHONY; nếu không, khi lỡ có file/thư mục tên test thì make test báo 'test' is up to date và không chạy gì.