工程目錄
假如我們有以下目錄結(jié)構(gòu):
.
├── inc
│ ├── add.h
│ └── sub.h
├── main.c
└── src
├── add.c
└── sub.c
文件中的內(nèi)容如下:
//main.c
#include
#include "add.h"
#include "sub.h"
int main()
{
int x = 9;
printf("x = %d\\\\\\\\\\\\\\\\n", add_one(x));
printf("x = %d\\\\\\\\\\\\\\\\n", sub_one(x));
return 0;
}
//add.h
int add_one(int x);
//add.c
int add_one(int x)
{
return x + 1;
}
//sub.h
int sub_one(int x);
//sub.c
int sub_one(int x)
{
return x - 1;
}
對于上述這樣的多.c文件,又不在同一個目錄下的大型工程中,借助makefile可以來減輕工作任務(wù)
(上述是一個很小很小的工程)
準(zhǔn)備工作
在使用gcc 將 源文件 main.c編譯成 可執(zhí)行目標(biāo)程序 總共需要4步:
平常在編譯項目時,預(yù)處理與編譯器這兩步會省略,是先將源文件 .c 編譯成 .o 文件,然后再鏈接 .o 文件
gcc -c main.c -o main.o
gcc main.o -o main.exe/main.out
編寫Makefile
接下來會一步一步的編寫一個Makefile文件,這個文件可以適配于大部分C/C++工程,讓我們開始吧!
1. 定義可執(zhí)行文件名、GCC類型
先定義一個最終可執(zhí)行文件名的變量:
TARGET = main
變量值可以隨意定義。
gcc分為很多種,常見的有:gcc、arm-linux-gcc、arm-none-eabi-gcc等等,所以為了Makefile適配更多的C/C++項目,可以將編譯器定義一個變量,這后續(xù)更改起來很方便。我這里使用的gcc:
CC = gcc
2. 中間文件的路徑的變量
由前文可知,在編譯過程中會編譯出很多的 .o 文件,一般將這些編譯過程中產(chǎn)生的文件單獨放到一個文件夾下,文件夾的名字大多叫做 build ,定義一個變量 BUILD_DIR 該變量的值就是build,用來存放中間產(chǎn)物,在后續(xù)編譯過程中會用到:
BUILD_DIR = build
3、.c 源文件的路徑
事先需要將工程中所用到的源文件 .c 的路徑,這樣在后續(xù)中就可直接得到 .c 文件,定義一個變量 SRC_DIR 來存放源文件 .c 的路徑
SRC_DIR = \\\\\\\\\\\\\\\\
./ \\\\\\\\\\\\\\\\
./src
4、 頭文件的路徑
接著得到所有用到的頭文件路徑:
INC_DIR = \\\\\\\\\\\\\\\\
./inc
這gcc選項中有這個參數(shù) -I 是告訴編譯器頭文件的路徑,在后續(xù)中會使用Makefile的一個函數(shù)為每個頭文件路徑添加 -I 。
5、為頭文件路徑添加 -I
當(dāng)所引用的頭文件與源文件不在同一級目錄下時需要添加 -I 選項指定頭文件路徑,在第四步中已經(jīng)獲取到頭文件的路徑,下面借助一個Makefile中的一個函數(shù)在每個頭文件前面添加 -I
首先看一下函數(shù) patsubst 的介紹。
$(patsubst ,,)
-
名稱:模式字符串替換函數(shù)。
-
功能:查找 中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式 ,如果匹配的話,則以 替換。這里, 可以包括通配符 % ,表示任意長度的字串。如果 中也包含 % ,那么, 中的這個 % 將是 中的那個 % 所代表的字串。(可以用 **** 來轉(zhuǎn)義,以 % 來表示真實含義的 % 字符)
-
返回:函數(shù)返回被替換過后的字符串。
-
示例:
$(patsubst %.c, %.o, x.c.c bar.c)
把字串 x.c.c、bar.c 符合模式 %.c 的單詞替換成 %.o ,返回結(jié)果是 x.c.o bar.o-
定義一個變量 INCLUDE
INCLUDE = $(patsubst %, -I %, $(INC_DIR))
這樣就會在每個頭文件路徑前加入 -I 了。
6、得到帶路徑的源文件
在第三步中,我們得到了 .c 文件的存放路徑,這一步我們得到帶有路徑的 .c 文件,簡單來說就是,假如在src目錄下有一個foo.c的文件,在第3步中只得到了 src 這個目錄,這一步得到的是 src/foo.c 。
得到目錄下的 .c 文件需要用到Makefile中的兩個函數(shù),foreach函數(shù) 、wildcard 函數(shù)
1、wildcard 函數(shù)
$(wildcard PATTERN...)
在Makefile中,它被展開為已經(jīng)存在的、使用空格分開的、匹配此模式的所有文件列表
2、foreach函數(shù)
$(foreach < var >,< list >,< text >)
這個函數(shù)的意思是,把參數(shù) 中的單詞逐一取出放到參數(shù) 所指定的變量中,然后再執(zhí)行 所包含的表達式。每一次 會返回一個字符串,循環(huán)過程中, 的所返回的每個字符串會以空格分隔,最后當(dāng)整個循環(huán)結(jié)束時, 所返回的每個字符串所組成的整個字符串(以空格分隔)將會是foreach函數(shù)的返回值。
所以, 最好是一個變量名,\\ 可以是一個表達式,而 中一般會使用 這個參數(shù)來依次枚舉 中的單詞。
舉個例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中, (name) 中的單詞會被挨個取出,并存到變量 n 中, (n).o 每次根據(jù) **(n) 計算出一個值,這些值以空格分隔,最后作為foreach函數(shù)的返回,所以, **(files) 的值是 a.o b.o c.o d.o 。
使用這兩個函數(shù)得到帶路徑的 .c 文件
CFILES := $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
7. 得到不帶路徑的 .c 文件
在上一步中我們得到了帶路徑的 .c 文件,這步借助Makefile中的函數(shù) notdir 將路勁去除,得到 "真正的.c"
notdir 函數(shù)
$(notdir
- 名稱:取文件函數(shù)——notdir。
- 功能:從文件名序列 中取出非目錄部分。非目錄部分是指最後一個反斜杠( / )之后的部分。
- 返回:返回文件名序列 的非目錄部分。
- 示例:
$(notdir src/foo.c hacks)
返回值是 foo.c hacks 。
定義一個變量 CFILENDIR 來存放不帶路徑的 .c 文件:
CFILENDIR := $(notdir $(CFILES))
8. 將工程中的.c 文件替換成 ./build 目錄下對應(yīng)的目標(biāo)文件 .o
這一步只是簡單的字符串進行替換,對原文件不進行任何操作。我們可以先寫一個偽目標(biāo),打印一下變量 CFILENDIR 的內(nèi)容
# 打印結(jié)束后可以刪除
print:
@echo $(CFILENDIR)
使用 make 查看一下輸出結(jié)果,會得到字符串:main.c add.c sub.c
在前面講過編譯時會在 build 目錄下得到.o文件,這個.o 文件就是由.c文件生成的,因此 main.c add.c sub.c 會對應(yīng)于 build 目錄下的 main.o add.o sub.o
由于現(xiàn)在不是編譯階段,我們只對字符串進行個簡單的替換操作,定義一個變量 COBJS
用來存放目錄 build 下的 .o 文件
COBJS = $(patsubst %, ./$(BUILD_DIR)/%, $(patsubst %.c, %.o, $(CFILENDIR)))
此時變量 COBJS 的值就是:./build/main.o ./build/add.o ./build/sub.o
到目前為止已經(jīng)得到了工程中的源文件 CFILENDIR
、可重定位目標(biāo)文件 COBJS
以及帶有 -I 前綴的頭文件路徑 INCLUDE
,注意,到目前為止我們操作的只是字符串而已,還未對源文件做任何操作。
9、搜索源文件
在我們這個工程中,有兩個目錄下存放著 .c 文件,當(dāng)make需要去找尋文件的依賴關(guān)系時,可以使用變量 VPATH
讓make在自動在這兩個目錄中去找依賴文件。
VPATH = $(SRC_DIR)
10、生成可重定位目標(biāo)文件(編譯階段)
$(COBJS) : $(BUILD_DIR)/%.o : %.c
@mkdir -p $(BUILD_DIR)
$(CC) $(INCLUDE) -c -o $@ $
會將源文件 .c 編譯成可重定位目標(biāo)文件 .o
11、鏈接 .o 文件
此步驟是最后一步,將所有的 .o 文件鏈接成可執(zhí)行程序
可執(zhí)行文件可以生成到指定的目錄下,我這里生成到了 build 目錄下
$(BUILD_DIR)/$(TARGET).exe : $(COBJS)
$(CC) -o $@ $^
此時,Makefile 已經(jīng)編寫完成。
當(dāng)執(zhí)行make 時,發(fā)現(xiàn)并不是預(yù)期的目標(biāo),只執(zhí)行了一句指令:
gcc -I ./inc -c -o build/main.o main.c
這是因為make會一層又一層地去找文件的依賴關(guān)系,直到最終編譯出第一個目標(biāo)文件,如是依賴存在編譯成功后就會退出執(zhí)行,若是沒有找到依賴,則會報錯并退出。
當(dāng)想達到預(yù)期的目標(biāo),共有兩種辦法:
第一種:將目標(biāo)**(BUILD_DIR)/**(TARGET).exe 寫在目標(biāo) $(COBJS) 的前面,這樣就可以達到預(yù)期的結(jié)果了。
第二種:使用關(guān)鍵字 all ,寫在關(guān)鍵字 all 后面的目標(biāo)都會執(zhí)行一次,直到所有目標(biāo)執(zhí)行完成,或者某個目標(biāo)不成立。
此時,再執(zhí)行make,就能得到預(yù)期的結(jié)果了
12、清理目標(biāo)
make編譯之后會在工程中多出很多目標(biāo)文件*.o,可以寫一個目標(biāo) clean 用來刪除工程中的目標(biāo)文件
clean:
rm -rf $(BUILD_DIR)
Makefile全部內(nèi)容:
# 可執(zhí)行文件名
TARGET = main
# gcc類型
CC = gcc
# 存放中間文件的路徑
BUILD_DIR = build
#存放.c 源文件的文件夾
SRC_DIR = \\\\\\\\\\\\\\\\
./ \\\\\\\\\\\\\\\\
./src
# 存放頭文件的文件夾
INC_DIR = \\\\\\\\\\\\\\\\
./inc
# 在頭文件路徑前面加入-I
INCLUDE = $(patsubst %, -I %, $(INC_DIR))
# 得到帶路徑的 .c 文件
CFILES := $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
# 得到不帶路徑的 .c 文件
CFILENDIR := $(notdir $(CFILES))
# 將工程中的.c 文件替換成 ./build 目錄下對應(yīng)的目標(biāo)文件 .o
COBJS = $(patsubst %, ./$(BUILD_DIR)/%, $(patsubst %.c, %.o, $(CFILENDIR)))
# make 自動在源文件目錄下搜索 .c 文件
VPATH = $(SRC_DIR)
$(BUILD_DIR)/$(TARGET).exe : $(COBJS)
$(CC) -o $@ $^
$(COBJS) : $(BUILD_DIR)/%.o : %.c
@mkdir -p $(BUILD_DIR)
$(CC) $(INCLUDE) -c -o $@ $
clean:
rm -rf $(BUILD_DIR)
至此,Makefile通用模板已經(jīng)編寫完成,文章中若有錯誤的地方請在評論區(qū)指出。
審核編輯:湯梓紅
-
文件
+關(guān)注
關(guān)注
1文章
566瀏覽量
24746 -
Makefile
+關(guān)注
關(guān)注
1文章
125瀏覽量
19184 -
make
+關(guān)注
關(guān)注
0文章
16瀏覽量
12524
發(fā)布評論請先 登錄
相關(guān)推薦
評論