• Linux应用开发基础知识——Makefile 的使用(二)


    前言:

    Linux 中使用 make 命令来编译程序,特别是大程序;而 make 命令所执 行的动作依赖于 Makefile 文件。最简单的 Makefile 文件: hello: hello.c 、gcc -o hello hello.c 、clean:、 rm -f hello 将上述 4 行存为 Makefile 文件,放入 01_hello 目录下,然后直接执行 make 命令即可编译程序,执行 “make clean”即可清除编译出来的结果。 make 命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率。目前我们只需要会使用Makefile即可,等后面学到驱动更底层的知识时候再详细认识Makefile。

    一、Makefile 规则与示例 

    规则

    建立两个文件

    (1)a.c

    1. #include
    2. int main()
    3. {
    4. func_b();
    5. return 0;
    6. }

    (2)b.c

    1. #include
    2. void func_b()
    3. {
    4. printf("This is B\n");
    5. }

    (3)Makefile

    1. test:a.o b.o
    2. gcc -o test a.o b.o
    3. a.o : a.c
    4. gcc -c -o a.o a.c
    5. b.o : b.c
    6. gcc -c -o b.o b.c

    book@100ask:~/source/04_2018_Makefile/001_test_app$ gcc -o test a.c b.c
    

            这样编译会全部编译步骤进行一下,如果每次这样编译会大大降低效率,应该改哪步进行编译哪步,这样才能更好的提高编译效率,所以我们就要学习makefile的规则,一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:

    目标(target)… : 依赖(prerequiries)…

    命令(command)

    如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目 标文件”。

    命令被执行的 2 个条件:依赖文件比目标文件,或是目标文件还没生成。 

    二、Makefile的语法

    1.通配符: %.o

    $@ 表示目标

    $<  表示第1个依赖文件

    $^   表示所有依赖文件

    1. test: a.o b.o c.o
    2. gcc -o test $^
    3. %.o : %.c
    4. gcc -c -o $@ $<

     2.假想目标:.PHONY

    Makefile 

    1. test: a.o b.o c.o
    2. gcc -o test $^
    3. %.o : %.c
    4. gcc -c -o $@ $<
    5. clean:
    6. rm *.o test
    7. .PHONY: clean

    3.即时变量、延时变量,export

    简单变量(即时放量):

    A := xxx        #    A的值即刻确定,在定义时即确定

    B = xxx           #    B的值使用到时才确定

    :=        #即时变量
    =         #延时变量
    ?=       #延时变量,如果是第1次定义才起效,如果在前面该变量已定义则忽略这句

    +=        #附加,它是即时变量还是延时变量取决于前面的定义

    Makefile  

    1. A := $(C)
    2. B = $(C)
    3. C = abc
    4. #D = 100ask
    5. D ?= weidongshan
    6. all:
    7. @echo A = $(A)
    8. @echo B = $(B)
    9. @echo D = $(D)
    10. C += 123

    三、Makefile 的函数

    $(foreach var,list,text)

    简单地说,就是 for each var in list, change it to text。

    对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述 的形式。

    1. A = a b c
    2. B = $(foreach f, $(A), $(f).o)
    3. all:
    4. @echo B = $(B)

     $(wildcard pattern)

     pattern 所列出的文件是否存在,把存在的文件都列出来。

    $(filter pattern. ..,text)                    #在text中取出符合patten格式的值

    $(filter-out pattern.. . ,text)            #在text中取出不符合patten格式的值

    1. C = a b c d/
    2. D = $(filter %/, $(C))
    3. E = $(filter-out %/, $(C))
    4. all:
    5. @echo D = $(D)
    6. @echo E = $(E)

    $(wildcard pattern)

    pattern定义了文件名的格式,wildcard取出其中存在的文件

    1. files = $(wildcard *.c) #列出符合后缀是.c的文件都有哪些
    2. all:
    3. @echo files = $(files)

    1. files2 = a.c b.c c.c d.c e.c
    2. files3 = $(wildcard $(files2))
    3. all:
    4. @echo files = $(files)
    5. @echo files3 = $(files3)

     $(patsubst pattern,replacement,$(files2))

    从列表中取出每——个值,如果符合pattern,则替换为replacement
     

    1. files2 = a.c b.c c.c d.c e.c
    2. files3 = $(wildcard $(files2))
    3. dep_files = $(patsubst %.c,%.d,$(files2)) #将files2中.c后缀文件替换为.d后缀文件
    4. all:
    5. @echo dep_files = $(dep_files)

     所有Makefile 函数代码

    1. A = a b c
    2. B = $(foreach f, $(A), $(f).o)
    3. C = a b c d/
    4. D = $(filter %/, $(C))
    5. E = $(filter-out %/, $(C))
    6. files = $(wildcard *.c)
    7. files2 = a.c b.c c.c d.c e.c abc
    8. files3 = $(wildcard $(files2))
    9. dep_files = $(patsubst %.c,%.d,$(files2))
    10. all:
    11. @echo B = $(B)
    12. @echo D = $(D)
    13. @echo E = $(E)
    14. @echo files = $(files)
    15. @echo files3 = $(files3)
    16. @echo dep_files = $(dep_files)

    四、Makefile实例

    1.支持头文件依赖

    a.c

    1. #include
    2. void func_b();
    3. void func_c();
    4. int main()
    5. {
    6. func_b();
    7. func_c();
    8. return 0;
    9. }

     b.c

    1. #include
    2. void func_b()
    3. {
    4. printf("This is B\n");
    5. }

    c.c

    1. #include
    2. #include "c.h"
    3. void func_c()
    4. {
    5. printf("This is C = %d\n", C);
    6. }

     c.h

    1. #define C 4

    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

    Makefile 

    1. objs = a.o b.o c.o
    2. dep_files := $(patsubst %,.%.d, $(objs))
    3. dep_files := $(wildcard $(dep_files))
    4. test: $(objs)
    5. gcc -o test $^
    6. ifneq ($(dep_files),) #将依赖文件添加进去
    7. include $(dep_files)
    8. endif
    9. %.o : %.c
    10. gcc -c -o $@ $< -MD -MF .$@.d #自动生成依赖文件
    11. clean:
    12. rm *.o test
    13. distclean:
    14. rm $(dep_files)
    15. .PHONY: clean

     2.添加CFLAGS

    c.c

    1. #include
    2. #include
    3. void func_c()
    4. {
    5. printf("This is C = %d\n", C);
    6. }

    CFLAGS = -Werror -I.include         #-Werror将所有的警告变为错误
                                                           #-I.include执行当前目录下的include文件

    %.o : %.c
            gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d       #自动生成依赖文件
     

    Makefile  

    1. objs = a.o b.o c.o
    2. dep_files := $(patsubst %,.%.d, $(objs))
    3. dep_files := $(wildcard $(dep_files))
    4. CFLAGS = -Werror -I.include #-Werror将所有的警告变为错误
    5. #-I.include执行当前目录下的include文件
    6. test: $(objs)
    7. gcc -o test $^
    8. ifneq ($(dep_files),) #将依赖文件添加进去
    9. include $(dep_files)
    10. endif
    11. %.o : %.c
    12. gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #自动生成依赖文件
    13. clean:
    14. rm *.o test
    15. distclean:
    16. rm $(dep_files)
    17. .PHONY: clean


    五、Makefile的使用

    参考 Linux 内核的 Makefile 编写了一个通用的 Makefile,它可以用来 编译应用程序:

    1.支持多个目录、多层目录、多个文件;

    2.支持给所有文件设置编译选项;

    3.支持给某个目录设置编译选项;

    4.支持给某个文件单独设置编译选项;

    5.简单、好用。

    说明:

    本程序的Makefile分为3类:
    1. 顶层目录的Makefile
    2. 顶层目录的Makefile.build
    3. 各级子目录的Makefile

    一、各级子目录的Makefile:
       它最简单,形式如下:

    EXTRA_CFLAGS  := 
    CFLAGS_file.o := 

    obj-y += file.o
    obj-y += subdir/
       
       "obj-y += file.o"  表示把当前目录下的file.c编进程序里,
       "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
       "EXTRA_CFLAGS",    它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
       "CFLAGS_xxx.o",    它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置

    注意: 
    1. "subdir/"中的斜杠"/"不可省略
    2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
    3. CFLAGS  EXTRA_CFLAGS  CFLAGS_xxx.o 三者组成xxx.c的编译选项

    二、顶层目录的Makefile:
       它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
       主要是定义工具链前缀CROSS_COMPILE,
       定义编译参数CFLAGS,
       定义链接参数LDFLAGS,
       这些参数就是文件中用export导出的各变量。

    三、顶层目录的Makefile.build:
       这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o

    四、怎么使用这套Makefile:
    1.把顶层Makefile, Makefile.build放入程序的顶层目录
       在各自子目录创建一个空白的Makefile

    2.确定编译哪些源文件
       修改顶层目录和各自子目录Makefile的obj-y
        obj-y += xxx.o
        obj-y += yyy/

        这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录    

    3. 确定编译选项、链接选项
       修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项;
       修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项;
       
       修改各自子目录下的Makefile:
       "EXTRA_CFLAGS",    它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
       "CFLAGS_xxx.o",    它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
       
    4. 使用哪个编译器?
       修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-)
       
    5. 确定应用程序的名字:
       修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字

    6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除

    1.顶层目录

     Makefile

    1. CROSS_COMPILE =
    2. AS = $(CROSS_COMPILE)as
    3. LD = $(CROSS_COMPILE)ld
    4. CC = $(CROSS_COMPILE)gcc
    5. CPP = $(CC) -E
    6. AR = $(CROSS_COMPILE)ar
    7. NM = $(CROSS_COMPILE)nm
    8. STRIP = $(CROSS_COMPILE)strip
    9. OBJCOPY = $(CROSS_COMPILE)objcopy
    10. OBJDUMP = $(CROSS_COMPILE)objdump
    11. export AS LD CC CPP AR NM
    12. export STRIP OBJCOPY OBJDUMP
    13. CFLAGS := -Wall -O2 -g
    14. CFLAGS += -I $(shell pwd)/include
    15. LDFLAGS :=
    16. export CFLAGS LDFLAGS
    17. TOPDIR := $(shell pwd)
    18. export TOPDIR
    19. TARGET := test
    20. obj-y += main.o
    21. obj-y += sub.o
    22. obj-y += a/
    23. all : start_recursive_build $(TARGET)
    24. @echo $(TARGET) has been built!
    25. start_recursive_build:
    26. make -C ./ -f $(TOPDIR)/Makefile.build
    27. $(TARGET) : start_recursive_build
    28. $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
    29. clean:
    30. rm -f $(shell find -name "*.o")
    31. rm -f $(TARGET)
    32. distclean:
    33. rm -f $(shell find -name "*.o")
    34. rm -f $(shell find -name "*.d")
    35. rm -f $(TARGET)

    Makefile.build

    1. PHONY := __build
    2. __build:
    3. obj-y :=
    4. subdir-y :=
    5. EXTRA_CFLAGS :=
    6. include Makefile
    7. # obj-y := a.o b.o c/ d/
    8. # $(filter %/, $(obj-y)) : c/ d/
    9. # __subdir-y : c d
    10. # subdir-y : c d
    11. __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
    12. subdir-y += $(__subdir-y)
    13. # c/built-in.o d/built-in.o
    14. subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
    15. # a.o b.o
    16. cur_objs := $(filter-out %/, $(obj-y))
    17. dep_files := $(foreach f,$(cur_objs),.$(f).d)
    18. dep_files := $(wildcard $(dep_files))
    19. ifneq ($(dep_files),)
    20. include $(dep_files)
    21. endif
    22. PHONY += $(subdir-y)
    23. __build : $(subdir-y) built-in.o
    24. $(subdir-y):
    25. make -C $@ -f $(TOPDIR)/Makefile.build
    26. built-in.o : $(subdir-y) $(cur_objs)
    27. $(LD) -r -o $@ $(cur_objs) $(subdir_objs)
    28. dep_file = .$@.d
    29. %.o : %.c
    30. $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
    31. .PHONY : $(PHONY)

    2.子目录 

    1. EXTRA_CFLAGS := -D DEBUG
    2. CFLAGS_sub3.o := -D DEBUG_SUB3
    3. obj-y += sub2.o
    4. obj-y += sub3.o

     六、通用 Makefile 的解析

    通用 Makefile 的设计思想

    在 Makefile 文件中确定要编译的文件、目录,比如:

    1. obj-y += main.o
    2. obj-y += a/

    “Makefile”文件总是被“Makefile.build”包含的。

    在 Makefile.build 中设置编译规则,有 3 条编译规则:

    怎么编译子目录? 进入子目录编译:

    1. $(subdir-y):
    2. make -C $@ -f $(TOPDIR)/Makefile.build

    怎么编译当前目录中的文件?

    1. %.o : %.c
    2. $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

    当前目录下的.o 和子目录下的 built-in.o 要打包起来:

    1. built-in.o : $(cur_objs) $(subdir_objs)
    2. $(LD) -r -o $@ $^

    顶层 Makefile 中把顶层目录的 built-in.o 链接成 APP:

    1. $(TARGET) : built-in.o
    2. $(CC) $(LDFLAGS) -o $(TARGET) built-in.o

    情景演绎

     

  • 相关阅读:
    SpringBoot -集成Druid
    自动化测试:为什么需要框架
    浅谈shadow dom
    实现高并发Web服务器(C语言版)
    docker安装sql-server数据库,使用navicat实现备份数据库导入
    大数据之LibrA数据库常见术语(八)
    C语言学习笔记 —— 转换函数
    python游戏开始界面
    salesforce零基础学习(一百三十)Report 学习进阶篇
    见证国内人工智能与机器人技术的进步
  • 原文地址:https://blog.csdn.net/m0_63168877/article/details/134230726