18. Makefile
18.1. gcc常用选项
gcc常用选项:
-v:查看gcc编译器的版本,显示gcc执行时的详细过程
-o <file> 指定输出文件名为file,这个名称不能跟源文件名同名
-E 只预处理,不会编译、汇编、链接
-S 只编译,不会汇编、链接
-c 编译和汇编,不会链接
最简单的编译命令 gcc -o hello hello.c 输出hello
实际分为以下几步 预处理 编译 汇编 链接 ,其中【预处理 编译 汇编】统称为编译
gcc -E -o hello.i hello.c # 预处理 hello.c -》 hello.i
gcc -S -o hello.s hello.i # 编译 hello.i -》 hello.s
gcc -c -o hello.o hello.s # 编译和汇编 hello.s -》 hello.o
gcc -o hello hello.o # 链接 hello.o -》 hello
18.2. 规则
目标 : 依赖1 依赖2 ...
[TAB]命令
当”目标文件”不存在, 或某个依赖文件比目标文件”新”,则: 执行”命令”
test:a.o b.o # 目标test 依赖a.o和b.o 如果a.o或者b.o比test新,那么就执行下面的编译命令
gcc -o test a.o b.o # 命令 :链接a.o和b.o 生成 test的可执行文件
a.o : a.c # a.o又依赖a.c ,同理当a.c比a.o新的时候,执行下面的命令
gcc -c -o a.o a.c # 命令:把a.c 编译为a.o
b.o : b.c # b.o又依赖b.c ,同理当b.c比b.o新的时候,执行下面的命令
gcc -c -o b.o b.c # 命令:把b.c 编译为b.o
18.3. 语法
18.3.1. 通配符
%.o 匹配所有的.o文件
$@ 表示目标
$< 表示第1个依赖文件
$^ 表示所有依赖文件
例如
test:a.o b.o
gcc -o test a.o b.o
当上面的test查找依赖a.o和b.o的时候会往下找这些.o的依赖规则,到里发现所有的.o文件依赖所有的.c文件
%.o:%.c
gcc -c -o $@ $< 如果此时查找的是a.o 这里$@表示目标即a.o $<表示第一个依赖即即a.c
注:make 命令后面跟个目标名字,如果不跟名字,那么就找当前makefile的第一个目标
18.3.2. 假想目标: .PHONY
clean:
rm *.o
如上代码的makefile,如果同级目录下有clean的文件,我们执行 make clean的时候会怎么处理。
答案是什么都没执行,为什么?
因为:我们编译规则是目标不存在或者依赖比目标新,才会执行命令,而这里的makefile里面的clean:并没有依赖,且同级目录下有clean文件即目标存在,不满足上面说的条件,所以不会执行该命令rm *.o,解决方式就是用假想目标。
makefile修改如下即可。假想有个这样的clean目标不存在。
clean:
rm *.o
.PHONY:clean
18.3.3. 即时变量、延时变量, export
18.3.3.1. 变量定义方式
:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
+= # 附加, 它是即时变量还是延时变量取决于前面的定义
18.3.3.1.1. 情况1
A :=$(C) # 即使变量,由于此时C还没定义,所以理论上A应该等于空
B = $(C) # 延时变量,由于B在用到的时候才定义,所以等B用到的时候会搜索整个makefile,找C的值,意味着C只要在此makefile定义,那么B就取C的值,无关C定义在这个makefile的哪个地方。
all:
@echo A= $(C)
@echo B= $(C) # 此处用到了B,开始从makefile头开始搜索,找C的值。
C=123
执行make结果:
A=
B=123
18.3.3.1.2. 情况2
A :=$(C) # 即使变量,
B = $(C) # 延时变量,
C = abc
all:
@echo A= $(C)
@echo B= $(C)
C=123 #覆盖掉abc
执行make结果:
A=
B=123
18.3.3.1.3. 情况3
A :=$(C) # 即使变量,
B = $(C) # 延时变量,
C = abc
all:
@echo A= $(C)
@echo B= $(C)
C+=123 #此时C=abc123
执行make结果:
A=
B=abc123
18.3.3.1.4. 情况4
D = abc
D ?= CONGXIN #由于上面已经定义了D,所以这里不会起作用,那么D还是abc
all:
@echo D= $(D)
执行make结果:
D=abc
18.3.3.1.5. 情况5
D ?= CONGXIN
all:
@echo D= $(D)
执行:make 后结果
D=CONGXIN
执行:make D=666 后的结果如下 #通过命令行传入参数
D=666
18.3.4. 函数
$(foreach var,list,text) # 遍历list每个成员赋值给var,然后执行text
$(filter pattern...,list) # 在 list 中取出符合patten格式的值
$(filter-out pattern...,list) # 在 list 中取出不符合patten格式的值
$(wildcard pattern) # pattern定义了文件名的格式wildcard取出其中存在的文件
$(patsubst pattern,replacement,$(var)) # 遍历列表var,中取出每一个值如果符合pattern则用replacement 替换
18.3.4.1. 情况1
A = a b c
B = $(foreach f,$(A),$(f).o) #遍历A里面的每一个值赋值给f,然后执行 $(f).o ,
#例如如果当前匹配到a,那么f=a,然后执行$(f).o,实际就是 a.o
all:
@echo B= $(B)
执行make结果
B=a.o b.o c.o
18.3.4.2. 情况2
C = a b c d/
D = $(filter %/,$(C)) #取出C中符合 %/ 即是目录的项赋值给D
E = $(filter-out %/,$(C)) #取出C中不符合 %/ 即不是目录的项赋值给D
all:
@echo D= $(D)
@echo E= $(E)
执行make结果
D=d/
E=a b c
18.3.4.3. 情况3
假如 目录下有 a.c b.c d.txt 等文件
SOURCES= $(wildcard *.c) #查找当前目录下所有的.c文件,然后把名字赋值给SOURCES
all:
@echo src = $(SOURCES)
执行make结果如下:
src = a.c b.c
18.3.4.4. 情况4
files = a.c b.c 666 333
OBJS = $(patsubst %.c,%.o,$(files)) # 把files里面的所有匹配.c的文件替换为.o文件然后赋值给OBJS
all:
@echo objs = $(OBJS)
执行make后结果
objs = a.o b.o
18.3.5. 支持头文件依赖
http://blog.csdn.net/qq1452008/article/details/50855810
gcc -M c.c // 打印出依赖
gcc -M -MF c.d c.c // 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d // 编译c.o, 把依赖写入文件c.d
objs = a.o b.o c.o
dep_files := $(patsubst %,.%.d, $(objs)) # dep_files :=.a.o.d .b.o.d .c.o.d
dep_files := $(wildcard $(dep_files)) # dep_files :=上个dep_files里面正真存在的文件
CFLAGS = -Werror \ # 把警告信息当做错误输出,一般警告都隐藏着bug,所以建议加上
-I./includes \ # 添加头文件的搜索路径
test: $(objs) # 目标test依赖所 $(objs)
gcc -o test $^ # $^ 所有依赖 即 $(objs)
ifneq ($(dep_files),) # 因为第二个参数空就是NULL,所以 意思是如果dep_files!=NULL
include $(dep_files)
endif
%.o : %.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #编译目标$@依赖$< 并且把依赖写入.$@.d里面
# 举个例子假如是a.o,上面的代码就如同下面的
#a.o : a.c
# gcc $(CFLAGS) -c -o a.o a.c -MD -MF .a.o.d
clean:
rm *.o test
distclean:
rm $(dep_files) #删除所有依赖
.PHONY: clean
18.3.6. gcc选项
18.3.6.1. 编译选项CFLAGS
| 选项 | 说明 |
|---|---|
| -c | 用于把源码文件编译成 .o 对象文件,不进行链接过程 |
| -o | 用于连接生成可执行文件,在其后可以指定输出文件的名称 |
| -g | 用于在生成的目标可执行文件中,添加调试信息,可以使用GDB进行调试 |
| -Idir | 用于把新目录添加到include路径上,可以使用相对和绝对路径,“-I.”、“-I./include”、“-I/opt/include” |
| -Wall | 生成常见的所有告警信息,且停止编译,具体是哪些告警信息,请参见GCC手册,一般用这个足矣! |
| -w | 关闭所有告警信息 |
| -O | 表示编译优化选项,其后可跟优化等级0\1\2\3,默认是0,不优化 |
| -fPIC | 用于生成位置无关的代码 |
| -v | (在标准错误)显示执行编译阶段的命令,同时显示编译器驱动程序,预处理器,编译器的版本号 |
18.3.6.2. GCC链接选项LDFLAGS参数
| 选项 | 说明 |
|---|---|
| -llibrary | 链接时在标准搜索目录中寻找库文件,搜索名为liblibrary.a 或 liblibrary.so |
| -Ldir | 用于把新目录添加到库搜索路径上,可以使用相对和绝对路径,“-L.”、“-L./include”、“-L/opt/include” |
| -Wl,option | 把选项 option 传递给连接器,如果 option 中含有逗号,就在逗号处分割成多个选项 |
| -static | 使用静态库链接生成目标文件,避免使用共享库,生成目标文件会比使用动态链接库大 |
18.4. 动态和静态库
解决问题:
使用makefile生成静态库.a或者动态库.so,然后用户的app程序引用这些库文件;
如果在生成库时候,又引用了其他库文件,makefile该怎么写。
源码结构:
Demo
├── App # 用户应用程序目录
| ├── includes # 库中函数头文件目录,可以从LibProject/includes里面拷贝过来
| ├── main.c # 用户源代码
| ├── Makefile # 用户应用程序的makefile文件
| └── thirdpart # 库文件存放目录 可以从LibProject/thirdpart里面拷贝过来
|
├── LibProject # 库工程目录
├── build # 暂时没有用空的
├── common # 库工程源代码目录,里面存放着.c文件
│ └── common.c
├── driver # 库工程源代码目录,同样里面存放着.c文件
│ └── ledDrv.c
├── includes # 库工程需要开放给用户的头文件,到时候可以和库文件一起拷贝给客户
│ ├── common.h
│ └── ledDrv.h
├── lib # 库工程 需要引用的外部库,就是问题2中描述的项
│ └── libsqlite3.so
├── Makefile # 库工程makefile,控制着该库的编译方式
└── thirdpart # 库工程最终生成的库文件存放路径
├── libtest.a
└── libtest.so
18.4.1. 应用程序相关
Demo/App/main.c
#include <stdio.h>
#include <ledDrv.h>
#include <common.h>
int main(void)
{
ledDrvInit(); // 调用libtest里面的ledDrvInit()函数
commonInit(); // 调用libtest里面的commonInit()函数
return 0;
}
Demo/App/Makefile
# 设置交叉编译环境
CROSS_COMPILE_PATH=/usr/local/arm_linux_4.8/bin
CROSS_COMPILE=$(CROSS_COMPILE_PATH)/arm-linux-
CC=$(CROSS_COMPILE)gcc
# 设置编译选项,并且制定头文件路径
CFLAGS = -g \
-Wall \
-I$(CURDIR)/../LibProject/includes \
-I$(CURDIR) \
# 源码目录 是当前路径下的所有.c文件,如果还有其它源码目录的请自行模仿添加
SRCS := $(wildcard ./*.c) \
# 去掉路径后的所有.c文件
DIRS :=$(notdir $(SRCS))
# 把去掉路径后的所有.c文件转换为.o文件 也就是目标文件
OBJS := $(patsubst %.c,%.o,$(DIRS))
# 引用库路径和库名字
LIBS := -L/$(CURDIR)/../LibProject/thirdpart \ # 就是自己编译的库的路径
-lpthread\ # 系统库,不需要指定路径
-ltest # 自己编译的库名字
# 应用程序名字
APP_NAME = APP
# 编译生成自己的应用程序
all:
$(CC) $(CFLAGS) -o $(APP_NAME) $(SRCS) $(LIBS)
clean:
rm -rf *.o
rm -rf $(OBJS) $(APP_NAME)
18.4.2. 库工程相关
Demo/LibProject/common.c
#include <stdio.h> #include "common.h" void commonInit() { printf("I am common.c\r\n"); // 这里只打印一句话 }
Demo/LibProject/common/common.h
#ifndef _COMMON_H #define _COMMON_H void commonInit(); #endif
Demo/LibProject/Drver/ledDrv.c
#include <stdio.h> #include "ledDrv.h" void ledDrvInit() { printf("I am ledDrv.c\r\n"); // 这里只打印一句话 }
Demo/LibProject/Drver/ledDrv.h
#ifndef _LED_DRV_H #define _LED_DRV_H void ledDrvInit(); #endif
Demo/LibProject/Lib/libsqlite3.so
Demo/LibProject/Makefile
# 设置交叉编译环境 CROSS_COMPILE_PATH=/usr/local/arm_linux_4.8/bin CROSS_COMPILE=$(CROSS_COMPILE_PATH)/arm-linux- CC=$(CROSS_COMPILE)gcc AR=$(CROSS_COMPILE)ar # 设置需要生成库的的路径和名字 LIB_NAME ?= test LIB_PATH ?= $(CURDIR)/thirdpart STATIC_NAME ?= lib$(LIB_NAME).a SHARE_NAME ?= lib$(LIB_NAME).so # 设置编译选项,这里指定了头文件的路径 CFLAGS = -g \ -Wall \ -I$(CURDIR)/includes \ -I$(CURDIR) \ # 源码目录 是当前路径下的所有.c文件,如果还有其它源码目录的请自行模仿添加 SRCS := $(wildcard ./common/*.c) \ $(wildcard ./driver/*.c) # 去掉路径后的所有.c文件 DIRS :=$(notdir $(SRCS)) # 把去掉路径后的所有.c文件转换为.o文件 也就是目标文件 OBJS := $(patsubst %.c,%.o,$(DIRS)) # 引用库路径和库名字 LIBS := -L/$(CURDIR)/lib \ # 指定需要引用的库的路径就是libsqlite3.so路径 -lpthread\ # 系统库,不需要指定路径 -lsqlite3 # 该库工程又需要引用的库,即问题2中提到的 # 执行 make all all: static_library shared_library # 编译所有的源码,生成目标.o,不链接 $(OBJS):$(SRCS) $(CC) $(CFLAGS) -c $(SRCS) $(LIBS) # 静态库的生成方式 static_library:$(OBJS) $(AR) -cr $(STATIC_NAME) $(OBJS) # 动态库的生成方式 shared_library:$(OBJS) $(CC) -shared -fpic -o $(SHARE_NAME) $(OBJS) # 执行make install 的时候可以把生成的库文件 移动到指定的目录,这里目录是$(LIB_PATH) install: mv $(STATIC_NAME) $(LIB_PATH) mv $(SHARE_NAME) $(LIB_PATH) #执行make out的时候调试信息 out: @echo srcs = $(SRCS) @echo dir = $(DIRS) @echo libs = $(LIBS) @echo objs = $(OBJS) clean: rm -rf *.o rm -rf $(STATIC_NAME) $(SHARE_NAME)
18.4.3. 编译运行
# 1.生成库文件步骤:
# make clean
# make all
# make install
# 如下:
book@pc:~/Demo$ cd LibProject/
book@pc:~/Demo/LibProject$ make clean
rm -rf *.o
rm -rf libtest.a libtest.so
book@pc:~/Demo/LibProject$ make all
/usr/local/arm_linux_4.8/bin/arm-linux-gcc -g -Wall -I/home/book/Demo/LibProject/includes -I/home/book/Demo/LibProject -c ./common/common.c ./driver/ledDrv.c -L//home/book/Demo/LibProject/lib -lpthread -lsqlite3
/usr/local/arm_linux_4.8/bin/arm-linux-ar -cr libtest.a common.o ledDrv.o
/usr/local/arm_linux_4.8/bin/arm-linux-gcc -shared -fpic -o libtest.so common.o ledDrv.o
book@pc:~/Demo/LibProject$ make install
mv libtest.a /home/book/Demo/LibProject/thirdpart
mv libtest.so /home/book/Demo/LibProject/thirdpart
# 2.编译用户的应用程序步骤:
# make clean
# make all
# 如下:
book@pc:~/Demo/LibProject$ ls thirdpart/
libtest.a libtest.so
book@pc:~/Demo/LibProject$ cd ../App/
book@pc:~/Demo/App$ ls
includes main.c Makefile thirdpart
book@pc:~/Demo/App$ make clean
rm -rf *.o
rm -rf main.o APP
book@pc:~/Demo/App$ make
/usr/local/arm_linux_4.8/bin/arm-linux-gcc -g -Wall -I/home/book/Demo/App/../LibProject/includes -I/home/book/Demo/App -o APP ./main.c -L//home/book/Demo/App/../LibProject/thirdpart -lpthread -ltest
book@pc:~/Demo/App$ ls
APP includes main.c Makefile thirdpart
book@pc:~/Demo/App$
最总把生成应用程序拷贝到开发板运行如下:
root@arm: sudo chmod +x ./APP
root@arm: ./APP
I am ledDrv.c
I am common.c