一、Makefile和Make的作用
什么是makefile?或许很多使用IDE(集成开发环境)开发者可能都不知道这个东西,因为IDE都为开发者做了这个工作。但是在Unix下的软件编译,你就不能不自己写makefile了,尽管Linux桌面环境下也有很多IDE,但是在嵌入式开发中经常需要使用中终端进行开发,不能直接使用IDE;同时,编写makefile,可以对项目中模块的依赖关系有更清晰的认识,有利于提高自己完成大型工程的能力。
1、引入
从一个简单例子开始,首先我们需要准备5个文件。这5个文件分别代表主程序和工具函数的实现与声明。
#ifndef HELLOMAKE_H_
#define HELLOMAKE_H_
void myPrintHelloMake();
#endif /* HELLOMAKE_H_ */
#include <stdio.h>
#include "hellomake.h"
void myPrintHelloMake()
{
printf("Hello, makefiles!\n");
return;
}
#ifndef SRC_TOOLKITS_H_
#define SRC_TOOLKITS_H_
void PrintName(char *name);
#endif /*
#include <stdio.h>
#include "toolkits.h"
void PrintName(char *name)
{
printf("Hello, %s!\n", name);
return;
}
#include <stdio.h>
#include "toolkits.h"
void PrintName(char *name)
{
printf("Hello, %s!\n", name);
return;
}
有了这三个文件,我们可以用下面的指令来编译
gcc -I./ main.c hellomake.c toolkits.c -o learn_make
这条指令编译两个 c 文件,并且生成可执行文件 hellomake。-I.
参数告诉工 gcc 从当前目录寻找头文件 hellomake.h。我们在测试 / 修改 / 调试代码的时候一般可以在 terminal 上用上下键切换编译指令,这样就不用每编译一次都要敲一次编译指令了。
但是不幸的是,这种方法有两个缺点:第一:如果你不小心丢掉了编译指令(比如不小心关掉了 terminal)或者换了一个电脑,你就得重新敲一遍编译指令;第二:就算你只修改了某一个 c 文件,你也必须把所有的源文件全部重新编译一次,这个是非常耗时间的,也是完全没有必要的。
2、makefile的作用
这两个问题都可以通过写一个 makefile 来解决。
makefile中包含了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile指定了目标文件一系列的依赖关系,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是—“自动编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
3、make的作用
makefile中指定了目标文件的依赖文件和目标文件生成的指令,那么谁来执行这些依赖规则和指令呢?那就是make了。
make是一个工具,是一个解释makefile中指令和规则的工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译规则。
总之, make是一种工具,用于控制从程序的源文件生成程序的可执行文件和其他非源文件。
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files. Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program. The make utility automatically determines which pieces of a large program need to berecompiled, and issues(调用) commands to recompile them.
二、简单makefile编写
给第一部分中的5个文件,编写makefile。本节1-6部分为递进关系,由简单到抽象。
1、最简单的makefile
lean_make: main.c hellomake.c toolkits.c # 指定依赖
# 默认输出可执行文件的名称为a.out,使用-o参数指定输出文件名称:learn_make
# 命令前面是tab,不是空格
gcc -I./ main.c hellomake.c toolkits.c -o learn_make
把这两行写入到一个名为 Makefile 或 makefile 的文件中,然后在 terminal 输入make,系统就会按照 makefile 文件中定义的规则编译你的代码。
注make命令不带参数的话,会默认执行 makefile 文件中的第一条规则。此外,目标文件依赖的文件列表放在“:”之后的第一行,make 就知道如果其中的任何文件发生更改,就需要执行 gcc编译命令。这样就解决了第一个问题,不需要在 terminal 中上下翻找最后一个编译命令了。但是这样写,make 仍然不能有效的只编译更改源文件。
有一点需要特别注意的是,在 gcc 命令之前有一个 tab 字符。事实上,在任何指令之前,都必须加一个 tab 字符,这是 make 的要求,不加 tab。
根据makefile内容可以看出,编写makefile是需要gcc常用参数 和 shell常用指令的使用基础的。连接如下
GCC 参数详解
Linux基本操作
2、仅编译更改的文件(make工作原理)
lean_make: main.o hellomake.o toolkits.o #指定依赖关系
# 命令前面是tab,不是空格
# 默认输出可执行文件的名称为a.out,使用-o参数指定输出文件名称:learn_make
gcc main.o hellomake.o toolkits.o -o learn_make
main.o: main.c hellomake.h toolkits.h
# -c 只允许执行到汇编步骤,不允许链接。
gcc -I./ -c main.c -o main.o
hellomake.o: hellomake.c toolkits.h
gcc -I./ -c hellomake.c -o hellomake.o
toolkits.o: toolkits.c toolkits.h
gcc -I./ -c toolkits.c -o toolkits.o
下图可以看到编译的顺序,同时可以看到中间生成的未链接的二进制代码(*.o文件)。。
3、添加编译时的参数
添加编译参数和清理命令
lean_make: main.o hellomake.o toolkits.o
gcc main.o hellomake.o toolkits.o -o learn_make
main.o: main.c hellomake.h toolkits.h
# -Wall可以看到所有的警告
# -g 包含调试内容
gcc -I./ -Wall -g -c main.c -o main.o
hellomake.o: hellomake.c toolkits.h
gcc -I./ -Wall -g -c hellomake.c -o hellomake.o
toolkits.o: toolkits.c toolkits.h
gcc -I./ -Wall -g -c toolkits.c -o toolkits.o
#清理命令
clean:
rm -rf *.o lean_make
4、利用变量简化makefile
# OBJS 代表 依赖文件
# CC 代表 gcc指令
# CFLAGS 代表 编译选项
# INC 代表 头文件目录
# L
# LIBS 代表 依赖库
TARGET=learn_make
OBJS=main.o hellomake.o toolkits.o
LIBS=
INC=-I./
CFLAGS=-Wall -g
RM= rm -rf
CC=gcc
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
main.o: main.c hellomake.h toolkits.h
$(CC) $(INC) $(CFLAGS) -c main.c -o main.o
hellomake.o: hellomake.c toolkits.h
$(CC) $(INC) $(CFLAGS) -c hellomake.c -o hellomake.o
toolkits.o: toolkits.c toolkits.h
$(CC) $(INC) $(CFLAGS) -c toolkits.c -o toolkits.o
clean:
$(RM) *.o $(TARGET)
5、利用默认变简化makefile
# $< 代表 代表上面规则中的第一个依赖文件
# $^ 代表 代表上面规则中的所有依赖文件
# $@ 代表 代表上面规则中的目标文件
TARGET=learn_make
OBJS=main.o hellomake.o toolkits.o
LIBS=
INC=-I./
CFLAGS=-Wall -g
RM= rm -rf
CC=gcc
lean_make: $(OBJS)
$(CC) $^ -o $@
main.o: main.c hellomake.h toolkits.h
$(CC) $(INC) $(CFLAGS) -c $< -o $@
hellomake.o: hellomake.c toolkits.h
$(CC) $(INC) $(CFLAGS) -c $< -o $@
toolkits.o: toolkits.c toolkits.h
$(CC) $(INC) $(CFLAGS) -c $< -o $@
clean:
$(RM) *.o $(TARGET)
6、利用通配符简化makefile
# %.o:%.c
# $(CC) $^ $(CFLAGS) -o $@
# 百分号是一个通配符
TARGET=learn_make
OBJS=main.o hellomake.o toolkits.o
LIBS=
INC=-I./
CFLAGS=-Wall -g
RM= rm -rf
CC=gcc
$(TARGET): $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(INC) $(CFLAGS) -c $< -o $@
clean:
$(RM) *.o $(TARGET)
7、make工作原理
make工作原理
- 首先make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“lean_make”这个文件,并把这个文件作为最终的目标文件。
- 如果lean_make文件不存在,或是lean_make所依赖的后面的 .o 文件的文件修改时间要比lean_make这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
- 如果lean_make所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极目标任务,也就是执行文件lean_make了
三、makefile文件详解
参考资料
https://www.gnu.org/software/make/manual/make.html
https://blog.csdn.net/weixin_38391755/article/details/803807861、makefile的内容
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
显式规则:显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的目标文件、目标文件的依赖文件和目标生成的指令。
- 隐晦规则:由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
- 变量的定义:在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示:其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
- 注释:Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
2、