Makefile
安装命令
sudo apt install make
GCC 编译工具链在编译一个C源文件时需要经过以下 4 步:
- 预处理:为把头文件的代码、宏之类的内容转换成生成的.i文件,还是C代码。
- 编译:把预处理后的.i文件通过编译成.s文件,汇编语言。
- 汇编:将汇编语言文件生成目标文件.o文件,机器码。
- 链接:将每个源文件对应的.o文件链接起来,就生成一个可执行程序文件
main_exec : main.o head.o #注意空格,否则会报错
gcc main.o head.o -o main_exec
head.o : head.c
gcc -c head.c -o head.o
main.o : main.c
gcc -c main.c -o main.o
clean:
rm -rf *.o main_exec
Makefile中的变量
普通变量
一般是用户自己根据需求定义的变量。变量的定义要做到见名知意。
- = 是最普通的等号,在 Makefile 中容易搞错赋值等号,使用 “=” 进行赋值,
- 变量的值是整个 Makefile 中最后被指定的值。
OBJ_A = A
OBJ_B = $(OBJ_A)B
OBJ_A = AA
all:
@echo ${OBJ_B}
打印结果:AAB
- “:=” 表示直接赋值,赋予当前位置的值。
OBJ_A := A
OBJ_B := $(OBJ_A)B
OBJ_A := AA
all:
@echo ${OBJ_B}
打印结果:AB
- “?=” 表示如果该变量没有被赋值,赋值予等号后面的值。(可理解为初始化变量)
自动变量
变量名 | 含义 |
---|---|
$@ | 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模式中定义的目标集合。 |
$< | 依赖文件集合中的第一个文件. |
$^ | 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,会去除重复的依赖文件,只保留一份。 |
代码实例:
CC := gcc
TARGET := main_exec
OBJECT := main.o head.o
$(TARGET) : $(OBJECT)
$(CC) $^ -o $@
%.o : %.c
$(CC) -c $^ -o $@
clean :
rm -rf *.o main_exec
等同于
main_exec : main.o head.o #注意空格,否则会报错
gcc main.o head.o -o main_exec
head.o : head.c
gcc -c head.c -o head.o
main.o : main.c
gcc -c main.c -o main.o
clean:
rm -rf *.o main_exec
伪目标
当 makefile 目录下有一个和目标相同的文件时,例如 clean 文件。我们在执行 make clean 命令的时候会出现错误。伪目标就是用于解决此种错误而产生。
伪目标只是一个标签。
当前项目目录下有 Makefile fun.c fun.h main.c clean此时我们执行make clean命令不生效。修改Makefile的代码。在重复的标签前面加上.PHONY
.PHONY : clean
CC := gcc
TARGET := main_exec
OBJECT := main.o head.o
$(TARGET) : $(OBJECT)
$(CC) $^ -o $@
%.o : %.c //一个.o对应一个.c文件
$(CC) -c $^ -o $@
clean :
rm -rf *.o main_exec
函数
1、wildcard
SRC = $(wildcard ./*.c)
功能: 匹配目录下所有.c 文件,并将其赋值给SRC变量
2、patsubst
$(patsubst 原模式, 目标模式, 文件列表)
示例:
OBJ = $(patsubst %.c, %.o, $(SRC))
功能:这个函数有三个参数,意思是取出SRC中的所有值,然后将.c 替换为.o 最后赋值给OBJ变量。
代码实例:
CC := gcc
TARGET := main_exec
SRC := $(wildcard *.c)
OBJECT := $(patsubst %.c,%.o,$(SRC))
$(TARGET) : $(OBJECT)
$(CC) $^ -o $@
%.o : %.c
$(CC) -c $^ -o $@
clean :
rm -rf *.o main_exec
3、foreach
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>;所指定的变量中,然后再执行< text>;所包含的表达式。每一次生成一个<text>,就会返回一个字符串,循环过程中,<text>;的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>;所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
eg:
SRCDIRS := bsp/clk \
bsp/led \
bsp/delay
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
print:
@echo CFILES=$(CFILES)
打印结果 make print
bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
4、notdir
格式:
$(notdir 文件列表)
剥离文件的绝对路径,只保留文件名。
5、$(SRC:%.c=%.o)
$(SRC:%.c=%.o)
含义:将SRC变量中所有以.c结尾的文件名替换成对应的以.o结尾的文件名,然后赋回给SRC。
目标文件搜索
如果需要的文件是存在于不同的路径下(即源文件与 Makefile 文件不在同一个路径下),在编译的时候就用到了 Makefile 中为我们提供的目录搜索文件的功能。
常见的搜索的方法的主要有两种:
VPATH 是变量,使用时需要指定文件的路径,是搜索路径下所有的文件;
.
├── header
│ └── name.h
├── Makefile
└── source
├── main.cpp
└── name.cpp
加上路径搜索VPATH = source header
VPATH = source header
main: main.o name.o
g++ main.o name.o -o main
指定头文件
linux 中一般通过 “-I” (大写 i) 来指定头文件, 形式如下:
gcc -c a.c -I /home/linux/include -o a.o
Makefile 中常用写法
CFLAGS = -I /home/linux/include
myapp: *.c
gcc $(CFLAGS) -o myapp
指定库文件路径
linux 中一般通过 “-L” (大写 l) 来指定库文件的路径, 形式如下:
-L /usr/lib
Makefile 中常用写法
LDFLAGS = -L /usr/lib
链接具体的库
linux 中一般用 “-l 库名 " 来指定链接对应的库,形式如下:
-lpthread -liconv
Makefile 中常用写法
LIBS = -lpthread -liconv
项目工程管理
global.h
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
#include <stdio.h>
extern int a;
#endif
global.c
#include "global.h"
int a = 20;
fun.h
#ifndef __FUN_H__
#define __FUN_H__
#include "global.h"
extern int fun();
#endif
fun.c
#include "fun.h"
int fun(){
printf("a = %d\n",a);
}
main.c
#include "fun.h"
int main(int argc, const char *argv[]){
fun();
return 0;
}
Makefile 工程架构
创建文件夹,把对应的文件放入
简易版
1、fun中的Makefile
../obj/fun.o : fun.c
gcc -c -I ../include/ $< -o $@
2、global中的Makefile
../obj/global.o : global.c
gcc -c -I ../include/ $< -o $@
3、main中的Makefile
../obj/main.o : main.c
gcc -c -I ../include/ $< -o $@
这三个Makefile的作用都是将当前目录的.c文件编译成.o文件放到obj目录下
4、obj中的Makefile
../bin/my_exec : *.o
gcc -I ../include/ $^ -o $@
obj中的Makefile的作用是将前面生成的.o文件编译成可执行文件放到bin目录下
5、主工程架构Makefile 编写
SUB_DIR := main fun global obj
export SUB_DIR #将SUB_DIR变量导出为全局,其他文件也可以使用
all : $(SUB_DIR)
$(SUB_DIR) : MK_BIN
make -C $@ //在Makefile中,-C选项的作用是改变当前的工作目录到指定的路径
MK_BIN:
mkdir -p ./bin
clean:
rm -rf ./bin ./obj/*.o
执行上面几个目录下的Makefile
运行命令:
make #生成可执行文件
./bin/my_exec #执行文件,输出结果
make clean #清除可执行文件
工程版
Makefile 的静态模式
Makefile 的静态模式指的是一种自动编译模式,在这种模式下,我们可以很容易的定义 “多目标”
规则,让我们的规则变得更加有弹性和灵活。
格式:
<targets …> : : <prereq-patterns…>
说明:
- targets 定义了一系列目标,也就是多个目标。可以是通配符,也可以是多个目标的集合。
- target-pattern 是 targets 的模式,也就是目标集模式。可以理解
- prereq-patterns 则是目标的 “依赖” 元素, 可以理解为 %.c, 意思就是对target-pattern 中的目标进行二次定义。其操作方式为取 target-pattern 中去掉. o 后的文件名,并加上. c 形成新的集合。
示例如下:
$(OBJS) : %.o : %.c
gcc -c $< -o $@
说明:
$(OBJS)是多个. o 文件的集合。例如: fun.o global.o main.o
%.o 是取里面某个. o , 例如 fun.o
%.c 是对应取的某个. c, 例如 fun.c
1、主Makefile编写
启动各子目录下的Makefile文件
CC := gcc
INCLUDE_DIR := ../include/
OBJ_DIR := $(shell pwd)/obj
BIN_DIR := $(shell pwd)/bin
SUB_DIR := main fun global obj
TARGET := my_exec
export CC INCLUDE_DIR OBJ_DIR BIN_DIR SUB_DIR TARGET
all : $(SUB_DIR)
$(SUB_DIR) : MK_BIN
make -C $@
MK_BIN:
mkdir -p $(BIN_DIR)
clean:
rm -rf ./bin ./obj/*.o
install:
sudo cp $(BIN_DIR)/$(TARGET) /usr/bin
uninstall:
sudo rm -rf /usr/bin/$(TARGET)
2、fun、global、main文件夹使用同样的 Makefile文件
SRC :=$(wildcard *.c)
OBJ :=$(patsubst %.c,%.o,$(SRC))
all : $(OBJ)
$(OBJ) : %.o:%.c
$(CC) -c -I $(INCLUDE_DIR) $^ -o $(OBJ_DIR)/$@
3、obj中的Makefile文件
$(BIN_DIR)/$(TARGET) : *.o
$(CC) -I $(INCLUDE_DIR) $^ -o $@