專案建置[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,學成。
恭喜各位認識了編譯與建置的流程
接下來的課會回到物件導向的部分
敬請期待~