前言
如果您有多個 c、c++ 和其他語言的文件,并且想通過終端命令編譯它們,我們該如何編譯他們呢?為了解決這類問題,Makefile就出現(xiàn)了。Makefile在編譯大型項目的過程中,可以一次性編寫大量的源文件以及需要鏈接器標志。廢話少說咱們直接開始今天的正文!
什么是Makefile
Makefile是一種用于簡化或組織編譯代碼的工具,是一組具有變量名稱和目標的命令(類似于終端命令),用于創(chuàng)建和刪除目標文件的工具。在單個 make 文件中,我們可以創(chuàng)建多個目標來編譯和刪除對象、二進制文件。您可以使用Makefile多次編譯您的項目(程序)。
讓我們通過一個例子來理解:
假設我們有 3 個文件main.c(主源文件)、 misc.c(包含函數(shù)定義的源文件)、misc.h(包含函數(shù)聲明)。在這里,我們將聲明和定義一個名為myFunc()的函數(shù)來打印一些東西——這個函數(shù)將分別在misc.c和misc.h中定義和聲明。
misc.c
?
#include?#include?"misc.h" ? /*function?definition*/ void?myFunc(void) { ????printf("Body?of?myFunc?function. "); }
?
misc.h
?
#ifndef?MISC_H ????#define?MISC_H ????? ????/*function?declaration.*/ ????void?myFunc(void); ????? #endif
?
main.c
?
#include?#include?"misc.h" ? int?main() { ????printf("Hello,?World. "); ????myFunc(); ????fflush(stdout); ? ????return?0; }
?
上面這個場景是非常常見也是最簡單的一個多文件系統(tǒng)了,我們想要編譯他,并將他們鏈接在一起該如何做呢?顯然僅僅使用gcc等這些簡單的編譯器是不夠的,此時我們就需要用到Makefile了。
下面將內容放在一個名為Makefile的文件中,注意Makefile文件的名字只能是這幾個字,而且區(qū)分大小寫。
Makefile
?
#make?file?-?this?is?a?comment?section ? all:????#target?name ????gcc?main.c?misc.c?-o?main
?
保存名為Makefile。
插入注釋,后跟#字符。
all是一個目標名稱,在目標名稱之后插入:。
gcc是編譯器名稱,main.c,misc.c源文件名,-o是鏈接器標志,main是二進制文件名。
“
注意: Makefile必須使用 TAB 而不是空格縮進,否則make會失敗。
”
我們寫好Makefile后怎么進行編譯呢?下面是代碼的編譯過程:
沒有目標名稱:
?
make
?
帶有目標名稱:
?
make?all
?
輸出:
?
????sh-4.3$?make ????gcc?????main.c?misc.c?-o?main ????sh-4.3$?./main ????Hello,?World. ????Body?of?myFunc?function. ????sh-4.3$
?
此時我們就可以看到對應文件夾里已經生成了對應的可執(zhí)行文件了!這就是Makefile的作用!
為什么會存在 Makefile?
Makefile 用于幫助決定大型程序的哪些部分需要重新編譯。在絕大多數(shù)情況下,編譯 C 或 C++ 文件。其他語言通常有自己的工具,其用途與 Make 相似。當您需要一系列指令來運行取決于哪些文件已更改時,Make 也可以在編譯之外使用。本教程將重點介紹 C/C++ 編譯用例。
這是您可以使用 Make 構建的示例依賴關系圖。如果任何文件的依賴項發(fā)生更改,則該文件將被重新編譯:
Makefile的語法
一個 Makefile 由一組規(guī)則組成。規(guī)則通常如下所示:
?
targets:?prerequisites ?command ?command ?command
?
targets:是文件名,以空格分隔。通常,每條規(guī)則只有一個。
command:是通常用于制作目標的一系列步驟。這些需要以制表符開頭,而不是空格。
prerequisites:先決條件也是文件名,以空格分隔。這些文件需要在運行目標命令之前存在。這些也稱為依賴項
Makefile的精髓
讓我們從一個 hello world 示例開始:
?
hello: ?echo?"Hello,?World" ?echo?"This?line?will?always?print,?because?the?file?hello?does?not?exist."
?
然后我們將運行make hello,只要hello文件不存在,命令就會運行。如果hello存在,則不會運行任何命令。
重要的是要意識到我說hello的是target和file,那是因為兩者是直接聯(lián)系在一起的。通常,當運行目標時(也就是運行目標的命令時),這些命令將創(chuàng)建一個與目標同名的文件。在這種情況下,hello 目標不會創(chuàng)建hello 文件。
那么我們怎么樣才能讓程序全部重新生成呢?這就要用到清理目標文件的語句了,下面我們一起看一下如何清理已生成的目標文件。
清理生成的目標文件
我們還可以使用 Makefile 中的變量來概括Makefile。在此示例中,我們使用變量和干凈的目標名稱編寫 Makefile 以刪除所有對象(.o 擴展文件)和二進制文件(主文件)。
?
#make?file?-?this?is?a?comment?section ? CC=gcc??#compiler TARGET=main?#target?file?name ? all: ????$(CC)?main.c?misc.c?-o?$(TARGET) ? clean: ????rm?$(TARGET)
?
編譯:
?
make
?
此時我們想要的目標文件以及.o文件已經出現(xiàn)在對應的文件夾中,那我們如何刪除編譯出來的文件呢?是不是要使用rm語句一個一個的刪除呢?其實大可不必,而且在大的工程中你也不可能一個一個的刪除,所以這時候make clean就出現(xiàn)了,他能通過一條語句就刪除剛才編譯出來的所有文件,下面我們來看一下應該如何操作!
直接在Makefile對應的文件夾先輸入一下命令,就會發(fā)現(xiàn)剛才生成的文件已經消失了。
?
make?clean
?
當我們有多個文件時,我們可以在 Makefile 中編寫命令來為每個源文件創(chuàng)建目標文件。如果你這樣做 只有那些被修改的文件將被編譯。
如果我們想要全部重新編譯只需要先執(zhí)行make clean 語句在執(zhí)行make即可。
Makefile中如何使用變量
在上面的示例中,大多數(shù)目標值和先決條件值都是硬編碼的,但在實際項目中,這些值被替換為變量和模式。
在 Makefile 中定義變量的最簡單方法是使用=運算符。例如,要將命令分配給gcc變量CC:
?
CC?=?gcc
?
這也稱為遞歸擴展變量,它用于如下所示的規(guī)則中:
?
hello:?hello.c ????${CC}?hello.c?-o?hello
?
那么實際在終端中執(zhí)行的語句是下面的:
?
gcc?hello.c?-o?hello
?
兩者${CC}和$(CC)都是對 gcc的有效引用。但是如果想將一個變量重新分配給它自己,它將導致一個無限循環(huán)。讓我們驗證一下:
?
CC?=?gcc CC?=?${CC} all: ????@echo?${CC}
?
運行make將導致下面的錯誤:
?
$?make Makefile:8:?***?Recursive?variable?'CC'?references?itself?(eventually).??Stop.
?
為了避免這種情況,我們可以使用:=運算符(這也稱為簡單擴展變量)。我們運行下面的makefile應就不會出現(xiàn)上面的問題了:
?
CC?:=?gcc CC?:=?${CC} all: ????@echo?${CC}
?
舉個例子
下面我們通過一個實際的例子來體會一下上面講的知識點。以下 makefile 使用了變量、模式和函數(shù)編譯所有 C 程序。
?
#?Usage: #?make????????#?compile?all?binary #?make?clean??#?remove?ALL?binaries?and?objects .PHONY?=?all?clean CC?=?gcc????????????????????????#?compiler?to?use LINKERFLAG?=?-lm SRCS?:=?$(wildcard?*.c) BINS?:=?$(SRCS:%.c=%) all:?${BINS} %:?%.o ????????@echo?"Checking.." ????????${CC}?${LINKERFLAG}?$-o?$@ %.o:?%.c ????????@echo?"Creating?object.." ????????${CC}?-c?$< clean: ????????@echo?"Cleaning?up..." ????????rm?-rvf?*.o?${BINS}
?
#注釋整行。
Line.PHONY = all clean定義虛假目標all和clean.
變量LINKERFLAG定義要在gcc中使用的標志。
SRCS := $(wildcard *.c):$(wildcard pattern)是文件名的功能之一。在這種情況下,所有帶有.c擴展名的文件都將存儲在一個變量SRCS中。
BINS := $(SRCS:%.c=%): 這稱為替代參考。在這種情況下,如果SRCS有值'foo.c bar.c',BINS就會有'foo bar'。
Line all: ${BINS}:虛假目標all將值${BINS}作為單獨的目標調用。
讓我們看一個例子來理解這個規(guī)則。假設foo是中的值之一${BINS}。然后%將匹配foo(%可以匹配任何目標名稱)。以下是擴展形式的規(guī)則:
?
foo:?foo.o ??@?echo?"Checking.." ??gcc?-lm?foo.o?-o?foo
?
如上所示,%替換為foo。%.o替換為foo.o。%.o被模式化以匹配先決條件,并將%匹配為目標。
下面是對上述makefile的重寫,并將它被放置在具有單個文件的foo.c中:
?
#?Usage: #?make????????#?compile?all?binary #?make?clean??#?remove?ALL?binaries?and?objects .PHONY?=?all?clean CC?=?gcc????????????????????????#?compiler?to?use LINKERFLAG?=?-lm SRCS?:=?foo.c BINS?:=?foo all:?foo foo:?foo.o ????????@echo?"Checking.." ????????gcc?-lm?foo.o?-o?foo foo.o:?foo.c ????????@echo?"Creating?object.." ????????gcc?-c?foo.c clean: ????????@echo?"Cleaning?up..." ????????rm?-rvf?foo.o?foo
?
這樣我們就可以使用一條語句make 完成整個程序的編譯了,如果想刪除編譯中生成的文件,可以使用make clean
多目標Makefile
制作多個目標并且您希望所有目標都運行?做一個all目標。make由于這是列出的第一條規(guī)則,如果在沒有指定目標的情況下調用它,它將默認運行。
?
all:?one?two?three one: ?touch?one two: ?touch?two three: ?touch?three clean: ?rm?-f?one?two?three
?
自動變量和通配符
* 通配符*和%在 Make 中都稱為通配符,但它們的含義完全不同。*在您的文件系統(tǒng)中搜索匹配的文件名。
?
#?Print?out?file?information?about?every?.c?file print:?$(wildcard?*.c) ?ls?-la??$?
?
*可以在目標、先決條件或wildcard函數(shù)中使用。
*不能在變量定義中直接使用
當*沒有匹配到文件時,保持原樣(除非在wildcard函數(shù)中運行)
% 通配符%確實很有用,但是由于可以使用的情況多種多樣,因此有些混亂。
在matching模式下使用時,它匹配字符串中的一個或多個字符。
在replacing模式下使用時,它采用匹配的詞干并替換字符串中的詞干。
%最常用于規(guī)則定義和某些特定功能中。
結語
最后讓我們通過一個非常多汁的 Make 示例來結束本文,它適用于中型項目。
這個 makefile 的巧妙之處在于它會自動為您確定依賴關系。您所要做的就是將您的 C/C++ 文件放入該src/文件夾中。
?
TARGET_EXEC?:=?final_program BUILD_DIR?:=?./build SRC_DIRS?:=?./src #?Find?all?the?C?and?C++?files?we?want?to?compile #?Note?the?single?quotes?around?the?*?expressions.?Make?will?incorrectly?expand?these?otherwise. SRCS?:=?$(shell?find?$(SRC_DIRS)?-name?'*.cpp'?-or?-name?'*.c'?-or?-name?'*.s') #?String?substitution?for?every?C/C++?file. #?As?an?example,?hello.cpp?turns?into?./build/hello.cpp.o OBJS?:=?$(SRCS:%=$(BUILD_DIR)/%.o) #?String?substitution?(suffix?version?without?%). #?As?an?example,?./build/hello.cpp.o?turns?into?./build/hello.cpp.d DEPS?:=?$(OBJS:.o=.d) #?Every?folder?in?./src?will?need?to?be?passed?to?GCC?so?that?it?can?find?header?files INC_DIRS?:=?$(shell?find?$(SRC_DIRS)?-type?d) #?Add?a?prefix?to?INC_DIRS.?So?moduleA?would?become?-ImoduleA.?GCC?understands?this?-I?flag INC_FLAGS?:=?$(addprefix?-I,$(INC_DIRS)) #?The?-MMD?and?-MP?flags?together?generate?Makefiles?for?us! #?These?files?will?have?.d?instead?of?.o?as?the?output. CPPFLAGS?:=?$(INC_FLAGS)?-MMD?-MP #?The?final?build?step. $(BUILD_DIR)/$(TARGET_EXEC):?$(OBJS) ?$(CXX)?$(OBJS)?-o?$@?$(LDFLAGS) #?Build?step?for?C?source $(BUILD_DIR)/%.c.o:?%.c ?mkdir?-p?$(dir?$@) ?$(CC)?$(CPPFLAGS)?$(CFLAGS)?-c?$-o?$@ #?Build?step?for?C++?source $(BUILD_DIR)/%.cpp.o:?%.cpp ?mkdir?-p?$(dir?$@) ?$(CXX)?$(CPPFLAGS)?$(CXXFLAGS)?-c?$-o?$@ .PHONY:?clean clean: ?rm?-r?$(BUILD_DIR) #?Include?the?.d?makefiles.?The?-?at?the?front?suppresses?the?errors?of?missing #?Makefiles.?Initially,?all?the?.d?files?will?be?missing,?and?we?don't?want?those #?errors?to?show?up. -include?$(DEPS)
審核編輯:湯梓紅
評論
查看更多