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.aliblibrary.so
-Ldir 用于把新目录添加到库搜索路径上,可以使用相对和绝对路径,“-L.”、“-L./include”、“-L/opt/include”
-Wl,option 把选项 option 传递给连接器,如果 option 中含有逗号,就在逗号处分割成多个选项
-static 使用静态库链接生成目标文件,避免使用共享库,生成目标文件会比使用动态链接库大

18.4. 动态和静态库

解决问题:

  1. 使用makefile生成静态库.a或者动态库.so,然后用户的app程序引用这些库文件;

  2. 如果在生成库时候,又引用了其他库文件,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. 应用程序相关

  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;
}
  1. 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. 库工程相关

  1. Demo/LibProject/common.c

    #include <stdio.h>
    #include "common.h"
    void commonInit()
    {
        printf("I am common.c\r\n");  // 这里只打印一句话
    }
    
  2. Demo/LibProject/common/common.h

    #ifndef _COMMON_H
    #define _COMMON_H
    void commonInit();
    #endif
    
  3. Demo/LibProject/Drver/ledDrv.c

    #include <stdio.h>
    #include "ledDrv.h"
    void ledDrvInit()
    {
        printf("I am ledDrv.c\r\n"); // 这里只打印一句话
    }
    
  4. Demo/LibProject/Drver/ledDrv.h

    #ifndef _LED_DRV_H
    #define _LED_DRV_H
    void ledDrvInit();
    #endif 
    
  5. Demo/LibProject/Lib/libsqlite3.so

  6. 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

该工程托管路径