前言
如果您有多个 c、c++ 和其他语言的文件,并且想通过终端命令编译它们,我们该如何编译他们呢?为了解决这类问题,Makefile就出现了。Makefile在编译大型项目的过程中,可以一次性编写大量的源文件以及需要链接器标志。废话少说咱们直接开始今天的正文!
什么是Makefile
Makefile是一种用于简化或组织编译代码的工具,是一组具有变量名称和目标的命令(类似于终端命令),用于创建和删除目标文件的工具。在单个 make 文件中,我们可以创建多个目标来编译和删除对象、二进制文件。您可以使用Makefile多次编译您的项目(程序)。
让我们通过一个例子来理解:
假设我们有 3 个文件main.c
(主源文件)、 misc.c
(包含函数定义的源文件)、misc.h
(包含函数声明)。在这里,我们将声明和定义一个名为myFunc()
的函数来打印一些东西——这个函数将分别在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;
}
上面这个场景是非常常见也是最简单的一个多文件系统了,我们想要编译他,并将他们链接在一起该如何做呢?显然仅仅使用gcc等这些简单的编译器是不够的,此时我们就需要用到Makefile了。
下面将内容放在一个名为Makefile
的文件中,注意Makefile文件的名字只能是这几个字,而且区分大小写。
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$
此时我们就可以看到对应文件夹里已经生成了对应的可执行文件了!这就是Makefile的作用!
为什么会存在 Makefile?
Makefile 用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,编译 C 或 C++ 文件。其他语言通常有自己的工具,其用途与 Make 相似。当您需要一系列指令来运行取决于哪些文件已更改时,Make 也可以在编译之外使用。本教程将重点介绍 C/C++ 编译用例。
这是您可以使用 Make 构建的示例依赖关系图。如果任何文件的依赖项发生更改,则该文件将被重新编译:
Makefile的语法
一个 Makefile 由一组规则组成。规则通常如下所示:
targets: prerequisites
command
command
command
targets
:是文件名,以空格分隔。通常,每条规则只有一个。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
,那是因为两者是直接联系在一起的。通常,当运行目标时(也就是运行目标的命令时),这些命令将创建一个与目标同名的文件。在这种情况下,hello
目标不会创建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
文件已经出现在对应的文件夹中,那我们如何删除编译出来的文件呢?是不是要使用rm语句一个一个的删除呢?其实大可不必,而且在大的工程中你也不可能一个一个的删除,所以这时候make clean
就出现了,他能通过一条语句就删除刚才编译出来的所有文件,下面我们来看一下应该如何操作!
直接在Makefile对应的文件夹先输入一下命令,就会发现刚才生成的文件已经消失了。
make clean
当我们有多个文件时,我们可以在 Makefile 中编写命令来为每个源文件创建目标文件。如果你这样做 只有那些被修改的文件将被编译。
如果我们想要全部重新编译只需要先执行make clean
语句在执行make
即可。
Makefile中如何使用变量
在上面的示例中,大多数目标值和先决条件值都是硬编码的,但在实际项目中,这些值被替换为变量和模式。
在 Makefile 中定义变量的最简单方法是使用=
运算符。例如,要将命令分配给gcc
变量CC
:
CC = gcc
这也称为递归扩展变量,它用于如下所示的规则中:
hello: hello.c
${CC} hello.c -o hello
那么实际在终端中执行的语句是下面的:
gcc hello.c -o hello
两者${CC}
和$(CC)
都是对 gcc
的有效引用。但是如果想将一个变量重新分配给它自己,它将导致一个无限循环。让我们验证一下:
CC = gcc
CC = ${CC}
all:
@echo ${CC}
运行make
将导致下面的错误:
$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
为了避免这种情况,我们可以使用:=
运算符(这也称为简单扩展变量)。我们运行下面的makefile应就不会出现上面的问题了:
CC := gcc
CC := ${CC}
all:
@echo ${CC}
举个例子
下面我们通过一个实际的例子来体会一下上面讲的知识点。以下 makefile 使用了变量、模式和函数编译所有 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}
作为单独的目标调用。
让我们看一个例子来理解这个规则。假设foo
是中的值之一${BINS}
。然后%
将匹配foo
(%
可以匹配任何目标名称)。以下是扩展形式的规则:
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
由于这是列出的第一条规则,如果在没有指定目标的情况下调用它,它将默认运行。
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
自动变量和通配符
*
通配符*
和%
在 Make
中都称为通配符,但它们的含义完全不同。*
在您的文件系统中搜索匹配的文件名。
# Print out file information about every .c file
print: $(wildcard *.c)
ls -la $?
*
可以在目标、先决条件或wildcard
函数中使用。*
不能在变量定义中直接使用- 当
*
没有匹配到文件时,保持原样(除非在wildcard函数中运行)
%
通配符%
确实很有用,但是由于可以使用的情况多种多样,因此有些混乱。
- 在
matching
模式下使用时,它匹配字符串中的一个或多个字符。 - 在
replacing
模式下使用时,它采用匹配的词干并替换字符串中的词干。 %
最常用于规则定义和某些特定功能中。
结语
最后让我们通过一个非常多汁的 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文章
2108浏览量
73621 -
编译
+关注
关注
0文章
657浏览量
32852 -
Makefile
+关注
关注
1文章
125浏览量
19181
发布评论请先 登录
相关推荐
评论