Skip to content

💀 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 app

Vớ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 binary

Make 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

AspectMakefileCMake
Dependency TrackingManual (error-prone)Automatic
Cross-PlatformNoYes (generates native build files)
IDE IntegrationMinimalFull (VS, CLion, VSCode)
Package ManagementNoneFetchContent, vcpkg, Conan
Parallel BuildRiskySafe by design
Incremental BuildTimestamp onlyHash + Config aware
Learning CurveDeceptively simpleModerate
Scale< 100 files< 1,000,000 files

Khi nào vẫn dùng Makefile?

✅ VẪN HỢP LÝ

  1. Học thuật/Prototypes — Project 5-10 files, đơn giản
  2. Non-C++ tasks — Document generation, scripts orchestration
  3. Legacy systems — Embedded systems với toolchain cổ
  4. Automation wrappermake 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.              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Bước tiếp theo

Đã hiểu vấn đề của Makefile, hãy chuyển sang giải pháp hiện đại:

🚀 Modern CMake Fundamentals →