今天的內容跟Lex & Yacc比較沒關係。我們要來介紹Makefile(簡稱Make檔或製作檔案)。
還記得昨天我們有提過Lex & Yacc的編譯嗎?從Yacc、Lex到主程式,編譯的流程逐漸變得複雜且繁瑣。有沒有辦法可以直接一次執行所有的編譯指令呢?有了Makefile,便可以自己定義編譯規則與順序,自動化建構專案整體的編譯。
接下來我們就來簡單介紹Makefile的語法,以及其基本應用。
目標(Targets):Target是要構建的文件或操作的名稱。通常,Target代表執行檔或其他文件。它們出現在 Makefile 的開頭位置,由冒號(:)分隔。例如:
target: dependencies
command
依賴(Dependencies):Dependency是Target所依賴的文件或其他Target。當Dependency中的任何一個文件發生變化時,Target需要重新構建。Dependency出現在冒號(:)之後,用空格分隔。例如:
target: dependency1 dependency2
command
規則(Rules):規則是指定如何生成Target的說明。它們出現在Target和Dependency之間,以 Tab 開頭。一個Target可以有多個規則。例如:
target: dependency
command1
command2
命令(Commands):命令是實際執行的指令,用於生成Target。命令必須以 Tab 開頭。例如:
target: dependency
gcc -o target dependency
註釋(Comments):註釋以 "#" 字符開頭,用於添加說明和說明 Makefile 中的部分。例如:
# 這是一個簡單的Makefile註釋
以下是一個簡單的 Makefile 範例,該範例用於編譯一個名為 "hello" 的 C 程式:
# Makefile範例
# Target:編譯"hello"程式
hello: hello.c
gcc -o hello hello.c
# 清理Target:刪除生成的執行檔
clean:
rm -f hello
這個 Makefile 包含兩個Target。第一個Target是 "hello",它依賴於 "hello.c" 程式檔。當運行 make hello
時,它將使用 gcc
編譯 "hello.c" 文件並生成 "hello" 執行檔。第二個Target是 "clean",它用於刪除生成的執行檔。運行 make clean
時,它將刪除 "hello" 執行檔。
由於”hello”是第一個Target,若是執行命令只下”make”的時候,Makefile會自動抓取第一個Target,故產生的結果與”make hello” 指令相同。
當我們遇到多檔案的編譯時,Makefile執行時會逐條比對規則。若某規則的所有input均滿足,才會執行該規則。否則,Makefile會先執行其他可以先執行的規則,最後再回去執行該規則。
這聽起來有點像繞口令,我們來看看以下的例子。
gcc main.cpp sub.cpp -o main
這個指令寫成Makefile的結果如下:
main: main.o sub.o
gcc main.o sub.o -o main
main.o: main.cpp
gcc main.cpp -c
sub.o: sub.cpp
gcc sub.cpp -c
clean:
rm -rf main.o sub.o
當執行make指令後,讀取此Makefile的流程如下:
要注意的是,如果我們修改程式碼,再一次執行make,會發生甚麼事呢?
所以,剛剛介紹的clean規則就很重要了。在編譯之前,最好先下make clean指令清除先前產生的執行檔,重新編譯後才會得到最新的編譯成果。
Makefile的變數宣告格式如下:
${VAR}
剛剛的例子中,如果我們把C++ compiler設成變數,Makefile可以改寫成這樣:
CC = gcc
main: main.o sub.o
${CC} main.o sub.o -o main
main.o: main.cpp
${CC} main.cpp -c
sub.o: sub.cpp
${CC} sub.cpp -c
clean:
rm -rf main.o sub.o
若是我們想要改成用g++來編譯,就只要更改最上面的compiler變數數值即可。
經過了上述的介紹,我們來試試看把昨天的編譯流程寫成一個Makefile吧!
請將下面的Lex & Yacc編譯流程寫成一個Makefile。
bison -d yacc.y
flex lex.l
gcc -c yacc.tab.c
gcc -c lex.yy.c
gcc -c main.cpp
gcc main.o lex.yy.o yacc.tab.o -o main
首先,我們先來看看每一步的編譯過程的輸入與輸出分別為何。
如果忘記的話,再用之前的流程圖複習一次:
bison -d yacc.y
Input: yacc.y
Output: yacc.tab.h, yacc.tab.c
flex lex.l
Input: lex.l, y.tab.h
Output: lex.yy.c
gcc -c yacc.tab.c
Input: yacc.tab.c
Output: yacc.tab.o
gcc -c lex.yy.c
Input: lex.yy.c
Output: lex.yy.o
gcc -c main.cpp
Input: main.cpp
Output: main.o
gcc main.o lex.yy.o yacc.tab.o -o main
Input: main.o, lex.yy.o, yacc.tab.o
Output: main (main.exe)
接著,根據每一步,我們分別建立起編譯規則:
LEX=flex
YACC=bison
CC=g++
OBJECT=main
$(OBJECT): lex.yy.o yacc.tab.o main.o
$(CC) main.o lex.yy.o yacc.tab.o -o $(OBJECT)
lex.yy.o: lex.yy.c yacc.tab.h main.h
$(CC) -c lex.yy.c
yacc.tab.o: yacc.tab.c main.h
$(CC) -c yacc.tab.c
yacc.tab.c yacc.tab.h: yacc.y
$(YACC) -d yacc.y
lex.yy.c: lex.l
$(LEX) lex.l
main.o: main.cpp
$(CC) -c main.cpp
clean:
@del -f $(OBJECT) *.o lex.yy.c yacc.tab.h yacc.tab.c main.exe
由於我所使用的環境為Win10,故clean的寫法修正如上。
這裡我們在command前加上一個@符號,意思是不把執行命令輸出到螢幕,僅輸出結果。這樣一來,terminal的訊息將更加簡潔。
今天我們透過Makefile將複雜的Parser編譯流程自動化,以後就不再需要在terminal輸入一大堆編譯指令了~~
明天起,我們會介紹更多Yacc強大的語法功能,可以讀入更複雜的文本字串喔!