目录
一、Makefile的作用和基础功能
(1)Make简介
Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳,自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作;Make将只编译改动的代码文件,而不用完全编译。
(2)主要完成三点编译工作:
- 编译多个源文件;
- 生成需要的库文件;
- 生成需求格式的可执行文件
(3)Makefile基本结构
Makefile是Make读入的唯一配置文件
1)由make工具创建的目标体(target),通常是目标文件或可执行文件;
2)要创建的目标体所依赖的文件(dependency_file);
3)创建每个目标体时需要运行的命令(command);
4)注意:命令行前面必须是一个”TAB键”,否则编译错误为:*** missing separator. Stop.
Makefile格式
target : dependency_files
<TAB> command
例子
hello.o : hello.c hello.h
gcc –c hello.c –o hello.o
二、Makefile编译规则和语法
(1)Makefile规则详解
Makefile编写规则:
target : dependency_files
<TAB> command
- target: 可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。可使用通配符,当有多个目标时,目标之间用空格分隔。
- dependency_files: 生成该target所需要的依赖文件,当有多个依赖项时,依赖项之间用空格分隔。
- command: 该target要执行的命令(任意的shell命令)。
注意:
(1)– 在执行command之前,默认会先打印出该命令,然后再输出命令的结果;如果不想打印出命令,可在各个command前加上@。
(2)– command可以为多条,可以分行写,但每行都要以tab键开始。另外,如果后一条命令依赖前一条命令,则这两条命令需要写在同一行,并用分号进行分隔。
(3)– 如果要忽略命令的出错,需要在各个command之前加上减号" - "。
实例:
1.在文件当前目录下编译hello.c文件:
hello: hello.o
gcc -o hello hello.o
hello.o: hello.c
gcc -c hello.c
clean:
rm hello.o
执行make命令:
$ make
gcc -c hello.c
gcc -o hello hello.o
$ ls
hello hello.c hello.o Makefile
%%可以在相关的Shell命令前加上@,可以停止打印命令输出语句,直接执行语句
%%注释:-Wall:表示允许发出gcc所有有用的报警信息.
-c:只是编译不链接,生成目标文件”.o”
-o file:表示把输出文件输出到file里
2.清理编译中间文件:Makefile一般都会有一个clean伪目标,
用来清理编译中间产物,或者对源码目录做一些定制化的清理
Hello: main.c main.h
$(CC) –o hello main.c
clean:
$(RM) hello
(2)Makefile核心语法
Makefile的核心语法包括:命令、变量、条件语句和函数。
1)命令
Makefile脚本文件中支持运行Linux的shell命令,基本与命令行下的输入方法相同,而且在默认模式下,脚本运行会输出打印运行的命令,可以通过在脚本命令前加符号“@”禁止打印。
- shell基本命令格式:
- $ Command [-Options] Argument1 Argument2...
指令 选项 参数1 参数2 ...Shell提示符,如果当前用户为超级用户,提示符为“#”,其他用户的提示符均为“$”;
Command:命令名称,Shell命令或程序,严格区分大小写
Options:命令选项,用于改变命令执行动作的类型,由“-”引导,可以同时带有多个选项;
Argument:命令参数,指出命令作用的对象或目标,有的命令允许带多个参数
2)变量
创建变量的目的: 用来代替一个文本字符串:
1.系列文件的名字
2. 传递给编译器的参数
3. 需要运行的程序
4. 需要查找源代码的目录
5. 你需要输出信息的目录
6. 你想做的其它事情。(后面的小节中会详细介绍到)
3)条件语句
Makefile支持条件语句:
基本表达式为:
if ... <conditional-directive> <text-if-true> endif if ... else ... <conditional-directive> <text-if-true> else <text-if-false> endif
if表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。
表示条件关键字,有4个关键字:ifeq、ifneq、ifdef、ifndef。
- ifeq (<arg1>, <arg2>) %()可换为 " "、' '
比较arg1和arg2的值是否相同,如果相同则为真。
- ifneq (<arg1>, <arg2>) %()可换为 " "、' '
比较arg1和arg2的值是否不同,如果不同则为真。
- ifdef <variable-name>
如果值非空,则表达式为真,否则为假。
- ifndef <variable-name>
如果值为空,则表达式为真,否则为假。也可以是函数的返回值。
4)函数
函数格式:
$(function arguments)
function
是函数名,arguments
是该函数的参数。- 参数和函数名之间是用空格或 <Tab> 隔开。
- 如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分
内核的 Makefile 中用到大量的函数,现在介绍一些常用的:
1.获取文件名中的路径部分 $(dir names…) eg: $(dir src/foo.c hacks) 结果为src/ ./ 2.获取names中的真正文件名 $(notdir names…) eg: $(notdir src/foo.c hacks) 结果为foo.c hacks 3.抽取names中每一个文件名的后缀。 $(suffix names…) eg: $(suffix src/foo.c src-1.0/bar.c hacks) 结果为.c .c 4.抽取names中每一个文件名中除后缀外一切字符 $(basename names…) eg: $(basename src/foo.c src-1.0/bar hacks) 结果为src/foo src-1.0/bar hacks 5.参数pattern是一个文件名格式,包含有通配符(通配符和 shell 中的用法一样)。 函数 wildcard 的结果是一列和格式匹配的且真实存在的文件的名称,文件名用空格隔开。 比如若当前目录下有文件 1.c、2.c、1.h、2.h,则: $(wildcard pattern) eg: c_src := $(wildcard *.c) 结果为1.c 2.c
(3)创建和使用变量
1)变量定义的两种方式:
a. 递归展开方式VAR=var
b. 简单方式 VAR:=var
* 变量使用$(VAR)
注:用”$”则用”$$”来表示、类似于编程语言中的宏 。
2)为变量添加值 你可以通过+=为已定义的变量添加新的值:
Main=hello.o hello-1.o
Main+=hello-2.o
3)自动变量
- $* 不包含扩展名的目标文件名称
- $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
- $< 第一个依赖文件的名称
- $? 所有时间戳比目标文件晚的的依赖文件,并以空格分开
- $@ 目标文件的完整名称
- $^ 所有不重复的目标依赖文件,以空格分开
- $% 如果目标是归档成员,则该变量表示目标的归档成员名称
4)系统中预定义的变量 :
- AR: 库文件维护程序的名称,默认值为ar。
- AS: 汇编程序的名称,默认值为as。
- CC C编译器的名称,默认值为cc。
- CPP C预编译器的名称,默认值为$(CC) –E。
- CXX C++编译器的名称,默认值为g++。
- FC FORTRAN编译器的名称,默认值为f77
- RM 文件删除程序的名称,默认值为rm -f
三、Makefile应用实例
1.变量使用的简单实例
Hello: main.c main.h
<tab> $(CC) –o hello main.c
clean:
<tab> $(RM) hello
2.较为完整的实例
OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
3.同时编译多个可执行文件
CC = gcc
all: server client
server:server.c net.h
$(CC) -o server server.c
client:client.c net.h
$(CC) -o client client.c
clean:
rm server client
4.效率高,精炼,支持检测头文件(手动添加)
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<
sub.o : sub.h
clean:
rm *.o test -f
5.效率高,精炼,支持自动检测头文件
objs := main.o sub.o
test : $(objs)
gcc -o test $^
# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
# 把依赖文件包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
rm *.o test -f
distclean:
rm $(dep_files) *.o test -f
总结
1. 本文只是基础的Makefile编译规则和方法,复杂的编译经验有待积累,文中有问题的欢迎指出;
2. 最后列出部分学习及参考的博客,并致谢:
【1】编译—Makefile基础知识_JackRsir的博客-CSDN博客_makefile编译【2】Makefile 的使用(在 Linux 中使用 make 命令来编译程序)_韦东山的博客-CSDN博客_编写makefile文件,通过make命令进行编译