Giao diện
💀 The Makefile Nightmare Legacy
Hiểu tại sao Makefile — công cụ build từ 1976 — không thể scale lên production codebases hiện đại.
Makefile cơ bản — Nhìn qua thì đẹp
Đây là Makefile "sách giáo khoa" mà hầu hết tutorials dạy:
makefile
# Makefile đơn giản cho project 3 files
CXX = g++
CXXFLAGS = -std=c++20 -Wall -Wextra
app: main.o network.o utils.o
$(CXX) $(CXXFLAGS) -o app main.o network.o utils.o
main.o: main.cpp utils.h
$(CXX) $(CXXFLAGS) -c main.cpp
network.o: network.cpp network.h
$(CXX) $(CXXFLAGS) -c network.cpp
utils.o: utils.cpp utils.h
$(CXX) $(CXXFLAGS) -c utils.cpp
clean:
rm -f *.o appVới 3 files, Makefile trông clean và dễ hiểu. Nhưng hãy xem chuyện gì xảy ra khi project lớn lên...
Vấn đề #1: Manual Dependency Tracking
makefile
# Khi bạn có 50 files...
main.o: main.cpp config.h network.h utils.h logging.h database.h \
cache.h auth.h session.h metrics.h ...
# Bạn QUÊN thêm "threading.h"
# → Code compile nhưng outdated object file
# → Runtime bug mà không ai hiểu tại sao😱 THE HORROR
Bạn phải thủ công liệt kê TỪNG header dependency. Quên 1 file = stale build = bugs không thể reproduce.
┌────────────────────────────────────────────────────────────────┐
│ DEPENDENCY HELL │
├────────────────────────────────────────────────────────────────┤
│ │
│ main.cpp includes: │
│ ├── config.h │
│ │ └── nlohmann/json.hpp │
│ │ └── (200+ internal headers) │
│ ├── network.h │
│ │ └── boost/asio.hpp │
│ │ └── (500+ internal headers) │
│ └── utils.h │
│ └── fmt/format.h │
│ └── (50+ internal headers) │
│ │
│ Bạn viết thủ công 750+ dependencies? Không. Bạn bỏ qua. │
│ → Project của bạn có bug mà không ai debug được. │
│ │
└────────────────────────────────────────────────────────────────┘Vấn đề #2: No Cross-Platform Support
makefile
# Developer A (Linux)
CXX = g++
LDFLAGS = -lpthread
# Developer B (macOS)
CXX = clang++
LDFLAGS = # không cần -lpthread
# Developer C (Windows)
CXX = cl.exe
LDFLAGS = /link kernel32.lib
# Và cú pháp hoàn toàn khác...Kết quả: Mỗi người có 1 Makefile khác nhau. CI server có cái thứ 4.
🤦 REAL STORY
Tôi từng thấy repo có 5 Makefiles: Makefile.linux, Makefile.macos, Makefile.win, Makefile.ci, Makefile.docker. Nightmare.
Vấn đề #3: Parallel Build là game may rủi
makefile
# Chạy make -j8 (8 parallel jobs)
# Nhưng...
libcore.a: core.o math.o
ar rcs libcore.a core.o math.o
app: main.o libcore.a
$(CXX) -o app main.o -L. -lcore
# Race condition:
# - Job 1 đang tạo libcore.a
# - Job 2 bắt đầu link app (libcore.a chưa xong)
# → Link fails hoặc corrupt binaryMake không đảm bảo ordering khi parallel build nếu dependencies không chính xác.
Vấn đề #4: Incremental Build Failures
makefile
# Bạn thay đổi CXXFLAGS thêm -O3
CXXFLAGS = -std=c++20 -Wall -Wextra -O3
# make không biết CXXFLAGS thay đổi
# → Object files cũ (-O0) được giữ lại
# → Binary inconsistent: một nửa optimized, một nửa không☠️ SILENT CORRUPTION
Make chỉ check file timestamps, không check build configuration. Thay đổi flags? Make không rebuild.
Ví dụ Real-World: Project 1000 files
project/
├── src/
│ ├── core/ (50 files)
│ ├── network/ (80 files)
│ ├── database/ (60 files)
│ ├── auth/ (40 files)
│ ├── api/ (100 files)
│ ├── utils/ (30 files)
│ └── ... (640 files more)
├── include/
│ └── (200 headers)
├── external/
│ ├── boost/ (10,000+ files)
│ └── ...
└── Makefile (good luck writing this)Câu hỏi: Bạn có muốn viết 1000 rules với 5000+ dependencies thủ công không?
Các "giải pháp" và tại sao chúng thất bại
1. Pattern Rules
makefile
# "Thông minh" pattern rule
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
# Vấn đề: Header dependencies ở đâu?
# utils.o phụ thuộc utils.h, config.h, logging.h
# Pattern rule không biết điều này.2. Auto-dependency generation
makefile
# gcc -MM tạo dependencies
DEPFLAGS = -MMD -MP
%.o: %.cpp
$(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
-include $(OBJS:.o=.d)Tốt hơn, nhưng:
- Phức tạp để setup đúng
- Không hoạt động với Windows (MSVC)
- Không handle header-only libraries
3. Recursive Make
makefile
# Top-level Makefile
all:
cd src/core && $(MAKE)
cd src/network && $(MAKE)
cd src/auth && $(MAKE)
# "Considered Harmful" - Peter Miller, 1998
# Paper: "Recursive Make Considered Harmful"📚 MUST READ
Paper "Recursive Make Considered Harmful" giải thích tại sao pattern này phá vỡ dependency graph và gây ra incorrect builds.
So sánh với CMake
| Aspect | Makefile | CMake |
|---|---|---|
| Dependency Tracking | Manual (error-prone) | Automatic |
| Cross-Platform | No | Yes (generates native build files) |
| IDE Integration | Minimal | Full (VS, CLion, VSCode) |
| Package Management | None | FetchContent, vcpkg, Conan |
| Parallel Build | Risky | Safe by design |
| Incremental Build | Timestamp only | Hash + Config aware |
| Learning Curve | Deceptively simple | Moderate |
| Scale | < 100 files | < 1,000,000 files |
Khi nào vẫn dùng Makefile?
✅ VẪN HỢP LÝ
- Học thuật/Prototypes — Project 5-10 files, đơn giản
- Non-C++ tasks — Document generation, scripts orchestration
- Legacy systems — Embedded systems với toolchain cổ
- Automation wrapper —
make build,make test,make deploy(gọi CMake/scripts bên trong)
Kết luận: Makefile là "Technical Debt"
┌─────────────────────────────────────────────────────────────────┐
│ MAKEFILE VERDICT │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ❌ Manual dependency = Human error │
│ ❌ No cross-platform = Multiple configs │
│ ❌ Timestamp-only = Stale builds │
│ ❌ No package management = External dependency hell │
│ ❌ Complex at scale = Unmaintainable │
│ │
│ ✅ Simple for tiny projects │
│ ✅ Universal availability (any Unix) │
│ ✅ Good for automation scripts │
│ │
│ VERDICT: Use CMake for any serious C++ project. │
│ │
└─────────────────────────────────────────────────────────────────┘