前言
如果您有多個(gè) c、c++ 和其他語言的文件,并且想通過終端命令編譯它們,我們該如何編譯他們呢?為了解決這類問題,Makefile就出現(xiàn)了。Makefile在編譯大型項(xiàng)目的過程中,可以一次性編寫大量的源文件以及需要鏈接器標(biāo)志。廢話少說咱們直接開始今天的正文!
什么是Makefile
Makefile是一種用于簡化或組織編譯代碼的工具,是一組具有變量名稱和目標(biāo)的命令(類似于終端命令),用于創(chuàng)建和刪除目標(biāo)文件的工具。在單個(gè) make 文件中,我們可以創(chuàng)建多個(gè)目標(biāo)來編譯和刪除對象、二進(jìn)制文件。您可以使用Makefile多次編譯您的項(xiàng)目(程序)。
讓我們通過一個(gè)例子來理解:
假設(shè)我們有 3 個(gè)文件main.c
(主源文件)、 misc.c
(包含函數(shù)定義的源文件)、misc.h
(包含函數(shù)聲明)。在這里,我們將聲明和定義一個(gè)名為myFunc()
的函數(shù)來打印一些東西——這個(gè)函數(shù)將分別在misc.c
和misc.h
中定義和聲明。
misc.c
#include
#include "misc.h"
/*function definition*/
void myFunc(void)
{
printf("Body of myFunc function.\\n");
}
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.\\n");
myFunc();
fflush(stdout);
return 0;
}
上面這個(gè)場景是非常常見也是最簡單的一個(gè)多文件系統(tǒng)了,我們想要編譯他,并將他們鏈接在一起該如何做呢?顯然僅僅使用gcc等這些簡單的編譯器是不夠的,此時(shí)我們就需要用到Makefile了。
下面將內(nèi)容放在一個(gè)名為Makefile
的文件中,注意Makefile文件的名字只能是這幾個(gè)字,而且區(qū)分大小寫。
Makefile
#make file - this is a comment section
all: #target name
gcc main.c misc.c -o main
- 保存名為
Makefile
。 - 插入注釋,后跟
#
字符。 all
是一個(gè)目標(biāo)名稱,在目標(biāo)名稱之后插入:
。gcc
是編譯器名稱,main.c
,misc.c
源文件名,-o
是鏈接器標(biāo)志,main
是二進(jìn)制文件名。
“注意: Makefile必須使用 TAB 而不是空格縮進(jìn),否則make會(huì)失敗。
”
我們寫好Makefile后怎么進(jìn)行編譯呢?下面是代碼的編譯過程:
沒有目標(biāo)名稱:
make
帶有目標(biāo)名稱:
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$
此時(shí)我們就可以看到對應(yīng)文件夾里已經(jīng)生成了對應(yīng)的可執(zhí)行文件了!這就是Makefile的作用!
為什么會(huì)存在 Makefile?
Makefile 用于幫助決定大型程序的哪些部分需要重新編譯。在絕大多數(shù)情況下,編譯 C 或 C++ 文件。其他語言通常有自己的工具,其用途與 Make 相似。當(dāng)您需要一系列指令來運(yùn)行取決于哪些文件已更改時(shí),Make 也可以在編譯之外使用。本教程將重點(diǎn)介紹 C/C++ 編譯用例。
這是您可以使用 Make 構(gòu)建的示例依賴關(guān)系圖。如果任何文件的依賴項(xiàng)發(fā)生更改,則該文件將被重新編譯:
Makefile的語法
一個(gè) Makefile 由一組規(guī)則組成。規(guī)則通常如下所示:
targets: prerequisites
command
command
command
targets
:是文件名,以空格分隔。通常,每條規(guī)則只有一個(gè)。command
:是通常用于制作目標(biāo)的一系列步驟。這些需要以制表符開頭,而不是空格。prerequisites
:先決條件也是文件名,以空格分隔。這些文件需要在運(yùn)行目標(biāo)命令之前存在。這些也稱為依賴項(xiàng)
Makefile的精髓
讓我們從一個(gè) hello world
示例開始:
hello:
echo "Hello, World"
echo "This line will always print, because the file hello does not exist."
然后我們將運(yùn)行make hello
,只要hello
文件不存在,命令就會(huì)運(yùn)行。如果hello
存在,則不會(huì)運(yùn)行任何命令。
重要的是要意識到我說hello
的是target
和file
,那是因?yàn)閮烧呤侵苯勇?lián)系在一起的。通常,當(dāng)運(yùn)行目標(biāo)時(shí)(也就是運(yùn)行目標(biāo)的命令時(shí)),這些命令將創(chuàng)建一個(gè)與目標(biāo)同名的文件。在這種情況下,hello
目標(biāo)不會(huì)創(chuàng)建hello
文件。
那么我們怎么樣才能讓程序全部重新生成呢?這就要用到清理目標(biāo)文件的語句了,下面我們一起看一下如何清理已生成的目標(biāo)文件。
清理生成的目標(biāo)文件
我們還可以使用 Makefile 中的變量來概括Makefile。在此示例中,我們使用變量和干凈的目標(biāo)名稱編寫 Makefile 以刪除所有對象(.o
擴(kuò)展文件)和二進(jìn)制文件(主文件)。
#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
此時(shí)我們想要的目標(biāo)文件以及.o
文件已經(jīng)出現(xiàn)在對應(yīng)的文件夾中,那我們?nèi)绾蝿h除編譯出來的文件呢?是不是要使用rm語句一個(gè)一個(gè)的刪除呢?其實(shí)大可不必,而且在大的工程中你也不可能一個(gè)一個(gè)的刪除,所以這時(shí)候make clean
就出現(xiàn)了,他能通過一條語句就刪除剛才編譯出來的所有文件,下面我們來看一下應(yīng)該如何操作!
直接在Makefile對應(yīng)的文件夾先輸入一下命令,就會(huì)發(fā)現(xiàn)剛才生成的文件已經(jīng)消失了。
make clean
當(dāng)我們有多個(gè)文件時(shí),我們可以在 Makefile 中編寫命令來為每個(gè)源文件創(chuàng)建目標(biāo)文件。如果你這樣做 只有那些被修改的文件將被編譯。
如果我們想要全部重新編譯只需要先執(zhí)行make clean
語句在執(zhí)行make
即可。
Makefile中如何使用變量
在上面的示例中,大多數(shù)目標(biāo)值和先決條件值都是硬編碼的,但在實(shí)際項(xiàng)目中,這些值被替換為變量和模式。
在 Makefile 中定義變量的最簡單方法是使用=
運(yùn)算符。例如,要將命令分配給gcc
變量CC
:
CC = gcc
這也稱為遞歸擴(kuò)展變量,它用于如下所示的規(guī)則中:
hello: hello.c
${CC} hello.c -o hello
那么實(shí)際在終端中執(zhí)行的語句是下面的:
gcc hello.c -o hello
兩者${CC}
和$(CC)
都是對 gcc
的有效引用。但是如果想將一個(gè)變量重新分配給它自己,它將導(dǎo)致一個(gè)無限循環(huán)。讓我們驗(yàn)證一下:
CC = gcc
CC = ${CC}
all:
@echo ${CC}
運(yùn)行make
將導(dǎo)致下面的錯(cuò)誤:
$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
為了避免這種情況,我們可以使用:=
運(yùn)算符(這也稱為簡單擴(kuò)展變量)。我們運(yùn)行下面的makefile應(yīng)就不會(huì)出現(xiàn)上面的問題了:
CC := gcc
CC := ${CC}
all:
@echo ${CC}
舉個(gè)例子
下面我們通過一個(gè)實(shí)際的例子來體會(huì)一下上面講的知識點(diǎn)。以下 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
定義虛假目標(biāo)all
和clean
.- 變量
LINKERFLAG
定義要在gcc
中使用的標(biāo)志。 SRCS := $(wildcard *.c):$(wildcard pattern)
是文件名的功能之一。在這種情況下,所有帶有.c
擴(kuò)展名的文件都將存儲(chǔ)在一個(gè)變量SRCS
中。BINS := $(SRCS:%.c=%)
: 這稱為替代參考。在這種情況下,如果SRCS
有值'foo.c bar.c'
,BINS
就會(huì)有'foo bar'
。Line all: ${BINS}
:虛假目標(biāo)all
將值${BINS}
作為單獨(dú)的目標(biāo)調(diào)用。
讓我們看一個(gè)例子來理解這個(gè)規(guī)則。假設(shè)foo
是中的值之一${BINS}
。然后%
將匹配foo
(%
可以匹配任何目標(biāo)名稱)。以下是擴(kuò)展形式的規(guī)則:
foo: foo.o
@ echo "Checking.."
gcc -lm foo.o -o foo
如上所示,%
替換為foo
。%.o
替換為foo.o
。%.o
被模式化以匹配先決條件,并將%
匹配為目標(biāo)。
下面是對上述makefile的重寫,并將它被放置在具有單個(gè)文件的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
完成整個(gè)程序的編譯了,如果想刪除編譯中生成的文件,可以使用make clean
多目標(biāo)Makefile
制作多個(gè)目標(biāo)并且您希望所有目標(biāo)都運(yùn)行?做一個(gè)all
目標(biāo)。make
由于這是列出的第一條規(guī)則,如果在沒有指定目標(biāo)的情況下調(diào)用它,它將默認(rèn)運(yùn)行。
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
自動(dòng)變量和通配符
*
通配符*
和%
在 Make
中都稱為通配符,但它們的含義完全不同。*
在您的文件系統(tǒng)中搜索匹配的文件名。
# Print out file information about every .c file
print: $(wildcard *.c)
ls -la $?
*
可以在目標(biāo)、先決條件或wildcard
函數(shù)中使用。*
不能在變量定義中直接使用- 當(dāng)
*
沒有匹配到文件時(shí),保持原樣(除非在wildcard函數(shù)中運(yùn)行)
%
通配符%
確實(shí)很有用,但是由于可以使用的情況多種多樣,因此有些混亂。
- 在
matching
模式下使用時(shí),它匹配字符串中的一個(gè)或多個(gè)字符。 - 在
replacing
模式下使用時(shí),它采用匹配的詞干并替換字符串中的詞干。 %
最常用于規(guī)則定義和某些特定功能中。
結(jié)語
最后讓我們通過一個(gè)非常多汁的 Make 示例來結(jié)束本文,它適用于中型項(xiàng)目。
這個(gè) makefile 的巧妙之處在于它會(huì)自動(dòng)為您確定依賴關(guān)系。您所要做的就是將您的 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)
-
C++
+關(guān)注
關(guān)注
22文章
2108瀏覽量
73651 -
編譯
+關(guān)注
關(guān)注
0文章
657瀏覽量
32872 -
Makefile
+關(guān)注
關(guān)注
1文章
125瀏覽量
19184
發(fā)布評論請先 登錄
相關(guān)推薦
評論