Makefile 用法

Makefile 用法

基本用法

直接在命令行執行 make 命令即可。

make 命令會在當前目錄下查找 makefile 文件,找到後就去解析該文件中的第一個目標。最簡單的 makefile 內容如下:

# 要生成 main.exe 這個目標文件: 需要依賴 main.c 這個文件
main.exe: main.c
# 用於生成 main.exe 的命令
	gcc main.c -o main.exe

make 命令找到 makefile 之後,會解析其中的第一個目標 main.exe,發現該目標需要 main.c 文件,就在當前目錄中查找 main.c 文件,如果找到,就執行後面的 gcc main.c -o main.exe 來生成 main.exe,如果找不到,就報錯。

我們也可以在 makefile 中指定 main.c 文件的生成方式,比如:

# 要生成 main.exe 這個目標文件: 需要依賴 main.c 這個文件
main.exe: main.c
# 用於生成 main.exe 的命令
	gcc main.c -o main.exe

# 要生成 main.c 這個文件: 不需要依賴任何其它文件
main.c:
# 用於生成 main.c 的命令
	echo "int main() { return 0; }" > main.c

這樣,當 make 程序找不到 main.c 文件時,就會在 makefile 中找 main.c 目標,找到後,就通過該目標生成 main.c 文件,然後再繼續編譯 main.exe 文件。

格式要求

makefile 以製表符(tab)作爲命令的識別符號,所以任何命令前面都必須有一個製表符,不能使用空格代替。另外,註釋前面不要添加製表符,否則會被當作命令執行。這裏的命令就是任何可以在命令行中執行的命令。

執行原則

如果一個目標所對應的文件已經存在,並且該目標的所有依賴項都沒有發生變更,則該目標中的命令不會被執行。比如上面的例子,如果 main.exe 已經存在,並且 main.c 沒有被修改,則 make 命令將不會做任何操作。

make 通過對比 main.exemain.c 的“最後修改時間”來判斷 main.c 是否被修改,如果 main.cmain.exe 新,則判斷爲 main.c 被修改,否則未被修改。

多個目標

一個目標可以依賴多個其它目標,各個依賴之間用空格隔開。

任何一個目標的名稱都可以作爲參數傳遞給 make,讓 make 從指定的目標開始解析:

# 依賴多個目標
main.exe: main.c foo.o bar.o
	gcc main.c foo.o bar.o -o main.exe

foo.o: foo.c
	gcc -c foo.c

bar.o: bar.c
	gcc -c bar.c

# 清理臨時文件
clean:
# - 表示遇到錯誤不要停止
# @ 表示不顯示所執行的命令(不顯示 rm *.o)
	-@rm *.o

執行 make clean 可以讓 makeclean 開始執行,而不是從 main 開始執行。

三個測試文件的內容如下:

// main.c
#include <stdio.h>

void foo();
void bar();

int main() {
	printf("hello main\n");
	foo();
	bar();
	return 0;
}
// foo.c
#include <stdio.h>

void foo() {
	printf("hello foo\n");
}
// bar.c
#include <stdio.h>

void bar() {
	printf("hello bar\n");
}

使用變量

# 定義變量
CC = gcc

main.exe: main.c foo.o bar.o
	$(CC) main.c foo.o bar.o -o main.exe

foo.o: foo.c
	$(CC) -c foo.c

bar.o: bar.c
	$(CC) -c bar.c

clean:
	rm *.o

指定輸出目錄

下面的腳本將臨時文件輸出到 output 目錄中統一存放:

main.exe: main.c output/foo.o output/bar.o
	gcc main.c output/foo.o output/bar.o -o main.exe

# 依賴 output 目錄
output/foo.o: output foo.c
	gcc -c foo.c -o output/foo.o

# 依賴 output 目錄
output/bar.o: output bar.c
	gcc -c bar.c -o output/bar.o

# 創建 output 目錄
output:
	-@mkdir output

clean:
	-@rm output/*.o

注意“目標名稱”中也需要指定路徑,如果寫成下面這樣,雖然也能使用,但每次都會重新編譯 foo.obar.o 這兩個文件:

main.exe: main.c foo.o bar.o
	gcc main.c output/foo.o output/bar.o -o main.exe

# 依賴 output 目錄
foo.o: output foo.c
	gcc -c foo.c -o output/foo.o

# 依賴 output 目錄
bar.o: output bar.c
	gcc -c bar.c -o output/bar.o

# 創建 output 目錄
output:
	-@mkdir output

clean:
	-@rm output/*.o

因爲 make 命令無法在當前目錄中找到 foo.obar.o 這兩個文件,所以每次都會執行 foo.obar.o 這兩個目標中的命令。

執行子目錄中的 makefile

修改 makefile,內容如下:

# 依賴 foo bar 兩個目標,注意 output 要在 foo bar 的後面
main.exe: main.c foo bar output
	gcc main.c output/foo.o output/bar.o -o main.exe

# 創建輸出目錄
output:
	-@mkdir output

# 將 foo bar 兩個目標合在一起寫,因爲它們的命令相同
# 依賴一個永遠不存在的文件 null,相當於讓本目標每次都執行
foo bar: null
# 這裏的 $@ 就是目標名稱 foo bar,相當於 make -C foo bar
# 依次解析 for bar 中的 makefile 文件
	-@make -C $@

# 一個永遠不存在的文件
null:

將前面的 foo.cbar.c 分別放入 foobar 兩個子目錄中,然後在兩個子目錄中各創建一個 makefile 來編譯各自的文件,內容如下:

# foo/makefile 的內容

OUTPUT = ../output

# 生成 ../output/foo.o: 依賴 ../output 和 foo.c
$(OUTPUT)/foo.o: $(OUTPUT) foo.c
	gcc -c foo.c -o $(OUTPUT)/foo.o
# bar/makefile 的內容

OUTPUT = ../output

# 生成 ../output/bar.o: 依賴 ../output 和 bar.c
$(OUTPUT)/bar.o: $(OUTPUT) bar.c
	gcc -c bar.c -o $(OUTPUT)/bar.o

在這個例子中,foobar 兩個目錄的“修改時間”比最後生成的 main.exe 要舊,按道理說,再次執行 make 時,main.exe 不會被重新編譯,所以也不會再執行 foo bar: 中的命令了,但事實上,每次執行 make 時都會執行 foo bar: 中的命令,但 main.exe 卻不會被重新編譯,個人理解原因如下:

  1. main.exe: 依賴 foobar,所以要去檢查 foo bar: 是否需要更新。
  2. 檢查發現 foo bar: 所依賴的文件 null 不存在,所以導致 null 需要被更新。
  3. null 更新後,還要將 foobarnull 做新舊比較,看 foobar 是否需要更新。結果發現 null 還是不存在,就默認 foobarnull 舊,需要更新。
  4. 所以就執行 -@make -C $@ 去更新 foo bar:,導致 foobar 兩個目錄中的 makefile 被解析,由於 foo.cbar.c 沒有被更改,所以此次解析沒有改變任何內容。解析完畢後 foo bar: 就算更新完了。
  5. 然後還需要將 main.exefoobar 做新舊比較,而 foobar 的時間戳沒有發生變化,所以 main.exe 依然比 foobar 新,結果導致 main.exe 不會被更新。
# 2 -- foo bar 沒有發生變化,所以 main.exe 不需要更新
main.exe: main.c foo bar output
	gcc main.c output/foo.o output/bar.o -o main.exe

output:
	-@mkdir output

# 1 -- null 不存在,所以 foo bar 需要更新,更新後 foo bar 也沒變化
foo bar: null
	-@make -C $@

null:

需要注意的是,在 Windows 中,修改了 foo.cbar.c 的內容後,foobar 兩個目錄的時間戳並不會發生變化,只有當 foo.cbar.c 被重新編譯到 output 目錄中時,output 的時間戳纔會發生變化,而 foobar 的時間戳依然沒有變化,所以在上面的 makefile 中,需要將 main.exe: 的依賴項 output 放到 foobar 之後,以保證 foobar 中的內容被編譯後,能及時在 output 中被檢測到。

向子目錄傳遞變量

OUTDIR = output

# 導出 OUTDIR,讓子目錄中的 makefile 也可以使用該變量
export OUTDIR

main.exe: main.c $(OUTDIR) foo bar
	gcc main.c $(OUTDIR)/foo.o $(OUTDIR)/bar.o -o main.exe

$(OUTDIR):
	-@mkdir $(OUTDIR)

foo bar: null
	-@make -C $@

null:
# foo/makefile 的內容

OUTPUT = ../$(OUTDIR)

# 生成 $(OUTPUT)/foo.o: 依賴 $(OUTPUT) 和 foo.c
$(OUTPUT)/foo.o: $(OUTPUT) foo.c
	gcc -c foo.c -o $(OUTPUT)/foo.o
# bar/makefile 的內容

OUTPUT = ../$(OUTDIR)

# 生成 $(OUTPUT)/bar.o: 依賴 $(OUTPUT) 和 bar.c
$(OUTPUT)/bar.o: $(OUTPUT) bar.c
	gcc -c bar.c -o $(OUTPUT)/bar.o

其它用法

bin/main.exe: bin foo bar foo bar
# 目標名稱
	@echo $@
# 目標的目錄部分
	@echo $(@D)
# 目標的文件名部分
	@echo $(@F)

# 所有依賴(保留重複部分)
	@echo $+
# 所有依賴(去掉重複部分)
	@echo $^
# 第一個依賴
	@echo $<
# 所有比目標新的依賴
	@echo $?

bin:
foo:
bar:
# 查找當前目錄中的所有 *.c 文件,存入變量 FILES 中
FILES = $(wildcard *.c)

# 將 FILES 中的文件後綴 .c 改爲 .o 存入變量 FILES1 中
FILES1 = $(patsubst %.c, %.o, $(FILES))

# 去掉 FILES 中的文件後綴 .c 存入變量 FILES2 中
FILES2 = $(patsubst %.c, %, $(FILES))

# 顯示變量
echo:
	@echo $(FILES)
	@echo $(FILES1)
	@echo $(FILES2)
# 查找當前目錄中的所有 *.c 文件,並將後綴 .c 改爲 .o 存入 FILES 中
FILES = $(patsubst %.c, %.o, $(wildcard *.c))

# 相當於 main.o: main.c
$(FILES): %.o : %.c
	@echo $@
	@echo $+
OUTPUT = output
SUBDIR = foo bar

all:
# 遞歸執行子目錄中的 make
	@for i in $(SUBDIR); do make -C $$i; done

clean: 
	-@rm $(OUTPUT) -R
# 遞歸執行子目錄中的 make clean
	@for i in $(SUBDIR); do make clean -C $$i; done
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章