Makefile基础

文章目录

    • 概述
    • make命令格式
    • Makefile语法规则
    • Makefile工作原理
    • Makefile常用编写方式
      • Makefile中的变量
      • Makefile中的函数
      • Makefile中伪目标

概述

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令

Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。


make主要解决两个问题

  • 大量代码的关系维护

    • 大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护;

    • 把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译.

  • 减少重复编译时间

    • 在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。

Makefile文件命名规则

makefile 或 Makefile 都可以,推荐后者。

make 工具的安装

sudo apt install make


make命令格式

make是一个命令工具,它解释Makefile中的指令(准确说是规则)。
make命令格式:

make [-f file] [option] [targets]

[-f file] :

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

[optios] :

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

[targets] :

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

Makefile语法规则

规则 :

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

Makefile 规则三要素 :

1)目标

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

2)依赖文件

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

3)命令

  • make执行的动作,一个规则可以包含几个命令(也可以没有);
  • 如果有多个命令,每个命令占一行。

示例程序 :

test : test1 test2
	echo "test1"
test1 : 
	echo test1
test2 : 
	echo test2
	

Makefile工作原理

当使用make 解析 Makefile文件时,会执行以下动作:

  • 如果想生成目标,先检查规则中的依赖条件是否存在;如果不存在,则寻找是否存在用来生成该依赖文件的规则。
    Makefile基础_第1张图片
  • 检查规则中的目标是否需要更新,必须先检查该目标的所有依赖,依赖中有任何一个被更新则目标必须更新。
    Makefile基础_第2张图片

Makefile常用编写方式

下面从一个实例出发,看看一个优美简练的Makefile是如何编写的。

首先我们编写了几个.h和.c文件:add.h sub.h mul.h div.hadd.c sub.c mul.c div.c main.c,这些代码基本相同,下面给出add.h add.c main.c的代码:

//add.h
#include 

int add(int a, int b);

//add.c
#include 

int add(int a, int b) {
	
	return a + b;
}

//main.c
#include 
#include "add.h"
#include "sub.h"
#include "mul.h"
#include "div.h"

int main() {
	
	int a = 9, b = 3;
	printf("x + y = %d\n", add(a, b));
	printf("x - y = %d\n", sub(a, b));
	printf("x * y = %d\n", mul(a, b));
	printf("x / y = %d\n", div(a, b));

	return 0;
}

我们先来写一个最简单的Makefile,就把它称作Makefile_v1吧,如下所示:

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

这个版本有什么缺点呢?那就是修改一个文件,所有文件都会被再次编译一遍。

将.c 文件编译链接成可执行文件的过程中,会产生一个.o 目标文件。如果我们把这些.o 文件当作依赖文件 ,再修改某一个.c 文件时,只需要重新生成对应的依赖.o 文件即可。如下所示:

main : add.o sub.o mul.o div.o main.o
	gcc add.o sub.o mul.o div.o main.o -o main

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

mul.o : mul.c
	gcc -c mul.c -o mul.o

div.o : div.c
	gcc -c div.c -o div.o


Makefile中的变量

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

自定义变量

1)定义变量方法:

变量名 = 变量值

2)引用变量:

$(变量名) 或 ${变量名}

【注】Makefile 中的变量名规则

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

我们来改改之前的Makefile:

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

$(TARGET):$(OBJS)
	gcc $(OBJS) -o $(TARGET)

add.o:add.c
	gcc -c add.c -o add.o

sub.o:sub.c
	gcc -c sub.c -o sub.o

mul.o:mul.c
	gcc -c mul.c -o mul.o

div.o:div.c
	gcc -c div.c -o div.o
	
main.o:main.c
	gcc -c main.c -o main.o

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

这样就减少了很多重复工作,变量的意义正在于此,方便开发者对数据的使用;

可以看到文件末尾多了一行clean,之前介绍过目标可以是一个动作,生成clean不需要依赖
所以直接执行命令rm -rf $(OBJS) $(TARGET)删除掉所有 .o 文件和可执行文件main。

自动变量

  • $@:表示规则中的目标
  • $<:表示规则中第一个依赖
  • $^:表示规则中的所有依赖,以列表形式存在且用空格隔开,如果这个列表中有重复项则消除重复项。

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

改写Makefile如下:

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

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


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

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

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

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

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

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

可以看到Makefile再一次被简化了,我们可以发现有多条规则的命令相同但却重复写了多次,此时Makefile给我们提供的模式匹配就派上了用场。

模式规则

%.o:%.c

改写Makefile如下:

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

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

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

这个版本还存在一个问题,我们看第一句OBJS = add.o sub.o mul.o div.o main.o,如果一个项目中的.o 非常多难到要手动一个个加上去吗?显然不现实,makefile提供的函数解决了这个问题。

Makefile中的函数

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

  • wildcard - 查找指定目录下的指定类型的文件
    src = $(wildcard *.c) //找到当前目录下所有后缀为.c 的文件,赋值给src
  • patsubst - 匹配替换
    obj = $(patsubst %.c, %.o, $(src)) //把 src 变量里所有后缀为.c 的文件替换成.o

makefile中的所有函数都是有返回值的。

改写Makefile如下:

SRC = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET = main
CC = gcc

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

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

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

这个Makefile看起来就很简洁了,但这还不是最终版本,因为此Makefile在make时可能会出现一些问题,请接着阅读。


Makefile中伪目标

clean 用途:清除编译生成的中间.o 文件和最终目标文件

make clean 如果当前目录下存在同名clean文件,则不执行clean对应的命令,解决方案如下:

  • 伪目标声明:.PHONY:clean
    声明目标为伪目标之后,makefile将不会该判断目标是否存在或者该目标是否需要更新;
  • clean命令中的特殊符号:
    • “-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
    • “@”不显示命令本身,只显示结果。如:“@echo clean done”

经过以上,Makefile最终版本来了:

SRC = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET = main
CC = gcc

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

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

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


你可能感兴趣的:(脚本编写)