makefile入门

文章详细介绍了Makefile的构成,包括显式规则、隐式规则、变量定义和编译流程。它强调了Makefile如何管理项目编译的依赖关系,以及如何通过自动化规则简化编译过程。此外,还提到了Makefile中的变量、条件判断和函数的使用,以及如何进行多目标文件的编译和管理。

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

makefile

  • makefile:整个工程的编译规则和流程【自动化编译】

  • 组成:显式规则、隐式规则(自动补全某些依赖关系)、变量定义、文件指示(makefile引用makefile,makefile部分生效)和注释(shell注释:#)

  • 入门结构:

    • edit : main.o fun.o
      cc -o edit main.o fun.o
      main.o : main.c header.h
      cc -c main.c
      fun.o : fun.c header.h
      cc -c fun.c
      clean :【没有依赖关系,故成为一个指令】
      rm edit main.o fun.o
  • makefile基本语法 & make 特性:

    • <target_file>[object/target]: <prerequisites_file>[dependencies]
      <command_line/recipe>【前有Tab键】
      object依赖于文件dependencies,由后者生成前者的命令为command_line;
      每当dependencies更新[target_file最后修改日期早于dependencies,或target_file不存在]时,重新生成object
    • make 在当前目录寻找makefile并执行其编译指令
      • 理论上,可以在一个makefile中定义和编译无关的命令,如make clean 清除所有生成文件
      • make 以makefile中第一个没有通配符的目标文件作为最终目标文件,一层又一层地去找文件的依赖关系,自上而下更新[后序遍历],直到最终编译出最终目标文件
        • 环境变量MAKECMDGOALS 会存放你所指定的最终目标文件
        • make执行时会将所有生效command_line及其结果打印出来
          • make -s or make --silent 只执行不显示命令
          • make -n or make --just-print 只显示命令不执行
      • 每个command默认是在一个新的进程(shell)中执行,接续符(;)将多个命令在同一个shell中执行,实现命令间的承接关系
      • 指定文件为makefile:make -f filename or make --file filename
        • make -d directory:切换到directory目录下再执行make
      • 全局忽视错误:make -i or make --ignore-errors
        [如果某规则(生成object对应的command_line内容)中某条命令错误,跳过该命令]
        • make -k or make --keep-going [如果某规则(生成object对应的command_line内容)中某条命令错误,跳过该规则]
        • make -w :执行依赖文件含有的生成命令【-wn可以查看依赖文件对应的命令】
        • make -debug=:输出make的调试信息
          • a:all所有的信息 [-debug=a --> -d]
          • v:verbose较为详细的信息
          • b:basic基本信息
        • make -j :编译调用内核数目
  • 伪目标文件

    • all 编译所有的目标文件
    • clean 删除所有被make创建的文件。
    • install 安装已编译好的程序,其实就是把目标执行文件拷贝到指定路径中去。
    • print 这个伪目标的功能是列出修改过的源文件。
    • tar 把源程序打包备份为一个tar文件。
    • dist 把tar文件压成Z文件或是gz文件
    • TAGS 更新所有的目标,以备完整地重编译使用
    • check和test 一般用来测试makefile的流程。
  • 变量 [类似宏,直接替代]时

    • 定义 obj = obj.o main.o

    • 取值 (obj)【注:(obj)【注:(obj)【注:−−make−−>--make-->make>

    • obj = .o [通配符并不会在变量定义处展开文件,而是直接将*.o代入command,如果*.o匹配不到任何的对象,变量就相当于文件’*.o’]

      • 解决策略:obj = ${wildcard .o}[当.o匹配不到任何的对象时,变量为空]
      • 细节:make使用的是懒惰策略,如果变量出现在command_line中,那么仅当这条command_line将被执行,变量才会在其内部展开
    • 用变量定义变量 foo=$(bar) bar=var

    • 定义一个变量,其值是一个空格:

      • nullstring :=
        space := $(nullstring) # end of the line
        <用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,之间一个空格>
        反例:dir := /foo/bar # directory [错误,宏为‘/foo/bar ’]
      • 替换变量中的共有的部分:$(var:a=b)
        • 直接替换:bar := $(foo:.o=.c)
        • 匹配替换(静态模式):bar := $(foo:%.o=%.c)
      • 追加后缀:
        • objects += another.o --> objects := $(objects) another.o
          【如果变量之前没有定义过,那么,“+=”会自动变成“=”】
    • 复杂例子:
      x = $(y)
      y = z
      z = Hello
      a := ((((x)) --> ((((y)) --> Hello

      first_second = Hello
      a = first
      b = second
      all = (((a_$b) --> Hello

    • override = ,在makefile文件中重写覆盖命令行带入的变量或是系统环境变量

      • 如果系统环境变量与makefile内部定义的变量重名,默认取makefile内部变量【类比局部全局变量】
      • 如果默认取环境变量:make -e【如定义环境变量CFLAGS,即可使用统一编译参数】
    • <target…> : (override) 定义某个目标文件的局部变量,可覆写全局变量

      • 例:prog.o : CFLAGS = -g 例: %.o : CFLAGS = -o
  • 命令包(类似函数,本质是宏):

    • 定义:define run-yacc
      yacc $(firstword $^)
      mv y.tab.c $@
      endef
    • 执行:$(run-yacc)
  • = := ?=

    • 一次性赋值 := [防止循环引用]
      x := foo
      y := $(x) bar
      x := later --> y = foo bar
    • 绑定赋值 =
      x = foo
      y = $(x) bar
      x = later --> y = later bar
    • 条件赋值 ?= [类三目运算符]
      FOO ?= bar 如果FOO没有被定义过,那么变量FOO的值就是“bar”
  • 优化结构

    • objects = main.o kbd.o command.o display.o
      edit : $(objects)
      cc -o edit $(objects)
      main.o : defs.h [GNU_make会自动推导file.o的依赖file.c,可省略]
      kbd.o : defs.h command.h
      command.o : defs.h command.h
      display.o : defs.h buffer.h
      .PHONY : clean 【表示clean是一个伪目标文件】
      clean : [make视角下的依赖文件名与目标文件名只是标记符,不关心是否是文件]
      -rm edit $(objects) ['-'表示忽略错误,继续执行]
  • makefile引用

    • include [a.make] [.mk] [$(file)] 将include_makefile文件内容在本makefile文件中展开 [makefile支持通配字符?[…]]
    • 寻找include_file时默认地址为makefile当前目录及子目录 or /usr/include
    • make -I [include_file_path] 可指定目录
    • -include a.make [忽视include错误,不报错继续执行]
  • 文件查找

    • 路径前缀:VPATH=src:…/headers [路径间用:分割,make会依次在当前目录、VPATH目录中寻找目标文件和依赖文件]
    • 关键字:vpath 为符合模式的文件指定搜索目录,make会按照vpath语句的先后顺序来执行搜索
    • 关于include的高级特性:
      • 当include_file不存在[找不到]:以include_file为名查找目标文件(object_file),并执行 [找不到object_file就报错]
        • 更进一步,如果 object_file的command_line 创建了include_file:将创建成功的include_file包含进当前makefile
      • 当include_file存在,将include_file包含进当前makefile
        • 更进一步,如果还有同名的object_file,比较object_file与dependencies的时间先后,来决定是否执行command_line
  • 多目标文件:

    • bigoutput littleoutput : text.g
      generate text.g -(substoutput,,(subst output,,(substoutput,,@) > @[′@ ['@['表示调用makefile_subst函数]
      等价于: bigoutput : text.g
      generate text.g -big > bigoutput
      littleoutput : text.g
      generate text.g -little > littleoutput
  • 模式规则:

    • 变量和函数在目标文件名和依赖文件名处的展开发生在make载入Makefile时,而模式规则中的%则发生在运行时
      %.o : %.c
      $(CC) -c $(CFLAGS) $< -o $@ [CFLAGS表示编译时所用的-options]

    • 静态模式规则:<targets …>: : <prereq-patterns …>

      objects = foo.o bar.o
      all: $(objects) [最终目标文件]
      $(objects): %.o: %.c [用模式为%.c的依赖文件生成_%.o的目标文件]
      $(CC) -c $(CFLAGS) $^ -o $@

            $(filter %.o,$(files)): %.o: %.c [调用filter函数过滤]
                $(CC) -c $(CFLAGS) $^ -o $@
      
    • ${OBJ:%.c=%.o} 将OBJ中所有.o后缀文件替换成.c后缀文件

    • gcc -MM main.c :输出源文件中包含的头文件(不含库头文件)
      main.o: main.c header.h

  • 特殊字符

    • ‘-’:删除、创建文件如果碰到文件不存在或者已经创建,那么希望忽略掉这个错误,继续执行
    • ‘@’:不显示command_line,只打印结果
    • 自动变量:
      • $@:表示当前命令的目标文件
      • $<:表示第一个依赖文件 $^:表示所有依赖文件
      • $?:表示比目标还要新的依赖文件列表
    • $:表示引用makefile定义变量的值
      • $$:表示引用shell命令中定义的变量的值
      • 例子:__build_one_by_one:
        $(Q)set -e;
        for i in $(MAKECMDGOALS); do
        $(MAKE) -f $(srctree)/Makefile KaTeX parse error: Can't use function '$' in math mode at position 65: …交由Unix-shell处理,$̲i会被展开为i宏的定义,而i会展为$i(在shell看来相当于取i的值),可以看做对i捆绑两次,所以需要解绑两次
  • makefile嵌套结构:

    • 主目录makefile管控子目录makefile
    • subsystem:
      $(MAKE) -C subdir or cd subdir && $(MAKE)
      总控makefile传递变量到子级makefile:export <variable …>【总控makefile传递的变量相当于子级makefile的环境变量】
      如果传递所有变量,只需要一个export即可
  • 条件判断

    • (可为ifeq;ifneq;ifdef;ifndef)【ifeq判断两个字符串是否相等,ifdef判断某个变量是否值为空】
      【注:ifdef只是测试一个变量是否直接定义为空,其并不会逐级扩展】
      【如:bar = foo = $(bar) -->ifdef(bar)==false,ifdef(foo)==true】

      else

      endif
    • 例如:根据CC变量是否等于gcc,决定生成命令
      ifeq ($(CC),gcc)
      $(CC) -o foo $(objects) $(libs_for_gcc)
      else
      $(CC) -o foo $(objects) (normallibs)endif【注:make在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,而自动化变量是在运行时才有确切含义,因此不要把自动化变量(如(normal_libs) endif 【注:make在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,而自动化变量是在运行时才有确切含义,因此不要把自动化变量(如(normallibs)endif【注:make在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,而自动化变量是在运行时才有确切含义,因此不要把自动化变量(如@等)放入条件表达式中】
  • 函数

    • ${ } or $( )

    • 字符串函数【参数是字符串或者字符串集(字符串间由空格隔开,本质也是字符串)】

      • 例如:
        comma:= ,
        empty:=
        space:= $(empty) $(empty) #定义空格
        str:= a b c
        str2:= $(subst (space),(space),(space),(comma),$(str)) #replace all the space in str with comma
        • $(subst ,,) 字符串text中的from子串替换为to
        • $(patsubst ,,) 字符串text中pattern格式的子串替换为to
      • $(strip ):去掉字串中开头和结尾的空字符
      • $(findstring ,):在字串中查找字串;如果找到,那么返回,否则返回空字符串
      • $(filter
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值