上一篇文章:makefile入门教程
一、选项
make命令和gcc一样,也是存在多种选项,一些选项在编译调试时会带来很大的帮助,下面我将介绍一些常用的make选项
(1)-f选项
-f选项用于指定要使用哪个makefile,用法如下:
make -f <Makefile文件>
这一般用于要执行的makefile文件不叫makefile或者makefile位于其他的路径下。但注意如果使用了其他目录下的makefile,编译的其实还是自己目录下的文件,不要搞混。例如给定如下的目录
project/
│── makefile
│── src/
│ ├── main.c
│ ├── func.c
│ ├── func.h
│ ├── other_makefile
目录共有两个makefile文件,一个在src目录中(但不是默认的名称),一个位于src目录外。这两个makefile文件,每一个都可以单独完成编译,使用方法如下:
# 假设当前终端路径在src目录下,直接使用make指令会提升找不到makefile
~/project/src$ make -f other_makefile #使用方法1
~/project/src$ make -f ../makefile #使用方法2
不过-f选项更常用于在一个makefile文件中调用其他的makefile文件,例如在makefile文件中调用makefile.build的文件,这往往用于多个子目录的情况。
all :
make -f $(dir)/makefile.build
(2)-C选项
-C用于进入指定目录并执行make,然后返回原目录,用法如下:
make -C <目录> <选项>
例如给定如下目录:
project/
│── src/
│ ├── main.c
│ ├── func.c
│ ├── func.h
│ ├── makefile
假如现在在project这一目录,要在src中执行make命令来编译src中的文件,那么就可以使用-C选项来实现
~/project$ make -C ./src #相当于在src目录下执行make
~/project$ make -C ./src clean #相当于在src目录下执行make clean
(3)指定变量
用于临时修改makefile中的变量值,用法为:
make VARIABLE=value #例如make CC=gcc
例如在目前makefile中有如下内容
CC = aarch64-linux-gun-gcc
all :
echo $(CC)
如果我们执行make CC=gcc
,则会输出gcc
,而makefile文件中这里仍然是aarch64-linux-gun-gcc
这一长串,即临时修改。
(4)-n选项
-n选项常用于调试,即打印make计划执行的命令,但实际上并不执行,这在调试时很有用,可以帮助我们快速查看执行的顺序是否符合预期。
此外,对于别人的makefile文件,我们也可以使用该指令来快速查看该文件的执行顺序,从而更快的掌握该makefile。例如给定如下makefile内容,这里的依赖规则略微复杂,那么我们就可以使用make -n来查看实际的执行顺序
TARGET := test
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built!
start_recursive_build:
make -C ./ -f ./Makefile.build
$(TARGET) : start_recursive_build
gcc -o $(TARGET) built-in.o
# 执行顺序输出为:
# make -C ./ -f ./Makefile.build
# gcc -o test built-in.o
# echo test has been built!
(5)-j选项
-j选项用于指定同时执行的任务数,加快编译的速度。用法为:
make -j<n> #例如make -j4
如果希望make自动使用所有的CPU核心,则可以使用如下命令:
make -j$(nproc) #nproc指令用于返回CPU的核心数
该加速一般用于编译linux内核或者大型的项目,对于小型工程的加速效果并不是很明显。此外使用-j可能会导致编译过程报错,如果报错,可以尝试去掉-j重试!
二、 makefile.build
之前我们完成了makefile文件的优化,该文件实现自动编译并检测头文件,相当于是一个模板了。假如现在我们有一个工程,这个工程有多个子目录,我们要把一些子目录下的文件按照上述makefile构建的规则编译。那么能不能把这个makefile给单独拿出来,然后在每一个需要编译的文件目录下调用该makefile呢?这就是本节要讲的makefile.build文件了。
makefile.build其实也是makefile文件,只不过为了和makefile区分,虽然也可以叫其他的名字,但为了强调其功能就约定俗成就叫makefile.build了。
makefile.build在功能上是和makefile有所区分的:一般来说makefile 是主构建文件,一般用于管理整个编译过程;makefile.build 是辅助构建文件,通常用于管理子目录或模块的具体构建规则。
makefile.build的使用和前面所讲的选项内容是分不开的,下面我将通过一个实际案例来介绍一下makefile和makefile.build的调用关系。同时我认为该案例也是非常好的自动化模版,只要大家的文件目录符合我下面所给的目录格式,都可以拿过来不做任何更改直接使用!
project/
│── src/ #.c目录
│ ├── head.c
│ ├── func.c
│ ├── makefile
│── .../ #支持多个.c目录
│ ├── xxx.c
│── include/ #头文件目录
│ ├── func.h
│ ├── xxx.h
│── makefile
│── makefile.build
首先是makefile的内容,可以看到makefile只负责编译最终的可执行文件,中间的.o过程文件则不处理
CC := gcc
CFLAGS := -Wall -g -MMD
CFLAGS += -I $(shell pwd)/include #设置头文件的目录,否则会提示找不到头文件
CFLAGS += -c -o
TARGET := test
PWD := $(shell pwd)
sub_dir := $(shell find . -maxdepth 1 -type d) #maxdepth为1表示只搜索本级目录,type表示只搜索文件夹
sub_dir := $(filter ./%,$(sub_dir))
sub_dir := $(filter-out %include,$(sub_dir)) #剔除include文件夹,因为该文件夹放的都是头文件,不需要编译
subdir_objs := $(foreach f,$(sub_dir),$(f)/build.o)
export CC CFLAGS PWD #这些要全局使用的值就export出去,这样其他的makefile文件就可以直接使用相应的值
#@用于抑制命令回显,即在终端只会输出build finish结果,而不是前面先输出一个echo build finish
all : start_recursive_build $(TARGET)
@echo build finish
#先执行如下规则,该指令会执行当前目录的makefile.build文件
start_recursive_build :
make -C ./ -f $(PWD)/makefile.build
#编译最终的可执行文件
$(TARGET) : start_recursive_build
$(CC) -o $(TARGET) build.o
# 清理生成的.o和目标文件
clean:
rm -f $(TARGET) $(shell find -name "*.o")
# 清理生成的.o、.d和目标文件
distclean:
rm -f $(TARGET) $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
其次是makefile.build的内容,在这里完成对过程.o文件的编译链接和头文件检测
__build: #这个build非常关键!相当于是第一个目标,从而避免include makefile导致makefile的规则成为第一个目标
objs := $(wildcard *.c)
objs := $(patsubst %.c,%.o,$(objs)) #将所有的.c改成.o
make_exist := $(wildcard makefile) #查看当前目录下是否存在makefile,如果有则包含
# 利用该ifneq语句来判断include makefile是否执行
ifneq ($(make_exist),)
include makefile #主要是将makefile中定义的变量值拿过来用
endif
__build : build.o #构建依赖关系
#将当前目录和子目录下的.o文件链接成一个build.o文件
build.o : $(objs) build_sub_dir #先将本级目录.c编译,再去编译子级目录.c文件
ld -r -o $@ $(objs) $(subdir_objs)
#编译.o文件
%.o : %.c
$(CC) $(CFLAGS) $@ $<
# 对每一个子目录进行处理,注意PWD不是当前目录,而是第一级目录,即project目录
build_sub_dir :
@for dir in $(sub_dir); do make -C $$dir -f $(PWD)/makefile.build; done
#判断是否存在依赖文件
dep_files := $(wildcard *.d)
ifneq ($(dep_files),)
include $(dep_files) #将依赖的关系添加到makefile中,实现自动的头文件检测
endif
下面我简单介绍一下下面的执行过程:
- (project)执行make命令后,首先进入makefile文件开始执行,发现第一个目标为all,依赖于start_recursive_build和$(TARGET)两个目标,于是就是先去处理start_recursive_build目标。
- (project)start_recursive_build目标为处理当前目录下的makefile.build文件,于是开始执行makefile.build的文件,相当于跳转执行,执行完后会再回来
- (project)下面进入了makefile.build文件,其第一个目标为__build,依赖于build.o。而build.o又依赖于当前目录下的.o文件以及build_sub_dir,即编译子目录
- (project)于是就先将当前目录下的.c文件编译成了.o文件,然后进入build_sub_dir目标,这里使用了for循环来处理每一个子目录
- (project/src、…)对于每一个子目录,进入后同样执行makefile.build文件,由于子目录下没有子目录,于是编译完子目录生成build.o文件后返回
- (project)此时build_sub_dir完成了子目录的编译,返回build.o规则,利用子目录和当前目录的.o开始编译build.o文件,并在编译完成后返回
- (project)现在回到了一开始的makefile文件,将build.o链接成为可执行文件,从而完成整个编译的流程,打印build finish
这里我通过一个实际的案例给大家讲了一下makefile和makefile.build之间的关系,希望能给大家的学习带来帮助!