前言
如果您有多個 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.\\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;
}
上面這個場景是非常常見也是最簡單的一個多文件系統(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)
-
C++
+關注
關注
22文章
2114瀏覽量
73785 -
編譯
+關注
關注
0文章
661瀏覽量
32967 -
Makefile
+關注
關注
1文章
125瀏覽量
19200
發(fā)布評論請先 登錄
相關推薦
評論