Makefile

專案建置[7]

講師:會寫組合語言的溫室蔡

編譯,好麻煩

上次編譯專案用的指令

cd build
g++ -g -Wall -std=c++17 -I../include -c ../src/LinkedList.cpp
g++ -g -Wall -std=c++17 -I../include -c ../src/main.cpp
g++ -g -Wall -std=c++17 -o run.exe ./main.o ./LinkedList.o

想像如果專案變的更大

編譯指令會變得多麼混亂

GNU Make:自動化建構

Make 是一套用來編譯大型專案的工具

與單純的 Shell script 相比

它最大的特點就是加入了「依賴項系統」

方便整理各個檔案之間的依賴關係

而 Makefile 就是給 make 的編譯指示

如何寫 Makefile

在專案根目錄建立一個叫 Makefile 的檔案

$ touch Makefile # M 要大寫

Makefile 基本語法如下

target: dep1 dep2 ...
	編譯指令 # 一定要用 Tab 縮排,不能用空格

Makefile 範例

寫個 Hello world

此時你會有一個 hello.cpp

接著建一個 Makefile,並照下面的寫

hello: hello.cpp
	g++ -o hello hello.cpp

Makefile 範例

hello: hello.cpp
	g++ -o hello hello.cpp

要生成的檔案

需要的檔案

編譯指令

就可以看到被編譯出來的 hello 執行檔

到 Makefile 所在的位置底下輸入 make 指令

Makefile 範例

hello: a.o b.o
	g++ -o hello a.o b.o

a.o: a.cpp
	g++ -c a.cpp

b.o: b.cpp
	g++ -c b.cpp

當你打 make (沒參數)的時候

它會試著生成最上面的目標

有還沒滿足的依賴項則會自動尋找該目標並生成

Makefile 範例

hello: a.o b.o
	g++ -o hello a.o b.o

a.o: a.cpp
	g++ -c a.cpp

b.o: b.cpp
	g++ -c b.cpp

make b.o 等

只要有相應的規則就好

也可以 make a.o

Makefile 範例

all: hello

hello: a.o b.o
	g++ -o hello a.o b.o

a.o: a.cpp
	g++ -c a.cpp

b.o: b.cpp
	g++ -c b.cpp

clean:
	rm *.o

也可以 make a.o

make b.o 等

只要有相應的規則就好

目標甚至不一定要是檔名

但當有檔案撞名時會出問題

Makefile 範例

.PHONY: all clean

all: hello

hello: a.o b.o
	g++ -o hello a.o b.o

a.o: a.cpp
	g++ -c a.cpp

b.o: b.cpp
	g++ -c b.cpp

clean:
	rm *.o

也可以 make a.o

make b.o 等

只要有相應的規則就好

目標甚至不一定要是檔名

但當有檔案撞名時會出問題

要用 .PHONY 聲明為偽目標

Makefile 變數

.PHONY: all clean

all: hello

hello: a.o b.o
	g++ -Wall -g -std=c++17 -O2 -I./include -o hello a.o b.o

a.o: a.cpp
	g++ -Wall -g -std=c++17 -O2 -I./include -c a.cpp

b.o: b.cpp
	g++ -Wall -g -std=c++17 -O2 -I./include -c b.cpp

clean:
	rm *.o

重複的東西太多會很困擾

Makefile 變數

CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include

.PHONY: all clean

all: hello

hello: a.o b.o
	g++ $(CXXFLAGS) -o hello a.o b.o

a.o: a.cpp
	g++ $(CXXFLAGS) -c a.cpp

b.o: b.cpp
	g++ $(CXXFLAGS) -c b.cpp

clean:
	rm *.o

一般用全大寫

並用 $(變數) 取值

可以設定成變數

Makefile 變數

# 要用的編譯器
CXX = g++
# 編譯器參數
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
# 連結器參數
LDFLAGS = -L.
# 要連結的函式庫
LDLIBS = -lm

.PHONY: all clean

all: hello

hello: a.o b.o
	$(CXX) $(CXXFLAGS) -o hello a.o b.o $(LDFLAGS) $(LDLIBS)

a.o: a.cpp
	$(CXX) $(CXXFLAGS) -c a.cpp

b.o: b.cpp
	$(CXX) $(CXXFLAGS) -c b.cpp

clean:
	rm *.o

還有一些慣例變數

自動變數

# 要用的編譯器
CXX = g++
# 編譯器參數
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
# 連結器參數
LDFLAGS = -L.
# 要連結的函式庫
LDLIBS = -lm

.PHONY: all clean

all: hello

hello: a.o b.o
	$(CXX) $(CXXFLAGS) -o hello a.o b.o $(LDFLAGS) $(LDLIBS)

a.o: a.cpp
	$(CXX) $(CXXFLAGS) -c a.cpp

b.o: b.cpp
	$(CXX) $(CXXFLAGS) -c b.cpp

clean:
	rm *.o

自動變數

# 要用的編譯器
CXX = g++
# 編譯器參數
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
# 連結器參數
LDFLAGS = -L.
# 要連結的函式庫
LDLIBS = -lm

.PHONY: all clean

all: hello

hello: a.o b.o
	$(CXX) $(CXXFLAGS) -o $@ a.o b.o $(LDFLAGS) $(LDLIBS)

a.o: a.cpp
	$(CXX) $(CXXFLAGS) -c a.cpp

b.o: b.cpp
	$(CXX) $(CXXFLAGS) -c b.cpp

clean:
	rm *.o

$@:目標名稱

自動變數

# 要用的編譯器
CXX = g++
# 編譯器參數
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
# 連結器參數
LDFLAGS = -L.
# 要連結的函式庫
LDLIBS = -lm

.PHONY: all clean

all: hello

hello: a.o b.o
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS)

a.o: a.cpp
	$(CXX) $(CXXFLAGS) -c a.cpp

b.o: b.cpp
	$(CXX) $(CXXFLAGS) -c b.cpp

clean:
	rm *.o

$@:目標名稱

$^:所有依賴項名稱

自動變數

# 要用的編譯器
CXX = g++
# 編譯器參數
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
# 連結器參數
LDFLAGS = -L.
# 要連結的函式庫
LDLIBS = -lm

.PHONY: all clean

all: hello

hello: a.o b.o
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS)

a.o: a.cpp
	$(CXX) $(CXXFLAGS) -c $<

b.o: b.cpp
	$(CXX) $(CXXFLAGS) -c $<

clean:
	rm *.o

$@:目標名稱

$^:所有依賴項名稱

$<:第一個依賴項名稱

樣板

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $<

可用 % 號設定樣板,以上面的例子為例

要建構任何一個 .o 檔

都要有一個同名的 .cpp 檔

並都用同樣的指令編譯

實作時間

├── include/
│   └── LinkedList.h
├── src/
│   └── LinkedList.cpp
├── example.cpp
├── LICENSE.txt
└── README.md

把至今為止寫的程式如下安排

1. 把 LinkedList

打包成一個函式庫

然後寫一個 Makefile

它要能夠:

2. 編譯 example.cpp

解答在 Github

解答 Makefile 講解

CXX = g++
CXXFLAGS = -Wall -g -std=c++17 -O2 -I./include
LDFLAGS = -L.
LDLIBS = -llist

.PHONY: all clean

all: liblist.a

example: example.cpp liblist.a
	$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)

liblist.a: LinkedList.o
	ar -rcs $@ $<

%.o: src/%.cpp
	$(CXX) $(CXXFLAGS) -c $<

clean:
	rm *.o *.a

警告全開

除錯資訊

語言標準

優化等級

標頭檔位置

偽目標

函式庫位置

函式庫名稱

初階 Makefile,學成。

恭喜各位認識了編譯與建置的流程

接下來的課會回到物件導向的部分

敬請期待~