Makefile基础知识

1. Makefile简介

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

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

        1.1. Make主要解决的问题

    (1)大量代码的关系维护

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

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

    (2)减少重复编译时间

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

        1.2. Makefile的命令规则

Makefile和makefile都可以作为主动识别的文件名字,但是推荐使用Makefile, 因为比较容易识别。

        1.3. Make工具的安装

$ sudo apt-get install make

[sudo] password for host:

Reading package lists... Done

Building dependency tree

Reading state information... Done

make is already the newest version (4.2.1-1.2).

0 upgraded, 0 newly installed, 0 to remove and 48 not upgraded.

2. Makefile语法规则

规则格式:

目标: 依赖文件列表

< tab >执行命令列表

Makefile基本规则三要素:

目标

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

依赖文件

·用来输入从而产生目标的文件;

·一个目标通常有几个依赖文件(也可以没有);

命令

·make执行的动作,一个动作可以含有几个命令,也可以没有;

·有多个命令时,每个命令占一行;

注意事项

(1) 目标: 即要生成的文件。如果目标文件的更新时间晚于依赖文件更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则会进行重新编译并更新目标文件。
(2) 默认情况下Makefile的第一个目标为终极目标。
(3) 依赖:即目标文件由哪些文件生成。
(4) 命令:即通过执行命令由依赖文件生成目标文件。注意每条命令之前必须有一个tab保持缩进,这是语法要求(会有一些编辑工具默认tab为4个空格,会造成Makefile语法错误)。

2.1. Make 的命令格式

make是一个命令工具,它解析makefile文件中的指令(规则)。

Make命令格式:

 Make [-f file][options][targets]

[-f  file]

·make默认在工作目录中寻找名为GNUmakefile, makefile, Makefile的文件作为makefile的输入文件;

·-f 可以指定以上名字以外的文件作为makefile输入文件;

[options]

·-v : 显示make工具的版本信息

·-w: 在处理makefile之前和之后显示工作路径

·-C dir: 读取makefile之前改变工作路径至dir指定的目录

·-n: 只打印要执行的命令但是不执行, 显示但是没有执行,用于测试makefile

·-s: 执行但不显示执行的命令

#编写makefile文件如下

test: obj1 obj2
        echo "hello makefile test"
obj1:
        echo "hello makefile obj1"
obj2:
        echo "hello makefile obj2"


# 执行make -v 参数

$ make -v
GNU Make 4.2.1
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


#执行make [-f]
$ make -f test.mk
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test



#make -w 参数
$ make -w -f test.mk
make: Entering directory '/mnt/e/Porject/test'
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test
make: Leaving directory '/mnt/e/Porject/test'

#make -C 参数
$ make -C -f ../test1/test1.mk

#make -n 参数
$ make -n -f test.mk
echo "hello makefile obj1"
echo "hello makefile obj2"
echo "hello makefile test"

#make -s 参数
$ make -s -f test.mk
hello makefile obj1
hello makefile obj2
hello makefile test

[targets]:

·若使用make指令是没有指定目标,则make工具默认会实现makefile文件中的第一个目标,然后退出;

·指定了make工具要实现的目标,目标可以是一个或者多个(多个目标间使用空格隔开)

# 指定目标

$ make obj1 -f test.mk
echo "hello makefile obj1"
hello makefile obj1

$ make obj2 -f test.mk
echo "hello makefile obj2"
hello makefile obj2

$ make test -f test.mk
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test

3. 变量

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

3.1. 变量的定义

变量名=变量值

3.2. 变量的引用

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

3.3. 命名规则

·makefile变量名可以以数字开头

·变量时大小写敏感的

·变量一般都在makefile的头部进行定义

·变量几乎可在makefile的任何地方使用

3.4. 系统变量

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

CC = gcc     #arm-linux-gcc 
CPPFLAGS     C预处理的选项 如 -l,无默认值
CFLAGS       C编译器的选项 -Wall -g -c,无默认值
LDFLAGS      连接器选项 -L -l
CURDIR       当前路径
CXX           C++语言的编译器名称
RM           删除文件程序的名称

3.5. 自动变量

# 这些变量不能单独使用,只能在命令中使用

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

4. 模式规则

# 模式匹配 所有的.o都依赖对应的.c
# 将所有的.c生成对应的.o的规则如下:
# %是一个通配符,用于匹配任意个字符。

%.o: %.c
    gcc  -c $^   -o  $@

5. 函数

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

wildcard - 查找指定目录下的指定类型的文件, 该函数能获取文件列表,并使用空格分隔开
$(wildcard 匹配规则)

# 如:查找当前目录下所有后缀为.c的文件,并赋值给SRC_FILES变量。
 SRC_FILES = $(wildcard src/*.c)

patsubst - 函数功能为模式字符串替换,注意逗号
$(patsubst 匹配规则, 替换规则, 输入的字符串)

# 如把SRC_FILES 变量里所有后缀为.c的文件替换成.o:
LBJ = $(patsubst %.c, %.o, $(SRC_FILES))

subst - 函数功能为字符串替换
$(subst 源字符串, 目标字符串, 输入的字符串)

# 将变量INPUT_SRT 变量中的hello字符串替换成HELLO
OUTPUT_STR = $(subst hello, HELLO, $(INPUT_STR))

Notdir - 函数用于去除文件路径中的目录部分
$(notdir 文件名)

# 将路径中的src/去掉的命令如下
SRC_FILES = $(notdir src/src1.c)

6. 伪目标

伪目标声明: .PHONY:clean

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

clean中的特殊符号:

· “-” 表示此命令出错也会继续执行后续的命令,如 -rm -f *.o;

· “@” 表示不显示命令本身,只显示结果;

7. 项目实例

假如项目文件中有add.c, sub.c, mul.c, div.c, test.c, 如果对项目中的所有文件进行编译,直接使用gcc 也可以,但是如果增加或者删除或者更改的话,就需要重新输入命令,并且在仅仅修改一个文件的情况下,仍然可能需要全部编译;

首先需要安装gcc:

$ sudo apt-get install build-essential 

7.1. 第一个版本

下面通过Makefile文件对该项目中的源文件进行管理,编写Makefile文件如下:

$ vi Makefile
test: add.c sub.c mul.c div.c test.c
        gcc add.c sub.c mul.c div.c test.c -o test
$ ls
Makefile  add.c  div.c  mul.c  sub.c  test.c

$ make
gcc add.c sub.c mul.c div.c test.c -o test

$ ls
Makefile  add.c  div.c  mul.c  sub.c  test  test.c

$ ./test
this is add file!
this is a sub file!
this is a mul file!
this is a div file!
this is a test file!

第一个版本的问题:执行make命令时,所有的源文件都会执行一次。

7.2. 第二个版本

改进第一个版本,将依赖文件修改成目标文件,即.o文件。对依赖文件逐个查找执行,最后执行目标的命令。

$ vi Makefile

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

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

test.o: test.c
        gcc -c test.c -o test.o

执行顺序结果如下:

$ ls

Makefile  add.c  div.c  mul.c  sub.c  test.c

$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test

$ ls
Makefile  add.c  add.o  div.c  div.o  mul.c  mul.o  sub.c  sub.o  test  test.c  test.o

$ vi add.c
$ make
gcc -c add.c -o add.o
gcc add.o sub.o mul.o div.o test.o -o test

第二个版本优点:修改任何一个文件,其他的文件都不会进行重新编译,只是编译新修改的文件。当修改add.c之后,目标的依赖add.o会和add.c进行比较时间,如果add.c比较新,就会重新编译add.c,从而达到对修改的文件重新编译的目的。

第二个版本的缺点:对源文件的书写重复太多,如果源文件太多的话,修改太困难,容易漏写或者重写。

7.3. 第三个版本

针对第二个版本的缺点,在第三个版本中加入变量来代替重复的部分;另外添加了删除目标文件的规则,Makefile的编写如下:

$ vi Makefile

OBJS = add.o sub.o mul.o div.o test.o

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

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

test.o: test.c
        gcc -c test.c -o test.o

.PHONY:clean
clean:

        Rm -f $(OBJS) test

执行命令如下:

$ vi Makefile

$ make clean
rm -f add.o sub.o mul.o div.o test.o  test

$ ls
Makefile  add.c  div.c  mul.c  sub.c  test.c

$ make -n
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o  -o test

$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o  -o test

$ ls
Makefile  add.c  add.o  div.c  div.o  mul.c  mul.o  sub.c  sub.o  test  test.c  test.o

当在命令行敲下make时,默认第一条规则的目标作为整个makefile的最终目标,如果想指定一条固定规则的目标作为编译目标时,就需要在make 命令后显式添加目标名字,make clean命令就是这样子实现的,clean的规则没有依赖,所以会直接执行命令。

有两点需要注意:

  1. . clean规则的位置不能在最头部,这样子的话,最终的目标最以clean的规则为准,这不是我们想要的,一般都会放在文件的结尾;
  2. clean应该定义为伪目标(见第7节),因为可能有clean的字符或者文件出现,因为这时候, 后面没有依赖文件,所以make 就认为这个文件是最新的,所以就不会执行后面的指令。所以为了避免这种情况的发生,Makefile使用 .PHONY 来区分伪目标。格式如下:
.PHONY:clean
clean:
        rm -f $(OBJS) test

7.4. 第四个版本

该版本引入自动变量(见4.5节)和TARGET自定义变量来进行简化makefile的书写。

$ vi Makefile
OBJS = add.o sub.o mul.o div.o test.o

TARGET=test

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

add.o: add.c
        gcc -c $< -o $@

sub.o: sub.c
        gcc -c $< -o $@

mul.o: mul.c
        gcc -c $< -o $@

div.o: div.c
        gcc -c $< -o $@

test.o: test.c
        gcc -c $< -o $@

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

7.5. 第五个版本

该版本引入规则匹配,见第5节。

$ vi Makefile
OBJS = add.o sub.o mul.o div.o test.o
TARGET=test

#模式规则匹配
%.o: %.c
     gcc -c $< -o $@

.PHONY:clean
clean:
        rm -f $(OBJS) $(TARGET)     
$ vi Makefile

$ make clean
rm -f add.o sub.o mul.o div.o test.o  test
$ make -n
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test

$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test

$ ls
Makefile  add.c  add.o  div.c  div.o  mul.c  mul.o  sub.c  sub.o  test  test.c  test.o    

7.6. 第六个版本

在第五个版本的基础上,有一个问题,就是变量OBJS 需要列出所有的源文件,如果源文件太多,造成书写困难,并且容易出错。这里就需要makefile的函数(见第6节),makefile的修改如下:

$ vi Makefile

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

TARGET=test

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

%.o: %.c
        gcc -c $< -o $@

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

7.7. 第七个版本

在第六个版本上添加辅助标记,主要是不显示一些不重要的打印和执行出错时的处理,标记符主要是“@”和“-”;

$ vi Makefile

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

TARGET=test

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

%.o: %.c
        @gcc -c $< -o $@

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

结束!

参考资料:

1. Makefile入门(超详细一文读懂)_我的小卷呀的博客-CSDN博客

2. c语言文件组织与多文件gcc命令行编译_gcc编译多个c文件_飞翔の荷兰人的博客-CSDN博客、

3. gcc 命令详解及最佳实践_gcc命令_阿基米东的博客-CSDN博客 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值