简介

Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率

出现“makefile:2: * 遗漏分隔符 。 停止。”问题,原因是在编写makefile文件时,使用了空格

make主要解决两个问题:
1) 大量代码的关系维护
大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护
把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译
2) 减少重复编译时间
n 在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间
Makefile文件命名规则
makefile和Makefile都可以,推荐使用Makefile。
make工具的安装

sudo apt install make

语法规则

一条规则:

目标:依赖文件列表 命令列表

Makefile基本规则三要素:
1)目标:

  • 通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可是一个动作的名称

2)依赖文件:

  • 用来输入从而产生目标的文件
  • 一个目标通常有几个依赖文件(可以没有)

3)命令:

  • make执行的动作,一个规则可以含几个命令(可以没有)
  • 有多个命令时,每个命令占一行

测试代码:

  1. test:
  2. echo "hello world"
test:test1 test2
    echo "test"
test1:
    echo "test1"
test2:
    echo "test2"

命令格式

make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。
make命令格式:
make [ -f file ][ options ][ targets ]
1.[ -f file ]:

  • make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为makefile输入文件
  • -f 可以指定以上名字以外的文件作为makefile输入文件

    make -f ./v1.mk

2.[ options ]

  • -v: 显示make工具的版本信息
  • -w: 在处理makefile之前和之后显示工作路径
  • -C dir:读取makefile之前改变工作路径至dir目录
  • -n:只打印要执行的命令但不执行
  • -s:执行但不显示执行的命令

3.[ targets ]:

  • 若使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,然后退出
  • 指定了make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。

    make test1 -f ./v1.mk make test2 -f ./v1.mk

工作原理

1)若想生成目标, 检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来 生成该依赖文件
image.png
2) 检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新
image.png
总结:

  • 分析各个目标和依赖之间的关系
  • 根据依赖关系自底向上执行命令
  • 根据修改时间比目标新,确定更新
  • 如果目标不依赖任何条件,则执行对应命令,以示更新

例子
测试程序: main.c add.c sub.c

  1. 最简单的Makefile

    test:main.c add.c sub.c
     gcc main.c add.c sub.c -o a.out
    

    缺点:效率低,修改一个文件,所有文件会被全部编译

  2. 第二个版本的Makefile

好处就是有修改的文件才编译

test:main.o add.o sub.o
    gcc main.o add.o sub.o -o a.out

main.o:main.c
    gcc -c main.c -o main.o
add.o:add.c
    gcc -c add.c -o add.o
sub.o:sub.c
    gcc -c sub.c -o sub.o

Makefile中的变量

在Makefile中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单变量定义及使用。

  1. 自定义变量

1)定义变量方法:
变量名=变量值

2)引用变量:
$(变量名)或${变量名}

3)makefile的变量名:

  • makefile变量名可以以数字开头
  • 变量是大小写敏感的
  • 变量一般都在makefile的头部定义
  • 变量几乎可在makefile的任何地方使用

例子

#变量
OBJS = add.o sub.o main.o
TARGET=main

$(TARGET):${OBJS}
    gcc $(OBJS) -o a.out

main.o:main.c
    gcc -c main.c -o main.o
add.o:add.c
    gcc -c add.c -o add.o
sub.o:sub.c
    gcc -c sub.c -o sub.o

clean:
    rm -rf $(OBJS)

除了使用用户自定义变量,makefile中也提供了一些变量(变量名大写)供用户直接使用,我们可以直接对其进行赋值。

CC = gcc #arm-linux-gcc CPPFLAGS : C预处理的选项 如:-I CFLAGS: C编译器的选项 -Wall -g -c LDFLAGS : 链接器选项 -L -l

  1. 自动变量
  • $@: 表示规则中的目标
  • $<: 表示规则中的第一个条件
  • $^: 表示规则中的所有条件, 组成一个列表, 以空格隔开,如果这个列表中有重复的项则消除重复项。

注意:自动变量只能在规则的命令中中使用

OBJS = add.o sub.o main.o
TARGET=main
CC=gcc

#$@: 表示目标
#$<: 表示第一个依赖
#$^: 表示所有的依赖

$(TARGET):${OBJS}
    $(CC) $^ -o $@
    echo $@
    echo $<
    echo $^

main.o:main.c
    $(CC) -c $< -o $@
add.o:add.c
    $(CC) -c $< -o $@
sub.o:sub.c
    $(CC) -c $< -o $@

clean:
    rm -rf $(OBJS) ${TARGET}
  1. 模式规则

模式规则示例:

%.o:%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

Makefile第三个版本:

OBJS = add.o sub.o main.o
TARGET=main
CC=gcc

#$@: 表示目标
#$<: 表示第一个依赖
#$^: 表示所有的依赖

$(TARGET):${OBJS}
    $(CC) $^ -o $@

%.o:%.c
    $(CC) -c $< -o $@

clean:
    rm -rf $(OBJS) ${TARGET}

Makefile中的函数

makefile中的函数有很多,在这里给大家介绍两个最常用的。

  1. wildcard – 查找指定目录下的指定类型的文件

src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src

  1. patsubst – 匹配替换

obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o

在makefile中所有的函数都是有返回值的。
Makefile第四个版本:

# 获取当前目录下所有的.c
SRC = $(wildcard *.c)
# 获取当前目录下所有的.o
OBJS = $(patsubst %.c,%.o, $(SRC))

TARGET=main
CC=gcc

$(TARGET):${OBJS}
    $(CC) ${OBJS} -o $(TARGET)

%.o:%.c
    $(CC) -c $< -o $@

clean:
    rm -rf $(OBJS) ${TARGET}

Makefile中的伪目标

clean用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令,解决方案:

  • 伪目标声明: .PHONY:clean

声明目标为伪目标之后,makefile将不会该判断目标是否存在或者该目标是否需要更新

clean命令中的特殊符号:

  • “-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
  • “@”不显示命令本身,只显示结果。如:“@echo clean done”


    Makefile第五个版本: ```shell

    获取当前目录下所有的.c

    SRC = $(wildcard *.c)

    获取当前目录下所有的.o

    OBJS = $(patsubst %.c,%.o, $(SRC))

TARGET=main CC=gcc

$(TARGET):${OBJS} $(CC) ${OBJS} -o $(TARGET)

%.o:%.c @$(CC) -c $< -o $@

声明clean为伪目标, 不去判断目标是否存在或者已经更新, 无条件执行命令

.PHONY:clean clean: -rm -rf $(OBJS) ${TARGET} ```