依赖规则定义

本文详细解析了Makefile的工作流程,包括规则定义、目标、依赖、指令、通配符、变量、搜索路径、隐式规则、静态匹配规则等方面。重点介绍了如何编译更新目标文件,如.exe、.lib、.so、.dll,并探讨了make工具的灵活性,如自定义目标、默认目标选择、通配符展开及prerequisite的搜索策略。同时,文章还涉及了GCC的编译选项用于自动生成依赖文件,以及隐式规则和静态匹配规则在项目构建中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 规则

    • 前景

      • 场景分析

        • leader上传了代码,svn,git下载了代码,然后重新编译生成可执行文件.
        • 过程简单,但是为什么会更新呢,更新哪些呢?
      • 三个要素

        • 目标:编译更新或生成的最终产物,exe,lib,so,dll都可能.
        • 依赖:用于生成exe,lib,so,dll的文件或文件夹.
        • 指令:需要执行什么指令来生成目标.
      • 开发

        • 变量:尽量少的编辑,一改全改.
        • 函数:重复使用的代码抽象成函数,功能简单.
      • make的编译三要素

        • target:表示最终要生成更新的.
        • prerequisite:target依赖的文件或文件夹,在生成前,需要准备好这些是最新的.
        • recipe:生成target需要的指令.
      • make与开发

        • 变量:有变量引用和函数引用.
        • 函数:rule规则.
    • 默认target

      • 自定义target

        • make target1 target2,在命令行的参数选项中声明的若干个target.
        • target的处理按照顺序,targetmakefile中一般有显式定义.(搜不到就说明隐式生成)
      • 不指定target

        • 也就是执行make的时候没有声明target,make就使用默认target进行处理.
        • 变量.DEFAULT_GOAL的值表示默认target,如果为空,进行下面的步骤进行选.
      • 选默认

        • 从上往下第一个rule
          • rule不能是正则匹配.
          • rule不能以.开头(.PHONY,.SECONDEXPANSION这种),到可以是./a.out这种.
        • 特殊rule
          • rule中包含多个target,从做往右数第一个.
      • 常见

        • 编译一个target:make a.out
        • 编译多个all:make all
    • 语法解析

      • 常见格式

        targets : prerequisites
           recipe
        
        targets : prerequisites ; recipe
           recipe
        
      • targets

        • 内容
          • 一个或多个文件名.
        • 格式
          • 可以包含变量,可以包含shell通配符.
          • 可以是a(m)格式,表示静态库(xx.a)的成员之一(.a成员包含了文件信息).
        • 需要编译
          • prerequisite旧就需要更新.
          • 本身不存在也要编译.
      • recipe

        • 特征
          • 所在行第一个字符等于变量.RECIPEPREFIX.
        • 特殊
          • 第一个字符.RECIPEPREFIX,是否算入recipe要看是否在rule上下文中.
          • 第二格式的行首就不是.RECIPEPREFIX,这种格式也特殊.
        • 处理

          • 原封不动的打包交给SHELL对应的指令处理.默认SHELL=/usr/bin/bash
      • prerequisite

        • 说明
          • target的依赖文件或文件夹.用于判断target是否需要更新.
        • $符号
          • 常规扩张用$$表示.
          • 二次扩张用$$$$表示.
          • 参考
        • 逻辑行
          • 可以用行尾添加\的方式将一个很长的行拆分成多行.
          • 但是意义不变.
        • 作用
          • 挨个和target比较,任何一个比target新,就说明target需要重新生成.
          • 但是在处理target之前需要处理每一个prerequisite,这些也要保证最新.
          • 也要执行同样的rule处理,每一个prerequisite都会进行rule处理.
          • 可能makefile中没有定义对应规则,但是还有内置隐式规则.
        • 比较方式

          • 时间戳last modify time.
      • 模型

        
        class Target:
           def __init__(self,name,prerequisite,recipe):
               self.name = name
               self.ltime = ltime(self.name)
               self.prerequisites = prerequisite;
               self.recipe = recipe
        
        def update(target)
           newer=False
           if not exists(target):
               target.ltime = 0
           else:
               target.ltime = fileinfo(target.name).ltime
        
           for file in target.prerequisites:
               if update(file) < target.ltime:
                   newer = True
        
           if newer:
               target.recipe()
               return timestamp()
           else:
               return 0
        
        • 伪代码
  2. 两类prerequisite

    • 前言

      • 两类

        • normal
        • order-only
      • normal

        • 需要比较,用于更新生成的.
      • order-only

        • 对这类的prerequisite进行更新检测,相当于额外的目标.
        • 但是这类的prerequisite是否更新对target没有影响.
    • 使用

      • 包含order-only的处理模型

        
        class Target:
           def __init__(self,name,prerequisite,order_only,recipe):
               self.name = name
               self.ltime = ltime(self.name)
               self.prerequisites = prerequisite;
               self.order_only = order_only
               self.recipe = recipe
        
        def update(target)
           newer=False
           if not exists(target):
               target.ltime = 0
           else:
               target.ltime = fileinfo(target.name).ltime
        
           for file in target.prerequisites:
               if update(file) < target.ltime:
                   newer = True
        
           for file in target.order_only:
               update(file)
        
           if newer:
               target.recipe()
               return timestamp()
           else:
               return 0
        
      • order-only使用

        • 这类prerequisite一般都是用来保证生成前的某些东西是存在的,或者生成前执行某些操作.
        • 比如生成前Bin目录要存在,不存在就创建.
      • 格式

        targets : normal-prerequisites | order-only-prerequisites
        
      • 格式说明

        • 左边是普通的,右边是order-only的.
        • 两边都有,则以左边为准,左边的已经包含了更新和存在检测,是order-only的超集.
        • 两个分别在不同的处理集合中.
        • 处理顺序是:先处理normal再处理order-only.order-onlynormal交叉出现也是normal先处理.
  3. 通配符

    • 说明

      • 符号

        • shell一样格式.
        • *,?,[...]表示通配符.
      • 特殊

        • ~在单词第一个表示特殊含义,和shell差不多.
        • ~user/dir表示切换到user用户的目录下的dir.
        • ~/dir表示当前用户的目录下的dir
    • 扩张

      • 特定上下文

        • target,prerequisite中才会扩张.recipe中也是原意字符,原封不动的传递给SHELL.
        • 其他位置仅仅是符号而已.即在变量定义中都是普通字符.
      • 类似函数

        • $(wildcard)
    • 问题

      • 规则

        objects = *.o
        foo : $(objects)
           cc -o foo $(CFLAGS) $(objects)
        
      • 分析

        • 如果当前目录下的.o文件一个也没有,那么就原字符串保留.
        • 也就是不进行替换.原模原样.
        • 这就导致了*.o不存在出错.
      • 优化

        • 一般来说是使用函数.cpp转化为.o.
        • 也就是每一个都cpp文件都应该 有对应的.o文件.
      • 优化

        objects = $(patsubst %.cpp,%.o,$(wildcard *.cpp))
        foo : $(objects)
           cc -o foo $(CFLAGS) $(objects)
        
    • 函数wildcard

      • 说明

        • 前面介绍了虽然可以使用通配符,但是只有在target,prerequisite中才会扩张.
        • 但是简单的扩张是会出错了,所以了wildcard来补足.
      • 扩张

        • 扩张和变量一样,立即扩张还是延迟扩张和所在上下文有关.
      • 扩张值

        • *.o上面不同,没有匹配就返回空.
        • 上面没有匹配是保留原字符串*.o.这样一个奇怪的字符串.
      • 问题

        • 如果扩张出来的是空就导致了prerequisite为空.
        • 所以扩张一般是扩张源文件然后再修改值.
        • $(patsubst %.cpp,%.o,$(wildcard *.cpp))
  4. prerequisite搜索

    • 前景

      • 情景分析

        • 大型项目,文件存在多个路径.
        • 写路径名有点不好看,比如项目依赖的库在另一个绝对路径很长的地方.使用相对路径也很长.
        • 特别是项目目录特别深的时候,写路径就特别不好看.
        • 有的甚至在其他的网络服务器上.网络服务器的挂载位置不确定,需要动态确认.
      • 解决

        • 提供了prerequisite搜索功能.
        • 好处就是写起来比较简洁.但是理解起来可能就有点难.
    • VPATH

      • 功能

        • 所有的prerequisite的搜索路径.
      • 处理

        • 当前文件夹下搜索prerequisite搜索不到的时候就在VPATH声明的文件夹中搜索.
        • 搜索顺序根据值所罗列目录的先后顺序进行搜索.
        • 如果搜到了,这个文件就用做了prerequisite
      • 值特征

        • 值表示搜索路径,通过空格或:分割,可以混合.
    • vpath

      • 说明

        • VPATH类似,但是这个更加的灵活.
        • 按照匹配规则进行搜索,文件夹也更加灵活.
      • 格式

        vpath pattern directories
        vpath pattern
        vpath
        
      • 格式说明

        • 第一个是定义,路径格式和VPATH相同.
        • 第二个是取消定义.
        • 第三个是取消所有定义.
        • pattern%当通配符,匹配零或多个字符.
        • pattern原字符表示\%.
        • 定义了多个同样的pattern则路径值是拼接在一起的.而不是替换.
      • 搜索

        • 当前路径不存在,就搜索vpath.
        • vpath按照定义的匹配规则进行匹配,然后用定义的路径进行搜索.
        • 如果多个规则可以匹配,则按照先后顺序进行依此搜索.
      • 案例分析

        vpath %.c foo
        vpath % blish
        vpath %.c bar
        
        • %.c重复,%.c的路径有foo bar
        • %表示所有.
        • 搜索xx.c则是按照foo,bar,blish依此搜索.
    • 搜索处理

      • 处理算法

        • prerequisite先在当前目录为工作目录下搜索.搜不到再进行目录搜索.
        • 搜到了,搜索路径暂时存放,作一个为target.
        • 然后保存的路径作为targetprerequisite以同样的方式进行处理.
        • 执行完了搜索并处理更新,就需要判断是否需要执行recipe.
        • 如果prerequisite要执行更新,仅仅保留原样,不保存完整路径.
        • 如果prerequisite不用执行更新,源recipe中的prerequisite使用搜索后的路径.
      • 特殊

        • 使用GPATH=xxx声明可以保留完整路径,不管是否是需要更新,但是可能有的make不支持.
      • 说明

        • prerequisite的处理步骤是.
        • 先进行当前文件夹搜索文件.
        • 搜不到再搜索显式或隐式规则进行生成.
        • 再进行vpath进行搜索,尝试其他文件夹.
        • 再搜索VPATH.
    • 总结

      • 搜索顺序

        • wordir > vpath > VPATH.
      • prerequisite处理

        • wordir > rule > search
      • 注意

        • 可能不同版本实现不同,建议使用make -d查看执行信息.
    • recipe怎么处理

      • 说明

        • 在其他地方搜索到仍然按照recipe那样处理.
        • 所以写recipe的时候就要注意处理方式.以及prerequisite路径问题.
      • 常用

        • 不适用prerequisite进行处理,而是使用自动变量,获取真正的值.
      • 案例

        VPATH = src:../headers
        foo.o : foo.c defs.h hack.h
           cc -c $(CFLAGS) $< -o $@
        
    • lib,so库搜索

      • prerequisite格式

        • 写入格式是-lname.
        • 搜索格式是libname.so,libname.a
        • so.a
      • 搜索路径

        • wordir > vpath > VPATH > default
        • default:/lib,/usr/lib,/usr/local/lib,当然也有32,64版本/usr/local/lib64.和所在环境有关.
      • 名字转换

        • 默认使用.LIBPATTERNS=lib%.so lib%.a,即name == %
        • 也可以自定义为其他的转换格式.
  5. PHONY

    • 说明

      • 格式

        • .PHONY:actions
      • 说明

        • 这个是一个特殊的任务,这个任务的prerequisite不会检测存在,总是执行,执行完了总是认为最新.
        • 如果这个类型的prerequisite是某个ruleprerequisite,那么这个rule总是需要执行recipe.
        • 也不会执行隐式搜索规则.
    • 常用

      • 常见指令

        • all clean install等.
        • 这些需求就是编译所以,清理,生成的可执行文件拷贝到指定目录或默认目录.
        • all同时编译多个可执行文件.即多任务.递归调用,通常文件夹也会声明为这种类型.
      • 案例

        .PHONY:clean
        clean:
           rm -f *.o
        
      • 问题

        • 如果clean存在与工作目录,或者搜索目录,因为没有prerequisite,所以recipe就得不到执行.
        • 我们的需求肯定是每一次clean都能clean成功.
        • 声明为.PHONY:prerequisite就可以规避这个问题.
    • 注意

      • 一般不用于prerequisite,用了就总是执行更新.
      • .PHONY类型的target可以拥有prerequisite,这种很常见,一般用作编译多任务.
  6. 没有prerequisite,reciperule

    • 说明

      • 作用

        • 一个rule没有recipe,prerequisite也不存在,那么就会当作总是更新了.
        • .PHONYprerequisite作用相同.
      • 比较

        • .PHONY不会进行隐式搜索,更高效.
        • 不过有的不支持.PHONYmake就需要使用这种原始的.
  7. 特殊的target

    • .PHONY

      • prerequisite是特殊的target,这类型的target总是执行prerequisite.
      • 这类通常可以看作是指令.
    • .DEFAULT

      • 任何没有匹配的rule(隐式和显式规则),就总是执行这个的recipe.
    • .PRECIOUS

      • 这个的prerequisite作为target编译的时候被打断不会被删除.
      • 中间文件也不会被删除.
      • 隐式规则相关.INTERMEDIATE,.SECONDARY.
    • .SECONDEXPANSION

      • 后面的所有ruleprerequisite还会进行第二次解析.
    • .DELETE_ON_ERROR

      • 声明了,这个make执行的时候,如果recipe的返回值有错误,则进行删除操作.
      • 就像是被信号中断一样.
    • .EXPORT_ALL_VARIABLES

      • 将所有的变量都传递给子make
      • 即所有的变量都export.
    • .NOTPARALLEL

      • 当前的make是线性.
      • 不影响子make.
    • .ONESHELL

      • 指令不再是一行行的传递.
      • 而是所有的一起传递.这种有时候更友好,效率更高.
    • .POSIX

      • 执行recipe时,遇到错误返回值,就立即停止执行.
  8. 一个规则多个target

    • 说明

      • 核心思想就是尽量的减少重复的代码编写.
      • 多个target的依赖和recipe都一样,那么就可以写在一个rule中,而不是多个重复的.
      • 但是也不是说完全一样,在构建的时候会各自独立,而且还可以用自变量进行分辨.
    • 效果

      • 和写多个target不同,prerequisite相同或相似的差不多.
    • 案例

      • 案例一

        kbd.o command.o files.o: command.h
        
        • 三个.o文件有相同的依赖文件.
      • 案例二

        bigoutput littleoutput : text.g
           generate text.g -$(subst output,,$@) > $@
        
        bigoutput : text.g
           generate text.g -big > bigoutput
        littleoutput : text.g
           generate text.g -little > littleoutput
        
        • 第一个rule和下面的两个效果相同.
        • 第一个在recipe中使用自动变量,有细微的差距.
  9. 一个target多个rule

    • 说明

      • 效果

        • 多次定义target,相当于将target的多个prerequisite按照顺序拼接在一起.
        • 有点类似用\分成了多行,只是多地方定义更加的灵活.
      • prerequisite处理

        • 拼接的方式有点类似二位数组.为什么说时二维而不是一维呢?
        • 因为自变量处理的时候会不一样.
      • 模型

        def expandAuto(target):
           ...
           plen = len(target.prerequisite)
           for i in range(plen):
               temp = []
               for file in target.prerequisite[i]:
                   if file.name == "$+":
                       temp.append(target.prerequisite[:i])
                   else:
                       temp.append(file)
               target.prerequisite[i] = temp
           ...
        
      • rule处理

        • 多个targetrule定义只能有一个recipe.
        • 如果有多个以最后一个为准,同时输出错误信息.
      • 特殊

        • .开头的特殊target可以有多个recipe
        • target::格式的则是多个独立的定义处理.可以存在多个recipe.
    • 使用

      • 案例一

        cpps:=$(wildcard *.cpp)
        objs:=$(cpps:%cpp=%o)
        -include $(cpps:%cpp=%mk)
        $(objs):%o:%cpp
           g++ $< $(CFLAGS) -o $@
        
        • 这种非常常见,使用g++获取某个文件的依赖.
        • 但是没有recipe,可以使用make中定义的,而且prerequisite是拼接后的.
    • 总结

      • 使得对targetprerequisite更加的灵活.
      • 和指令include,ifeq,define等结合使用很有效果.
  10. 静态匹配规则

    • 说明

      • 特征

        • 以枚举或动态检查的方式对有限个target进行规则处理.
      • 格式

        targets ...: target-pattern: prereq-patterns ...
           recipe
           ...
        
        • target和普通的一样.
        • target-pattern,针对的是target格式匹配.可以用%匹配若干个字符.只能出现一次,要使用%字符需要使用\转意.
        • prereq-pattern表示prerequisite,可以通过%获取target-pattern%匹配的内容进行替换当前的%.prerequisite可以有多个.
      • 不匹配

        • 输出不匹配的target.
        • 部分匹配可以使用fileter函数筛选.
    • 案例

      • 案例一

        objects = foo.o bar.o
        all: $(objects)
        $(objects): %.o: %.c
           $(CC) -c $(CFLAGS) $< -o $@
        
        • %.otarget进行抽取,获取到foo,bar,后面的prerequisite%进行替换,得到foo.c,bar.c.
    • 隐式规则

      • 说明

        • 和上面的静态规则有点类似.
        • 静态是对有限个target进行匹配,隐式规则则是对任意个匹配的target匹配.
      • 差异

        • 优先级
          • 静态 > 隐式
        • 使用隐式的场景
          • target匹配.
          • target没有recipe.
          • targetprerequisite存在,但是没有对应的规则.
        • 多个匹配隐式规则
          • 先后顺序选择.
      • 静态的优势

        • 对某几个进行特殊处理.而不使用隐式规则编译.
        • 对于prerequisite,如果是路径搜索后的结果,那么更新就无法确认具体文件夹.通过精确处理可以避免错误.
  11. 自动生成rule

    • 说明

      • 场景

        • 一个cpp文件的依赖文件人为的书写是很复杂的.
        • 特别是在大项目中.
        • 认为书写很容易出错,而且也不精确.
      • 解决方案

        • 编译器提供了对应的功能.
      • 介绍

        • 这里介绍的是gcc/g++的编译选项.
        • 然后结合案例进行分析
    • gcc选项

      • 注意

        • 不要和其他的混用,比如编译混用.
        • 这个指令选项专门用来做依赖查看的.
      • -M

        • 输出
          • 不输出预处理结果,输出makefilerule格式的依赖描述.
        • 输入
          • 只指明一个文件.
          • g++ -M xxx.cpp
        • 输出ruletarget
          • target一般是源文件名去掉文件路径,然后将文件名后缀替换为.o.
        • 输出ruleprerequisite
          • 输入文件的直接或间接通过include,-imacros包含的文件.
          • 包括系统级别的文件(stdio.h,stdlib.h等)
      • -MM

        • 和上面一样,但是不包含系统级别的头文件.
        • 也就是说全是用户自定义的头文件.include "self/defined/headers"
      • -MF file

        • -MM,-M一起使用,将结果写入指定位置file处.没有这个选项,-MM,-M和与处理结果一起输出.
        • -MD,-MMD一起使用,修改默认输出位置.
        • file如果是-,则输出到标准输出stdout.
      • -MP

        • 为除了输入文件以外的输入文件的依赖文件都声明一个rule
        • 比如test.o: test.c test.h;test.h:;两个rule.
        • 这种有时候友好,但是这种会带来问题.
      • -MT target

        • 修改rule中的target.
        • 默认是只保留文件名并修改后缀为.o.这种就可以修改target.
        • 一般是添加路径前缀或者是添加自身的.mk适用同样的规则.因为依赖的文件变了很可能导致主文件的依赖发生改变.
        • xx.o xx.mk:prerequisite,然后在主makefile中声明隐式规则处理.
        • .mk的内容和prerequisite也息息相关.
      • -MQ target

        • -MT一样,不过是原样输出,即保留字符串.
        • 比如-MQ '$(obj)xx.o'得到的target就是$$(obj)xx.o
        • Q就是quotes的意思.
      • -MD

        • 等价于-MF -MF file.
        • 但是输出文件名需要用-o声明输出文件.
        • 默认是输入文件保留文件名后替换后缀为.d.
      • -MMD

        • -MD的无系统文件版本.
      • 注意

        • 输出比如c,cpp,rule.target可能和源文件一样.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值