1.2.4 使用Makefile简化构建
尽管我们在1.2.3节中掌握了如何避免不必要的编译,但真到了实践中却会发现很难操作。前面的实例还仅仅只有两个源程序,我们尚且能够判断修改了哪一个源程序。如果源程序更多,该怎么办呢?
GNU make(简称make)是Linux中一个常见的构建工具。Windows上也有类似的工具,称为NMake,语法与make不尽相同,也并不常用,因为大家往往更倾向于直接使用与Visual Studio集成度更高、功能更强大的MSBuild构建工具。那么不妨先重点看一下make的用法。
使用make工具
make构建工具会根据Makefile规则文件来进行构建。简言之,Makefile规则文件是由一系列面向目标的规则构成的。用于1.2.3小节实例的Makefile如代码清单1.6所示,注意Makefile中的缩进必须使用制表符(Tab键)而非空格。
代码清单1.6 ch001/Makefile/Makefile
main: main.o slow.o
g++ -o main main.o slow.o
main.o: main.cpp
g++ -c main.cpp -o main.o
slow.o: slow.cpp
g++ -c slow.cpp -o slow.o
其中,冒号前面的是构建目标,冒号后面的是依赖目标。这里会建立构建目标对依赖目标的依赖关系,make能够据此安排好各个目标构建的次序。每个规则下面缩进的部分,就是构建这一目标所需要执行的命令。
另外,Makefile中对GCC/G++编译器指定的-o参数可以指定生成的目标文件的名称。
下面先使用make编译全部目标,第一次构建需要花费较长时间:
$ cd CMake-Book/src/ch001/Makefile
$ cp ../漫长等待/*.cpp ./
$ make
g++ -c main.cpp -o main.o
g++ -c slow.cpp -o slow.o
g++ -o main main.o slow.o
$ ./main
斐波那契数列第25项为:75025
make会将当前工作目录中的名为Makefile的文件作为默认规则文件。因此,这里调用make时不必指定 Makefile的文件名。如果需要指定自定义的Makefile文件名,可以使用-f参数。
修改主程序main.cpp(通过复制并覆盖文件的方式),再次调用make命令构建项目,可以看到 make只按需编译了变更的main.cpp:
$ cp -f ../按需编译/main.cpp
$ make
g++ -c main.cpp -o main.o
g++ -o main main.o slow.o
$ ./main
The 25th item of Fibonacci Sequence is:75025
第三次构建,make没有做任何操作,并友好地提示我们main已经是最新的了:
$ make
make: 'main' is up to date.
如果想使用Clang编译器而非GCC,也可以在调用make的时候加上参数:
$ make CXX=clang++
那么,make到底是怎么知道哪些源程序做了修改的呢?其实这里没有使用什么奇技淫巧,它只是简单地对比了一下构建目标与依赖目标的修改日期。但凡有一条构建规则中的依赖目标比构建目标更新,这一规则对应的命令就会被重新执行。
使用NMake工具
接下来简要介绍一下Windows平台中NMake的使用。首先是规则文件Makefile的书写方式,如代码清单1.7所示。
代码清单1.7 ch001/Makefile/NMakefile
main.exe: main.obj slow.obj
cl /Fe"main.exe" main.obj slow.obj
main.obj: main.cpp
cl /c main.cpp /Fo"main.obj" /EHsc
slow.obj: slow.cpp
cl /c slow.cpp /Fo"slow.obj"
其与前Makefile最直观的不同还是编译器参数不同:
● /Fe指生成可执行文件的名称;
● /Fo指生成目标文件的名称;
● MSVC的目标文件扩展名一般为.obj而不是.o。
另外在后面的示例代码中,为了与make的Makefile做区分,NMake的Makefile文件均命名为NMakefile。由于NMake同样会将Makefile作为默认文件名,这里需要使用/F参数指定自定义文件名为NMakefile。
首先,使用NMake第一次构建项目,这同样需要漫长的等待:
> cd CMake-Book\src\ch001\Makefile
> copy ..\漫长等待\*.cpp .\
> nmake /F NMakefile
> main.exe
斐波那契数列第25项为:75025
然后,修改主程序后再次构建项目。注意,这里没有使用copy命令来修改源文件,因为Windows中的copy命令会保留被复制文件的修改时间,因而NMake不会认为这个main.cpp比main.exe更新,也就不会重新编译它了。这里改用type命令和重定向输出文件来模拟对文件的修改。当然,本书是为了方便演示才通过复制文件来修改源程序。正常开发中肯定会使用编辑器直接修改源程序,也就不会存在这种问题。
> type ..\按需编译\main.cpp > main.cpp
> nmake /F NMakefile
> main.exe
The 25th item of Fibonacci Sequence is:75025
第二次构建同样只会按需编译变更的文件,耗时很短。
make工具小结
make简约而不简单。它实在太灵活了,有时候会让人无所适从。尤其是面临跨平台需求时,其不足就很明显了。
与其说make是构建工具,倒不如说它是一个面向目标规则的命令行工具。归根结底,它只是根据规则推导出执行命令的顺序罢了,并非是一个专门针对构建某类程序的工具。换句话说,即使能够使得make在Windows操作系统上运行[4],从而避免NMake与之语法不同的问题,我们也不能够用简单的一份Makefile 来完成跨平台的C和C++程序构建。这里最明显的问题就是MSVC编译器的参数写法和GCC/Clang并不兼容,另外还有其他更多的与平台相关的问题。很多问题也许有一些解决方法,如使用Cygwin、MinGW等,但终究受限很多。总而言之,make并不是一个适合跨平台程序构建的工具。
[4]GnuWin32就是make的一个Windows构建版本。另外,也可以通过WSL在Windows上使用make调用MSVC编译器,WSL是可以同时调用Windows和 Linux应用程序的。