2020年12月24日 星期四

makefile 心得、教學

  1. 1. 使用 GCC 編譯命令,並且印出 hello world
  2. 2. 使用 makefile 簡化
  3. 3. 執行 makefile
  4. 4. 多個檔案的 makefile
  5. 5. 撰寫 makefile 的一些心得
  6. 6. 編譯前的 pre-task
  7. 7. make 時不顯示指令
  8. 8. .PHONY 符號的用法
  9. 9. 在 make file 中使用 awk
  10. 10. makefile 中,定義變數
  11. 11. "?= 語法"
  12. 12. := 語法
  13. 13. 巢狀 make
  14. 14. makefile 建立目錄
  15. 15. make 內部變數

要寫 makefile 之前,首先我們必須要先從最基本的 GCC 編譯指令開始學起,才可以一步一步地建立起 makefile,幸好這不會花我們太多時間

使用 GCC 編譯命令,並且印出 hello world

建立一個檔案叫做 main.c 後,輸入以下指令

#include<stdio.h>
int main(){
    printf("\r\nHello World");
}

接著在命令列(windows 系統可以使用 cygwin)中,輸入 compile 指令

 gcc -c main.c

執行完後會產生 obj file,如 main.o
而上述所使用的編譯參數如下所示

-c : 只編譯不連結 

執行連結,使用 gcc -o 指令

 gcc -o test main.o

-o 代表作 link,-o filename 為指定輸出檔名
此時應該會出現一個叫 test 的檔案
執行 test

./test

Hello World

編譯時,如果有 header file 的時候,可以使用 -I 參數,如下所示
這邊是指定 header file 是在哪個目錄可以找的到

 gcc -c -I ./inc main.c

使用 makefile 簡化

建立一個文字檔,取名為 makefile,內容填入如下所示

test: main.o
    gcc -o main.o test
main.o: main.c
    gcc -c main.c
clean:
    rm -f *.o *.exe

先解說以下這行

main.o: main.c

main.o: 分號前面代表目標,而後面的 main.c 就是告訴 make,要完成前面那個目標的話,必須要有main.c這個前置條件, 所以如果輸入make main.o 的話,是會自動執行 gcc -c main.c 這段指令的,因為前置條件已經滿足,所以可以執行 執行完就是產生 main.o 了

再看看第一行

test: main.o

test: 代表目標,而後面的 main.o 是前置條件, 執行 make test 的時候,make 會找看看有沒有 main.o 這個檔,如果有的話就會執行,如果沒有的話,他會去找看看tag有沒有產生 main.o 的方法,並且嘗試產生出main.o

執行 makefile

在 console 輸入 make test 之後,make 會去找有無 test 這個 tag, 這邊有 test 的 tag,而執行 test 的前置條件是必須要有 main.o , 則 make 會檢查有無 main.o 這個檔案,如果沒有的話,會自動搜尋 makefile 中,有無 main.o 這個檔案的產生方法,

這邊是有的,不過產生 main.o 的先決條件是要有 main.c , 則 make 會檢查有無 main.c 這個檔案,目前是有的,

所以 make 會先去執行 main.o 那個 tag,也就是 gcc -c main.c, 執行這行指令後,會產生 main.o 這個檔案出來, 所以執行 test 這個 tag 的條件也已經滿足了,所以可以執行 test 這個 tag ,也就是執行 gcc -o main.o test, 所以產生出 test 這個檔案出來了

多個檔案的 makefile

加入第二個 .c file

main.o: main.c
    gcc -c  main.c
DataIn.o: DataIn.cpp
    gcc -c DataIn.cpp

可以觀察到 main.o 與 dataIn.o 其實差不多格式,所以應該要有個萬用的格式例如 . 這種東西來簡化, 而 make 的確是有這種簡化指令的,他是使用 % 來簡化,但 % 是屬於一對一的,也就是 foo.o 對應到 foo.c,這跟 * 不太一樣, 而目標與前置條件都有萬用符號後,其實 gcc -c 要接的那個檔案名稱,也必須要是一種變數才行

%.o: %.c
    gcc -c $<

$< : 屬於第一條件,也就是 foo.c
$@ : 屬於目標條件,也就是 foo.o

撰寫 makefile 的一些心得

先把ld 需要的 object 建立起來,如建立起 obj_files

 OBJ_FILES = \
    $(OBJDIR)/head.o  \
    $(OBJ_LIB) \
    $(OBJ_KERNEL) \

然後利用 make 的前置規則讓他去找自動產生編譯需求

 system.bin:  $(OBJ_FILES )

再利用萬用符號,讓每個檔案被編譯出來,如下所示

# == rule for kernel/ ==
$(DIR_KERENL)/%.o: $(DIR_KERENL) /%.asm
    $(AS) $< -o $@

$(DIR_KERENL)/%.o: $(DIR_KERENL) /%.c
    $(CC) $(CFLAGS) $< -o $@ 

編譯前的 pre-task

make 的前置條件,不見得是一個檔案,也可以是某個 tag

all: clean mkdir boot.img 

這邊代表需要先執行 clean 這個規則,再需要 執行 mkdir 這個規則,然後執行boot.img

make 時不顯示指令

在命令前面加上 @ ,代表不顯示該命令,如下所示 @mkdir -p $@

.PHONY 符號的用法

例如有時候都會見到

.PHONY: clean

.PHONY。這個符號的目的是告訴 make,"clean" 不是一個真正的檔案目標,只是一個標記,不要把他當成檔案來處理, 避免有檔案真的叫 clean 時,make 會在依賴性判斷時判斷錯誤,那就糗了。

在 make file 中使用 awk

awk '{ print $$1" "$$3 }' system.nm > system.bsb

只有使用一個$的話,會被make吃掉,使用兩個$,就不會消失

makefile 中,定義變數

可利用 $(MACRO) 或 ${MACRO} 來存取已定義的變數

M=$(PWD) 表明然後返回到當前目錄繼續讀入、執行當前的 Makefile。

"?= 語法"

?= 語法: ?= 是一個簡化的語法:若變數未定義,則替它指定新的值。否則,採用原有的值。例: FOO ?= bar 若 FOO 未定義,則 FOO = bar;若 FOO 已定義,則 FOO 的值維持不變。

:= 語法

:= 語法 注意到,make 會將整個 Makefile 展開後,再決定變數的值。也就是說,變數的值將會是整個 Mackfile 中最後被指定的值。例:

x = foo
y = $(x) bar
x = xyz    
# 此時 y 的值為 xyz bar

在上例中,y 的值將會是 xyz bar,而不是 foo bar。 您可以利用 := 來避開這個問題。:= 表示變數的值決定於它在 Makefile 中的位置,而不是整個 Makefile 展開後最終的值。

巢狀 make

也就是說 make 可以執行其他的 make , 如每個目錄都有自己的 make ,根目錄的 make 是可以進入到 其他目錄中,跳去執行其他的 make 後再回來 使用 -C 參數。後面帶目錄名稱如下所示

make -C boot

你就會看到 make 會 Entering directory 後,再做 make

makefile 建立目錄

必須要一個 target來幫助,如 directories,如下所示

OBJDIR = ./obj
$(OBJDIR):
    mkdir -p $@

makeDir: ${OBJDIR}

接下來就是在 make all 那邊,加入dependence

all: makeDir 

make 內部變數

$?:代表已被更新的 dependencies 的值,也就是 dependencies 中,比 targets 還新的值。
$@:代表 targets 的值。$<:代表第一個 dependencies 的值

$* :代表 targets 所指定的檔案,但不包含副檔名 


資料來源: https://wwssllabcd.github.io/blog/2016/10/03/how-to-write-make-file/

沒有留言: