Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目。一旦写编写好 Makefile 文件,只需要一个 make 命令,整个工程就开始自动编译,不再需要手动执行 GCC 命令。一个中大型 C/C++ 工程的源文件有成百上千个,它们按照功能、模块、类型分别放在不同的目录中,Makefile 文件定义了一系列规则,指明了源文件的编译顺序、依赖关系、是否需要重新编译等。
Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,在 Makefile 中都可以进行描述。换句话说,Makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。
以 Linux 下的 C 语言开发为例来具体说明一下,多文件编译生成一个文件,编译的命令如下所示:
gcc -o outfile name1.c name2.c ...
outfile 要生成的可执行程序的名字,nameN.c 是源文件的名字。这是我们在 Linux 下使用 gcc 编译器编译 C 文件的例子。如果我们遇到的源文件的数量不是很多的话,可以选择这样的编译方式。如果源文件非常的多的话,就会遇到下面的这些问题。
下面列举了一些需要我们手动链接的标准库:
因为有很多的文件,还要去链接很多的第三方库。所以在编译的时候命令会很长,并且在编译的时候我们可能会涉及到文件链接的顺序问题,所以手动编译会很麻烦。
如果我们学会使用 Makefile 就不一样了,它会彻底简化编译的操作。把要链接的库文件放在 Makefile 中,制定相应的规则和对应的链接顺序。这样只需要执行 make 命令,工程就会自动编译,省略掉手动编译中的参数选项和命令,非常的方便。
Makefile 支持多线程并发操作,会极大的缩短我们的编译时间,并且当我们修改了源文件之后,编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译,也极大的解决了我们耗费时间的问题。
并且文件中的 Makefile 只需要完成一次,一般我们只要不增加或者是删除工程中的文件,Makefile 基本上不用去修改,编译时只用一个 make 命令。为我们提供了极大的便利,很大程度上提高编译的效率。
它的规则主要是两个部分组成,分别是依赖的关系和执行的命令,其结构如下所示:
targets : prerequisites
command
或者
targets : prerequisites; command
command
相关说明如下:
如果 command 太长, 可以用 \ 作为换行符。
注意:我们的目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用 Tab 键,不能使用空格键。
简单的概括一下Makefile 中的内容,它主要包含有五个部分,分别是:
显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile
的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
由于我们的 make
命名有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile
,这是由 make
命令所支持的。
在 Makefile
中我们要定义一系列的变量,变量一般都是字符串,这个有点像 C 语言中的宏,当 Makefile
被执行时,其中的变量都会被扩展到相应的引用位置上。
其包括了三个部分,一个是在一个 Makefile
中引用另一个 Makefile
,就像 C 语言中的 include
一样;另一个是指根据某些情况指定 Makefile
中的有效部分,就像 C 语言中的预编译 #if
一样;还有就是定义一个多行的命令。
Makefile
中只有行注释,和 UNIX
的 Shell
脚本一样,其注释是用 #
字符,如果你要在你的 Makefile
中使用 #
字符,可以用反斜框进行转义,如: \#
。
*
表示任意一个或多个字符?
表示任意一个字符[...]
[abcd] 表示 a,b,c,d中任意一个字符, [^abcd]表示除 a,b,c,d 以外的字符, [0-9] 表示 0~9中任意一个数字~
表示用户的 home 目录make
安装:
sudo apt-get install make
make -v # 查看是否安装成功
main.cpp
代码:
#include
int main()
{
std::cout << "hello,world" << std::endl;
return 0;
}
通过下面的例子来具体使用一下 Makefile
的规则,Makefile
文件中添代码如下:
main: main.cpp
g++ main.cpp -o main
其中 main 是的目标文件,也是我们的最终生成的可执行文件。依赖文件就是 main.cpp 源文件,重建目标文件需要执行的操作是 g++ main.cpp -o main。这就是 Makefile 的基本的语法规则的使用。
使用 Makefile 的方式:首先需要编写好 Makefile 文件,然后在 shell 中执行 make 命令,程序就会自动执行,得到最终的目标文件。
wohu@ubuntu:~/cpp/demo$ ls
main.cpp `Makefile`
wohu@ubuntu:~/cpp/demo$ make
g++ main.cpp -o main
wohu@ubuntu:~/cpp/demo$ ls
main main.cpp `Makefile`
wohu@ubuntu:~/cpp/demo$ ./main
hello,world
wohu@ubuntu:~/cpp/demo$
如果命令的开始使用的是空格键,那么会报错
Makefile:2: *** missing separator. Stop.
Makefile
:2 表示第二行错误,应该以 Tab
开始。
当我们在执行 make 条命令的时候,make 就会去当前文件下找要执行的编译规则,也就是 Makefile 文件。我们编写 Makefile 的时可以使用的文件的名称 GNUMakefile 、makefile 、Makefile ,make 执行时回去寻找 Makefile 文件,找文件的顺序也是这样的。
推荐使用 Makefile(一般在工程中都这么写,大写的会比较的规范)。如果文件不存在,make 就会给我们报错,提示:
make: *** No targets specified and no `Makefile` found. Stop.
在 Makefile
中添加下面的代码:
main: main.o name.o greeting.o
g++ main.o name.o greeting.o -o main
main.o: main.cpp
g++ -c main.cpp -o main.o
name.o: name.cpp
g++ -c name.cpp -o name.o
greeting.o: greeting.cpp
g++ -c greeting.cpp -o greeting.o
在我们编译项目文件的时候,默认情况下,make 执行的是 Makefile 中的第一规则(Makefile 中出现的第一个依赖关系),此规则的第一目标称之为“最终目标”或者是“终极目标”。
在 shell 命令行执行的 make 命令,就可以得到可执行文件 main 和中间文件 main.o、name.o 和 greeting.o,main 就是我们要生成的最终文件。
通过 Makefile 我们可以发现,目标 main 在 Makefile 中是第一个目标,因此它就是 make 的终极目标,当修改过任何文件后,执行 make 将会重建终极目标 main。
它的具体工作顺序是:当在 shell 提示符下输入 make 命令以后。 make 读取当前目录下的 Makefile 文件,并将 Makefile 文件中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。
在我们的例子中,第一个规则就是目标 main 所在的规则。规则描述了 main 的依赖关系,并定义了链接 .o 文件生成目标 main 的命令;make 在执行这个规则所定义的命令之前,首先处理目标 main 的所有的依赖文件(例子中的那些 .o 文件)的更新规则(以这些 .o 文件为目标的规则)。
对这些 .o 文件为目标的规则处理有下列三种情况:
通过上面的更新规则我们可以了解到中间文件的作用,也就是编译时生成的 .o 文件。作用是检查某个源文件是不是进行过修改,最终目标文件是不是需要重建。
我们执行 make 命令时,只有修改过的源文件或者是不存在的目标文件会进行重建,而那些没有改变的文件不用重新编译,这样在很大程度上节省时间,提高编程效率。