linux系统编程(2)--Makefile
1.Makefile简介
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。
make主要解决两个问题:
1) 大量代码的关系维护
大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护。把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译。
2) 减少重复编译时间
在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。
Makefile文件命名规则
makefile和Makefile都可以,推荐使用Makefile。
make工具的安装
sudo apt install make
2.Makefile文件规则
#规则样式
目标:依赖文件列表
<tab>命令列表
#命令列表前是一个制表符!推荐将vim的制表符设置为4个空格(在/etc/vim/vimrc文件最后添加set ts=4)
#目标:通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可是一个动作的名称。
#依赖文件:用来输入从而产生目标的文件。一个目标通常有几个依赖文件(也可以没有)。
#命令列表:make执行的动作,一个规则可以含几个命令(也可以没有)。
举例(vim Makefile
):
all:test1 test2
echo "hello all"
test1:
echo "hello test1"
test2:
echo "hello test2"
在命令行输入make
查看效果。
3.make命令
make是一个命令工具,用来解释Makefile中的规则。
#语法:
make [ -f file ][ options ][ targets ]
说明:
- -f file
- make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为输入文件
- -f 可以指定以上名字以外的文件作为makefile输入文件
- options
- -v:显示make工具的版本信息
- -w:在处理makefile之前和之后显示工作路径
- -C dir:读取makefile之前改变工作路径至dir目录
- -n:只打印要执行的命令但不执行
- -s:执行但不显示执行的命令
- targets
- 若使用make命令时没有指定目标,则make工具默认实现Makefile文件内的第一个目标,然后退出
- 指定make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。
4.Makefile示例
测试文件:test.c add.c sub.c mul.c div.c
add.h:
#ifndef __ADD_H__
#define __ADD_H__
int add(int x, int y);
#endif /*__ADD_H__*/
add.c:
#include "add.h"
int add(int x, int y)
{
return x + y;
}
类似完成sub.c mul.c div.c。
test.c:
#include <stdio.h>
#include "add.h"
#include "sub.h"
#include "mul.h"
#include "div.h"
int main()
{
int x = 15;
int y = 5;
printf("x + y = %d\n", add(x, y));
printf("x - y = %d\n", sub(x, y));
printf("x * y = %d\n", mul(x, y));
printf("x / y = %d\n", div(x, y));
return 0;
}
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
这种写法在修改一个文件后,所有文件会被重写编译,效率低。
改进后的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
5.Makefile中的变量
5.1 自定义变量
#创建变量:
变量名=变量值
#使用变量:
$(变量名)
${变量名}
#注意:
#makefile变量名可以以数字开头
#makefile变量名大小写敏感
#变量一般在makefile的头部定义
使用变量名修改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
clean:
rm -rf $(OBJS) test #Tips: 使用make clean可以快速清理生成的文件
5.2 自动变量
- $@: 表示规则中的目标
- $<: 表示规则中的第一个依赖条件
- $^: 表示规则中的所有依赖文件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项。
注意:自动变量只能在规则的命令中中使用
使用自动变量进一步优化我们的Makefile:
OBJS = add.o sub.o mul.o div.o test.o
TARGET=test
$(TARGET):$(OBJS)
gcc $^ -o $@
echo $@
echo $<
echo $^
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 $@
clean:
rm -rf $(OBJS) $(TARGET)
5.3 模式规则
使用模式匹配进一步优化我们的Makefile:
OBJS = add.o sub.o mul.o div.o test.o
TARGET=test
$(TARGET):$(OBJS)
gcc $^ -o $@
#模式匹配 所有的.o都依赖对应的.c
#将所有的.c 生成对应的.o
%.o:%.c
gcc -c $< -o $@
clean:
rm -rf $(OBJS) $(TARGET)
5.4 变量赋值的其他方式
-
"="是最普通的等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。
VAR_A = A VAR_B = $(VAR_A) B VAR_A = AA
经过上面的赋值后,最后VIR_B的值是AA B,而不是A B,在make时,会把整个Makefile展开,来决定变量的值。
-
“:=” 表示直接赋值,赋予当前位置的值。
VAR_A := A VAR_B := $(VAR_A) B VAR_A := AA
最后VAR_B的值是A B,即根据当前位置进行赋值。
-
“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。
-
"+="和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上。
5.5 预定义变量
除了使用用户自定义变量,makefile中也提供了一些变量(变量名大写)供用户直接使用,我们可以直接对其进行赋值。
CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 如:-I
CFLAGS: C编译器的选项 -Wall -g -c
LDFLAGS : 链接器选项 -L -l
6.Makefile中的函数
makefile中的函数有很多,以下两个比较常用。
- wildcard:查找指定目录下的指定类型的文件
- patsubst:匹配替换
使用这两个函数优化我们的Makefile:
SRC=$(wildcard ./*.c) #获取当前目录下所有的.c文件
OBJS=$(patsubst %.c, %.o, $(SRC)) #将SRC中所有的.c替换成.o
TARGET=test
$(TARGET):$(OBJS)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
rm -rf $(OBJS) $(TARGET)
7.Makefile中的伪目标
在之前的Makefile最后添加了一条clean规则。
当使用make clean
时,方便清除编译生成的中间.o文件和最终目标文件。
问题:
如果当前目录下有一个名为clean的文件,当使用make clean
时就不能执行clean规则中对应的命令。
解决:
声明clean为伪目标。声明为伪目标后,makefile将不会判断该目标是否存在或是否需要更新。
SRC=$(wildcard ./*.c)
OBJS=$(patsubst %.c, %.o, $(SRC))
TARGET=test
$(TARGET):$(OBJS)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
.PHONY:clean #声明clean为伪目标 伪目标不去判断目标文件是否存在或者已经更新
clean:
rm -rf $(OBJS) $(TARGET)
8.命令中的特殊符号
- - 此条命令出错,make也会继续执行后续的命令。
- @ 不显示命令本身,只显示结果。
注意:只能在命令前加!
示例:
SRC=$(wildcard ./*.c)
OBJS=$(patsubst %.c, %.o, $(SRC))
TARGET=test
$(TARGET):$(OBJS)
gcc $^ -o $@
%.o:%.c
@gcc -c $< -o $@ #make后的输出结果将不显示这条命令本身(即gcc -c ......)
.PHONY:clean
clean:
-rm -rf $(OBJS) $(TARGET)
⭐Makefile工作原理
- 若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件。
- 检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新。