因为Github的流行,越来越多的项目都来自于github。而github上面推荐的构建就是makefile加上本身良好的跨平台特性,于是现在无论是linux平台还是windows平台包括MSFT自己的一些项目都采用了Makefile来进行build。
所以作为一个windows平台下的技术人员,学会makefile的一些基础只是也是有必要的。
Makefile作为一个build工具,如同VS那样的IDE一样,可以基于同一份code base通过不同的build option就可以生成不同的最终目标文件。其中Make主要是针对C/C++。其他语言例如Java有自己的Ant和Maven。
正如上面说的类似于VS那样,可以管理编译器,源文件,库文件等,Makefile都是通过文本文件来控制这一切。 尽管我们通常编写Makefile来编译一些C/C++程序或库,但是Makefile本身提供的是一套脚本,通过make来执行。虽然你完全能够使用Makefile来完成一些比如仅仅是输出,拷贝或者生成一些文本文件的任务,但Makefile这个脚本中的语法以及内建函数以及预定义会使得用来构建编译项目非常的方便。
例如我们可以利用echo命令来输出一串字符串。
- all:
- echo "only ouput a string!"
运行Makefile可以用多种应用,不同平台有自己的实现,如果是linux平台上,直接就能使用make来执行makefile。 如果是在windows上,windows SDK中也提供了nmake可以用来执行makefile。通过/?选项就可以查看如何使用。
- c:>nmake /?
-
- Microsoft (R) Program Maintenance Utility Version 14.31.31105.0
- Copyright (C) Microsoft Corporation. All rights reserved.
-
- Usage: NMAKE @commandfile
- NMAKE [options] [/f makefile] [/x stderrfile] [macrodefs] [targets]
-
- Options:
-
- /A Build all evaluated targets
- /B Build if time stamps are equal
- /C Suppress output messages
- /D Display build information
- /E Override env-var macros
- /ERRORREPORT:{NONE|PROMPT|QUEUE|SEND} Report errors to Microsoft
- /G Display !include filenames
- /HELP Display brief usage message
- /I Ignore exit codes from commands
- /K Build unrelated targets on error
- /N Display commands but do not execute
- /NOLOGO Suppress copyright message
- /P Display NMAKE information
- /Q Check time stamps but do not build
- /R Ignore predefined rules/macros
- /S Suppress executed-commands display
- /T Change time stamps but do not build
- /U Dump inline files
- /Y Disable batch-mode
- /? Display brief usage message
默认情况下运行make,会默认在当前目录下面寻找makefile并执行,当然也能够指定执行执行的文件。下面显示了如果我们执行了之前我们写的一个makefile。
- C:\>nmake all
- echo "only ouput a string!"
- only ouput a string!
通过之前我们看到的nmake的帮助所显示的make所能接受的参数
NMAKE [options] [/f makefile] [/x stderrfile] [macrodefs] [targets]
我们能知道all就是我们所指定的target。这告诉了nmake去执行all这个target。也可以不指定target,则nmake就会执行第一个遇到的target。接下来的问题就是什么是target?
和普通的脚本语言有一个入口函数,然后顺序往下执行不同的是Makefile的执行流程是通过依赖驱动的。而依赖target所在的部分也被称为描述块(Description Block)。其语法如下
- targets... : dependents...
- commands...
其执行顺序为
目标可以是一个字符串,如同之前我们所创建的all,就是一个目标。也能使一个文件,例如:
- # this is comment
- test.exe: test.obj
- LINK test
执行该makefile时,会检查test.exe这个文件是否存在或者是否新于test.obj。如果不是则执行LINK test这句命令,之后也就会得到test.exe。需要注意的是并没有强制规定命令就一定会生成目标文件,不过通常这样做没有意义。
同时#符号表示注释。
同时目标和依赖也能指定多个,命令也能够设置多行。同样依赖项本身也能作为一个目标例如
- test1.exe test2.exe : test1.obj test2.obj test.3.exe
- LINK test1
- LINK test2
- test3.exe : test3.obj
- LINK test3
描述块可以看作是makefile脚本的核心流程控制流,通过层层依赖就能够生成最终的文件。
对于目标还有一点值得注意。clean是一个比较特殊的target,虽然语法没有强制规定该目标需要完成的任务,但是大家都会将其作为清空之前构建出来的文件。所以几乎所有makefile构建的项目都能使用make clean 来清空项目。
脚本不会光靠控制流来构建整个项目,也有类似于其他语言变量一样的部分,这里称之为宏定义(Macro), 也被称之为变量(Variables)。MSFT称之为宏,因此下面都用宏来命名。
宏的语法非常简单,就和其他语言的赋值语法类似
- macro_name=string
- macro_name:=string
宏的值可以由已定义的宏组成。而在使用宏时需要加上$(macro_name)。
之前提到过注释为#开头,如果向赋值为#,则需要通过^#来转义。同样,因为string的内容可以很长,需要换行,和C规则一样,可以在行尾加上\符号。同样的如果要将\作为输入,也是转义^\。
宏可以在多个地方定义,也会存在不同地方定义了多个同名的宏。所以也就有优先级概念。下面列出了可以定义宏的地方,以及生效优先级从高到底。