makefile是什么?

Makefile本质上是一脚本文件,这个文件中存放了编译和构建程序的脚本指令,定义了整个工程构建可执行程序的规则。

Makefile 的基本语法

一个简单的 Makefile 由一系列「规则」组成,每条规则的格式如下:

目标(target) : 依赖(prerequisites)
    命令(commands)
  • 目标(target):要生成的文件(如可执行文件 main、目标文件 main.o),也可以是「伪目标」(如 clean,用于执行清理操作)。
  • 依赖(prerequisites):生成目标所需的文件或其他目标(如源文件 main.cfunc.c)。
  • 命令(commands):生成目标的具体操作(如编译命令),必须以 Tab 键开头(不能用空格)。

如果项目有多个源文件(如 main.c、add.c、add.h),需要定义更清晰的依赖关系:

项目结构:

project/
├── main.c
├── add.c
├── add.h
└── Makefile

Makefile:

main: main.o add.o
    gcc main.o add.o -o main
main.o: main.c compute.h
    gcc -c main.c -Wall -g
add.o: add.c compute.h
    gcc -c add.c -Wall -g
clean:
    rm -f main main.o add.o     #删除所有生成的目标文件以及可执行程序,并且不给提示。-f是必须的
rebuild: clean main             #clean和main要写在依赖的位置,而不是命令的位置!

    Makefile变量

    Makefile变量,在本质上就是一个文本字符串,在执行脚本代码的时候会原模原样地展开在所使用的地方(类似于宏的文本替换)。

    Makefile变量在使用的时候:

    1. 需要给在变量名前加上$符号
    2. 如果变量名不是单字符,就必须用()或大括号 {} 把变量名括起来。如果变量名是单字符,则允许不用括号。
    OUT := main
    $(OUT): main.o add.o        #脚本执行时,等价于main: main.o add.o    变量名必须用()括起来

    Makefile当中的变量,又可以分为三类:

    1. 自定义变量
    2. 自动变量
    3. 预定义变量

    自定义变量

    自定义变量就是程序员自己编写代码定义的变量

    OUT := main     #目标文件
    OBJS := main.o add.o       #生成目标文件所需要的依赖   
    COM_OP := -Wall -g        #编译选项
    
    $(OUT):$(OBJS)
        gcc main.o add.o -o main
    main.o: main.c compute.h
        gcc -c main.c -o main.o $(COM_OP)
    add.o: add.c compute.h
        gcc -c add.c -o add.o $(COM_OP)
    
    clean:
        rm -f $(OUT) $(OBJS) 
    rebuild: clean $(OUT)

      自动变量

      Makefile中的自动变量是指那些在规则执行时自动设置的变量,它们通常用于引用规则的目标和依赖项。这些变量在Makefile的执行过程中非常有用,因为它们能够让规则更加简洁且易于管理。下面是一些常用的自动变量,变量名及其描述如下(注意是变量名)

      1. @:表示此规则的目标文件名。
      2. <:表示此规则的第一个依赖文件名。
      3. ^:表示此所有的依赖文件列表,这些依赖项之间以空格分隔。

      注意:这些都只是变量名,要使用这些变量的话,需要在变量名前加字符$(单字符名不用加括号)。

      于是我们就可以进一步的修改上面的Makefile脚本代码,如下:

      OUT := main     #目标文件
      OBJS := main.o add.o       #生成目标文件所需要的依赖
      COM_OP := -Wall -g        #编译选项
      
      $(OUT):$(OBJS)
          gcc $^ -o $@
      main.o: main.c compute.h
          gcc -c $< -o $@ $(COM_OP)
      add.o: add.c compute.h
          gcc -c $< -o $@ $(COM_OP)                            
      
      .PHONY: clean rebuild
      clean:
          rm -f $(OUT) $(OBJS)         
      rebuild: clean $(OUT)

      预定义变量

      预定义变量,即由Makefile自身预先定义好的变量,我们可以直接拿来,也可以先重新赋值再用。

      常用的预定义变量如下:

      模式规则

      通过Makefile的变量,你已经将脚本代码写得很简洁了,但简洁不是我们的主要目的,通用才是。目前这个Makefile,我们觉得它仍然不够通用,于是我们继续学习Makefile的模式规则,以将Makefile写得更加通用。

      所谓模式规则:

      用于定义如何从一种类型的文件生成另一种类型的文件,它直接指定了文件之间的转换规则。模式规则下一般使用百分号(%)来表示文件名中的通配符部分。

      它的语法结构如下:

      %.target : %.source 
         commands

      解释如下:

      1. %.target表示可以匹配任意目标文件。比如%.o表示任意.o文件作为目标
      2. %.source表示可以匹配任意依赖文件。比如%.c表示任意.c文件作为依赖

      使用这个模式规则后,Makefile可以自动推导如何从任何 .c 文件生成 .o 文件,这样就可以大幅度简化脚本代码。

      于是有了模式规则后,我们可以这样写 Makefile:

      OUT := main     #目标文件
      OBJS := main.o add.o sub.o      #生成目标文件所需要的依赖
      COM_OP := -Wall -g        #编译选项
      CC := gcc        #修改CC的默认值
      
      $(OUT):$(OBJS)
          $(CC) $^ -o $@
      
      %.o : %.c compute.h
          $(CC) -c $< -o $@ $(COM_OP)
      #以下内容可以被上面一个模式规则替代
      #main.o: main.c compute.h
      #    $(CC) -c $< -o $@ $(COM_OP)
      #add.o: add.c compute.h
      #    $(CC) -c $< -o $@ $(COM_OP) 
      #sub.o: sub.c compute.h
      #    $(CC) -c $< -o $@ $(COM_OP)                       
      
      .PHONY: clean rebuild
      clean:
          $(RM) $(OUT) $(OBJS)         
      rebuild: clean $(OUT)

      显然使用模式规则,可以极大的简化代码,而且还可以提高扩展性。

      比如这时,我们再增加一个运算mul(乘法),创建一个mul.c文件包含头文件compute,并实现函数。此时我们该如何改这个Makefile呢?

      很简单只需要修改一个OBJS自定义变量的值就可以了:

      OBJS := main.o add.o sub.o mul.o      #生成目标文件所需要的依赖

      只需要改变上面一行就可以了。

      注意:

      1. 模式规则不能作为Makefile的第一个规则,因为Makefile会选择第一个明确的目标作为默认目标,而模式规则中的目标,不指定具体的目标文件名,而仅仅定义了从一种文件类型转换到另一种文件类型的通用规则。模式规则中的目标不是一个明确的目标,Makefile脚本执行时若把模式规则写在最上面,脚本执行会跳过这个模式规则目标。
      2. 模式规则定义了如何将一种类型的文件(比如.c源文件)转换成另一种类型的文件(比如.o目标文件),这种转换是根据文件名模式来完成匹配的。
      3. 在模式规则中,通配符 % 的匹配是一致的,即目标和依赖列表中 % 所表示的内容是一致的。

      内置函数

      经过上面的一番折腾,我们的Makefile已经非常简单,非常具有扩展性了。但"不满足"是进步的阶梯,我们能不能把Makefile扩展到新增.c文件完全不需要改动这个Makefile呢?

      于是我们就需要学习一下Makefile的内置函数语法。

      Makefile脚本语言,也像传统编程语言一样,支持函数调用,这种函数,我们称之为Makefile的"内置函数"

      内置函数的调用语法,和使用变量的语法非常类似,如下:

      $(<function> <arguments>)       #调用时要用小括号把函数名和参数括起来
      ${<function> <arguments>}       #也可以用大括号括起来,两种方式都可以,使用时二选一

      以上两种方式都可以,解释如下:

      1. <function>为函数名。注意尖括号只是为了语法格式美观,它不是实际调用语法的一部分。
      2. <arguments>为参数列表,允许出现一个或多个参数,参数之间以逗号分隔。注意尖括号只是为了语法格式美观,它不是实际调用语法的一部分。
      3. 函数名和参数列表之间以"空格"分隔。
      4. 参数允许有多个,多个参数之间用","分隔。

      Makefile 内置的函数并不算多,我们这里只介绍两个最常用的:

      通配符函数

      $(wildcard <pattern>)   

      函数的名字是:wildcard

      它的作用是:查找符合<pattern>的所有文件列表

      函数的返回值是:返回所有符合<pattern>的文件名,文件名之间以空格分隔。

      示例:

      SRCS := $(wildcard *.c)

      其效果是:查找当前目录下,所有以.c结尾的文件名,并将文件名以空格分隔,再赋值给变量SRCS。

      比如当前目录下有.c文件:main.c、add.c、sub.c

      那么这个函数调用的作用等价于:

      SRCS := main.c add.c sub.c

      模式替换函数

      $(patsubst <pattern>,<replacement>,<text>)

      函数的名字是:patsubst,它是词组"pattern substitution"的简写,意为模式替换。

      它的作用是:查找<text>中符合模式<pattern>的单词(单词以空白字符分隔),将其替换为<replacement>。

      注意事项:

      1. <pattern>可以包括通配符%,表示任意长度的字符串。
      2. 如果<replacement>中也含有%,那么<replacement>中的%所代表的字符串和<pattern>中%所代表的字符串相同。
      3. <text>作为替换的数据源,它是最后一个参数。

      函数的返回值是:返回替换后的字符串

      示例:

      OBJS := $(patsubst %.c, %.o, foo.c bar.c)

      其效果是:将字符串 foo.c、bar.c 中符合模式 %.c 的单词替换成 %.o,返回结果为 foo.o 和 bar.o

      注意:该函数的参数部分是以空格为间隔的,只需要一个空格作为间隔符,不要添加过多的空格!!比如下图中的用法就是错误的:

      也就是说,相当于变量赋值

      OBJS := foo.o bar.o

      于是我们结合这两个内置函数,就可以将Makefile脚本改成下面的样子:

      OUT := main
      SRCS := $(wildcard *.c) #将当前目录下的所有.c文件的文件名以空格分割,然后赋值给SRCS变量
      OBJS := $(patsubst %.c,%.o,$(SRCS)) #获取当前目录下所有.c文件对应的.o文件,以空格分割
      COM_OP := -Wall -g 
      CC := gcc        #修改CC的默认值
      
      $(OUT):$(OBJS)
          $(CC) $^ -o $@
      %.o : %.c compute.h
          $(CC) -c $< -o $@ $(COM_OP)
      
      .PHONY: clean rebuild
      clean:
          $(RM) $(OUT) $(OBJS)
      rebuild: clean $(OUT)

      以上,我们就的得到了一个极具通用性的Makefile脚本。

      Logo

      有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

      更多推荐