• 【linux】Makefile简要知识+一个通用Makefile



    Makefile

     meke命令一般用于编译程序,而make命令都依赖于 Makefile 文件。
     最简单的Makefile如下:

    hello:  hello.c
       gcc -o hello hello.c
    clean:
       rm -f hello
    

    注:缩进使用 tab 键 , 因为Makefile 会为每个以 tab 键开头的命令创建一个 shell 去执行。
     具体规则参考 《GUN Make 使用手册》

    以下为粗略笔记

    Makefile规则与示例

    为什么需要Makefile

     提高编译效率。

    Makefile样式

     一个简单的Makefile文件包含一系列"规则":

    目标...:依赖...
    <tab>命令
    
    • 命令被执行的两个简单条件:
      • 目标文件还没有生成
      • 依赖文件比目标文件新

    先介绍Makefile的两个函数

    1. $(foreach var,list,text)
       for each var in list,change it to text。
       对list中的每一个元素,取出赋值给var,然后把var改为text所描述的形式。
      例子:
    objs := a.o b.o
    dep_files := $(foreach f, $(objs), .$(f).d)  // 最终 dep_files := .a.o.d .b.o.d
    
    1. $(wildcard pattern)
        把存在的 pattern 文件列出来
      例子:
    src_files := $(wildcard *.c)
    

    完善Makefile

    1. 先来一个简单粗暴、效率低的:
    test: main.c hello.c hello.h
            gcc -o test main.c hello.c
    
    1. 再来一个Makefile,效率高、精炼,支持自动检测头文件
    objs := main.o hello.o
    
    test : $(objs)
        gcc -o test $^
    
    # 需要判断是否存在依赖文件
    # .main.o.d   .hello.o.d
    dep_files := $(foreach f, $(objs), .$(f).d)
    dep_files := $(wildcard $(dep_files))
    
    # 把依赖文件包含进来
    ifneq ($(dep_files),)
    include $(dep_files)
    endif
    
    %.o : %.c
        gcc -Wp,-MD,.$@.d -c -o $@ $<
    
    clean:
        rm *.o test -f
    
    distclean:
        rm $(dep_files) *.o test -f
    

    了解自动化变量。

    通用Makefile的使用

    参考linux内核的Makefile来编写一个通用的Makefile,特点:

    1. 支持多个目录、多层目录、多个文件;
    2. 支持给所有文件设置编译选项;
    3. 支持给目录设置编译选项;
    4. 支持给某个文件单独设置编译选项;
    5. 简单、好用。

    通用的Makefile解释

    零星知识点
    make命令的使用

     执行make命令时,它会去当前目录下查找名为Makefile的文件,并根据它的只是去执行操作,生成第一个目标。
     也可以用 -f 选项指定文件,如:

    make -f Makefile.build
    

    指定文件的名字随意定义


     可以使用 -C 指定目录,切换到其他目录去,如:

    make   -C   a/   abc.def
    

     可以指定目标,不再默认生成第一个目录,如:

    make   -C   a/   abc.def   other_target
    
    即时变量与临时变量

     变量定义语法:

    形式 说明
    A = xxx 延时变量
    B ?= xxx 延时变量,只有第一次定义时赋值才成功,若曾被定义过,则此赋值无效。
    C := xxx 立即变量
    D += yyy 如果D在前面是延时变量,那么现在它还是延时变量
    如果D在前面是立即变量,那么它现在还是立即变量

    延时变量
     使用时才确定该值。
    如:

    A = $@
    tets:
        @echo $A
    

    输出 A 的值为 test
    即时变量
     定义时立即确定该值。
    如:

    A := $@
    tets:
        @echo $A
    

    输出 A 的值为

    变量的导出(export)

    export 是供给子目录的 Makefile 使用的(即 sub-make),同一级的makefile是访问不到的。
     可以通过makefile中的内置变量MAKELEVEL可以查看当前的makefile的level。

    Makefile中的shell

     Makefile中可以使用shell命令,如:

    TOPDIR := $(shell_pwd)
    
    Makefile中放置第一个命令

     执行目标时,如果不指定目标,则默认执行第一个目标。
    所以,第一个目标的位置很重要。有时候不太方便把第一个目标完整地放在文件的前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖和命令。比如:

    First_target:      // 这句话放在前面
    ........               // 其他代码,比如include其它文件得到后的 xxx 变量
    First_target : $(xxx) $(xxx)
        command
    
    假想目标

    Makefile中可能会有这样的目标:

    clean:
        rm     -f     $(shell find -name "*.o")
        rm    -f      $(TARGET)
    

    :如果当前目录下更好有个名为“clean”的文件,那么执行“make clean”时就不会执行makefile中目标clean的内容,所以要把clean设置为假想目标即可:

    .PHONY    :   clean
    
    小知识

    PHONY 是 phoney,即伪造的意思。PHONY后面的target是伪造的target,不是真实存在的文件。同时,注意make命令中后面的target默认是文件。

    常用函数
    • $(foreach var, list, text)
      for each var in list,change it to text.
      直接上例子:
    objs := a.o b.o
    dep_files := $(foreach f, $(objs), .$(f).d)   // dep_files 的最终结果为“**.a.o.d .b.o.d**”
    

    • $(wildcard pattern)
      列出 pattern 存在的文件。
      例子:
    src_files = $(wildcard *.c) // src_files的值为当前目录下所有 .c 文件。
    

    • $(filter pattern..., text)
      text中符合pattern格式的内容留下来
    obj-y := a.o b.o c/ d/
    DIR := $(filter %/, $(obj-y))   //结果为:c/ d/
    

    • $(filter-out pattern..., text)
      text中符合pattern格式的内容删除掉
    obj-y := a.o b.o c/ d/
    DIR := $(filter-out %/, $(obj-y))   // 结果为:a.o b.o
    

    • $(patsubst pattern, replacement, text)
      寻找text中符合pattern格式的内容,用replacement代替他们。
    subdir-y := a.o b.o c/ d/
    subdir := $(patsubst %/, %, $(obj-y))   // 结果为:c d
    

    *通用Makefile设计思想
    1. 在Makefile文件中确定要编译的文件、目录,比如:
    obj-y += main.o
    obj-y += a/
    

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

    1. 在 Makefile.build 中设置编译规则,有 3 条编译规则
      1. 怎么编译子目录?
        1. 进入子目录编译即可:
    $(subdir-y):
        make -C $@ -f $(TOPDIR)/Makefile.build
    
    2. 如何编译当前目录中的目标文件?
    
    %.o : %.c
        $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
    
    3. 当前目录下的 **.o** 和子目录下的 **built-in.o** 要打包起来:
    
    $(TARGET) : built-in.o
        $(CC) $(LDFLAGS) -o $(TARGET) built-in.o
    

    思想总结

    1. Makefile文件确定要编译的文件和目录
    2. Makefile.build文件包含规则
    3. 每个需要编译的子目录都放个Makefile文件,把需要编译的文件编译成built-in.o
    4. 再把所有子目录的built-in.o链接到顶层的built-in.o
    5. 最后把顶层built-in.o链接到APP

    最好重新分析一下通用Makefile

    一个通用Makefile

    本程序的Makefile分为3类:

    1. 顶层目录的Makefile
    2. 顶层目录的Makefile.build
    3. 各级子目录的Makefile

    源码(注释版)

    Makefile

    CROSS_COMPILE = # 交叉编译工具头,如:arm-linux-gnueabihf-
    AS      = $(CROSS_COMPILE)as # 把汇编文件生成目标文件
    LD      = $(CROSS_COMPILE)ld # 链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者一个可执行文件
    CC      = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
    CPP    = $(CC) -E
    AR      = $(CROSS_COMPILE)ar # 打包器,用于库操作,可以通过该工具从一个库中删除或则增加目标代码模块
    NM     = $(CROSS_COMPILE)nm # 查看静态库文件中的符号表
    
    STRIP       = $(CROSS_COMPILE)strip # 以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码
    OBJCOPY  = $(CROSS_COMPILE)objcopy # 复制一个目标文件的内容到另一个文件中,可用于不同源文件之间的格式转换
    OBJDUMP = $(CROSS_COMPILE)objdump # 查看静态库或则动态库的签名方法
    
    # 共享到sub-Makefile
    export AS LD CC CPP AR NM
    export STRIP OBJCOPY OBJDUMP
    
    # -Wall : 允许发出 GCC 提供的所有有用的报警信息
    # -O2 : “-On”优化等级
    # -g : 在可执行程序中包含标准调试信息
    # -I : 指定头文件路径(可多个)
    CFLAGS := -Wall -O2 -g 
    CFLAGS += -I $(shell pwd)/include
    
    # LDFLAGS是告诉链接器从哪里寻找库文件,这在本Makefile是链接最后应用程序时的链接选项。
    LDFLAGS := 
    
    # 共享到sub-Makefile
    export CFLAGS LDFLAGS
    
    # 顶层路径
    TOPDIR := $(shell pwd)
    export TOPDIR
    
    # 最终目标
    TARGET := test
    
    # 本次整个编译需要源 文件 和 目录
    # 这里的“obj-y”是自己定义的一个格式,和“STRIP”这些一样,*但是 一般内核会搜集 ”obj-”的变量*
    obj-y += main.o # 需要把当前目录下的 main.c 编进程序里
    obj-y += sub.o # 需要把当前目录下的 sub.c 编进程序里
    obj-y += subdir/ # 需要进入 subdir 这个子目录去寻找文件来编进程序里,具体是哪些文件,由 subdir 目录下的 Makefile 决定。
    #obj-y += $(patsubst %.c,%.o,$(shell ls *.c))
    
    # 第一个目标
    all : start_recursive_build $(TARGET)
        @echo $(TARGET) has been built !
        
    # 处理第一个依赖,**转到 Makefile.build 执行**
    start_recursive_build:
        make -C ./ -f $(TOPDIR)/Makefile.build
        
    # 处理最终目标,把前期处理得出的 built-in.o 用上
    $(TARGET) : built-in.o
        $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
        
    # 清理
    clean:
        rm -f $(shell find -name "*.o")
        rm -f $(TARGET)
        
    # 彻底清理
    distclean:
        rm -f $(shell find -name "*.o")
        rm -f $(shell find -name "*.d")
        rm -f $(TARGET)
    

    Makefile.build

    注意,include 命令,相对路径为 执行本 Makefile.build 的路径

    # 伪目标
    PHONY := __build
    __build:
    
    # 清空需要的变量
    obj-y :=
    subdir-y :=
    EXTRA_CFLAGS :=
    
    # 包含同级目录Makefile
    # 这里要注意,相对路径为 执行本 Makefile.build 的路径
    include Makefile
    
    # 获取当前 Makefile 需要编译的子目录的目录名
    # obj-y := a.o b.o c/ d/
    # $(filter %/, $(obj-y))   : c/ d/
    # __subdir-y  : c d
    # subdir-y    : c d
    __subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
    subdir-y	+= $(__subdir-y)
    
    # 把子目录的目标定为以下注释
    # built-in.o d/built-in.o
    subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
    
    # 获取当前目录需要编进程序的文件名作为,并写为目标
    # a.o b.o
    cur_objs := $(filter-out %/, $(obj-y))
    # 使修改头文件 .h 后,重新make后可以重新编译(重要)
    dep_files := $(foreach f,$(cur_objs),.$(f).d)
    # 列出存在的文件
    dep_files := $(wildcard $(dep_files))
    
    ifneq ($(dep_files),)
      include $(dep_files)
    endif
    
    
    PHONY += $(subdir-y)
    
    # 第一个目标
    __build : $(subdir-y) built-in.o
    
    # 优先编译 子目录的内容
    $(subdir-y):
    	make -C $@ -f $(TOPDIR)/Makefile.build
    
    # 链接成 目标
    built-in.o : $(cur_objs) $(subdir_objs)
    	$(LD) -r -o $@ $^
    
    dep_file = .$@.d
    
    # 生成 cur_objs 目标
    %.o : %.c
    	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
    	
    .PHONY : $(PHONY)
    

    使用说明

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

    1. 各级子目录的Makefile

    • 参考:
    # 可以不添加下面两个变量
    EXTRA_CFLAGS  := 
    CFLAGS_test.o := 
    
    obj-y += test.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的编译选项

    2. 顶层目录的Makefile

    主要作用:

    • 整个工程的参数初期定义
      • 架构
      • 工具链
      • 编译工具
      • 编译参数
      • 需要导出的变量
      • 等等
    • 定义最终目标
    • 跳转到 Makefile.build

    3. 顶层目录的Makefile.build **

    注意:该文件虽然放在顶层,但是也是提供给各级子目录使用的。
    主要功能:

    • 把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,把各个subdir/built-in.o和当前目录的目标 .o 合并打包为当前目录的built-in.o

    使用提示

    • 执行"make"来编译,执行 make clean 来清除,执行 make distclean 来彻底清除。
    Makefile附件
    一些符号
    符号 说明
    $@ 表示规则中的目标文件集
    $% 当目标为函数库的时候,则表示规则中的目标成员名。反之为空。如一个目标为"foo.a(bar.o)",那么,"$%"就是"bar.o",以空格分隔开。
    $< 依赖文件集合中的第一个文件,如果依赖文件以"%"形式出现,则表示符合模式的一系列文件集合
    $? 所有比目标新的依赖集合,以空格分隔开。
    $^ 所有依赖文件集合,以空格分隔开。如果依赖有相同,则取其一。
    $+ 和 "$^"类同,但是不会把相同的删除掉。
    $* 这个变量表示目标模式中 "%"及其之前的部分,如果目标是 test/a.test.c,目标模式为 a.%.c, 那么 "$* " 就是 test/a.test。

    参考

    • 以上笔记为学习和整理韦东山老师资料而来的
  • 相关阅读:
    Request
    HTTP
    mysql递归查询函数
    redis 6.0.9配置文件详解
    java对数据进行加密、解密
    java Base64编码、解码
    nginx基础使用
    linux指令笔记
    Spring 常用注解粗陋看法
    docker 已有容器修改容器配置
  • 原文地址:https://www.cnblogs.com/lizhuming/p/13956017.html
Copyright © 2020-2023  润新知