GNU make and Makefile

make是用来组织应用程序编译过程的基本工具。Linux环境下,不管是自己进行项目开发还是安装应用软件,都经常要用到make或make install。对于只包含一个或少数文件的工程,可以通过gcc命令进行编译,但是试想如果对于成百上千个源文件构成的工程,如果通过gcc命令一一进行编译,对于程序的编译来说将是一个灾难。make工具就是为了解决这个问题而开发的,它将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和makefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系;make工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。因此,有效的利用make和makefile工具可以大大提高项目开发的效率。

make 工具会读取一个包含指令的文件(这个文件的名字通常都是 makefile 或 Makefile),并执行各种操作来编译程序。makefile文件可以自己手动编写,也可以通过自动化工具如DE>autoconf/automakeDE> 程序来辅助生成。

经常使用的 make 工具至少有 3 个变种:GNU make、System V make 和 Berkeley make。它们都是从早期 UNIX 的一个核心规范发展而来的,但是在特性上会有一些细微的差异。在Linux中,我们通常采用GNU make(是免费的);有很多起源于 BSD 的项目都要求我们使用 Berkeley make(这也是免费的)。

GNU的make工作时的执行步骤入下:

1)读入所有的Makefile;

2)读入被include的其它Makefile;

3)初始化文件中的变量;

4)推导隐晦规则,并分析所有规则;

5)为所有的目标文件创建依赖关系链;

6)根据依赖关系,决定哪些目标要重新生成;

7)执行生成命令。

 一. make命令

make命本身可带有四种参数:标志、宏定义、描述文件名和目标文件名。其标准形式为: make [flags] [macro definitions] [targets]

-f file  指定makefile文件;如果没有"-f"参数,则系统将默认当前目录下名为makefile或者名为Makefile的文件为描述文件,在Linux中, GNU make 工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索 makefile文件;例如 make -f makefile.debug。 

-i   忽略命令执行返回的出错信息, 如果在执行command命令时返回了一个非"0"的出错信号,例如makefile文件中出现了错误的目标文件名或者出现了以连字符打头的命令字符串,make操作一般会就此终止,但如果make后带有"-i"参数,则make将忽略此类出错信号。 

-s   沉默模式,在执行之前不输出相应的命令行信息。

-r   禁止使用build-in规则。

-n   非执行模式,输出所有执行命令,但并不执行。

-t   更新目标文件。

-q   make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息。

-p   输出所有宏定义和目标文件描述。

-d   Debug模式,输出有关文件和检测时间的详细信息。

-c dir   在读取 makefile 之前改变到指定的目录dir。

-I dir   当包含其他 makefile文件时,利用该选项指定搜索目录。

-h   help文挡,显示所有的make选项。

-w   在处理 makefile 之前和之后,都显示工作目录。

带宏定义相关的参数:

make "LIBES= -LL -LS"

make CFLAGS=-g

在make命令后不仅可以出现宏定义,还可以跟其他命令行参数,这些参数指定了需要编译的目标文件 (例如:make run, make install, make clean)。其标准形式为:

target1 [target2 …]:[:][dependent1 …][;commands][#…] [(tab) commands][#…]

方括号中间的部分表示可选项。Targets和dependents当中可以包含字符、数字、句点和"/"符号。除了引用,commands中不能含有"#",也不允许换行。

在通常的情况下命令行参数中只含有一个":",此时command序列通常和makefile文件中某些定义文件间依赖关系的描述行有关。如果与目标相关连的那些描述行指定了相关的command序列,那么就执行这些相关的command命令,即使在分号和(tab)后面的aommand字段甚至有可能是NULL。如果那些与目标相关连的行没有指定command,那么将调用系统默认的目标文件生成规则。

果命令行参数中含有两个冒号"::",则此时的command序列也许会和makefile中所有描述文件依赖关系的行有关。此时将执行那些与目标相关连的描述行所指向的相关命令。同时还将执行build-in规则。

二.makefile语法基础

1. Makefile的规则

target ... : prerequisites ...

[tab]command

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令,命令必须以Tab键开头)

target是一个目标文件或者Object File,可以是执行文件或者是一个标签(Label)。伪目标就是一种标签。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。只有通过显示地指明这个“目标”才能让其生效。“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。为了避免和文件重名的这种情况,可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY: clean

clean:

[tab]rm *.o temp

伪目标一般没有依赖的文件。但是,也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果希望通过一个make命令来生成若干个可执行文件,那么可以利用“伪目标”的特性,将所有的目标文件都写在一个Makefile中。例如:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o

Makefile中的第一个目标会被作为其默认目标。当声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了一口气生成多个目标的目的。

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

2. 注释

Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

3.命令

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

@echo 正在编译XXX模块......

当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:

echo 正在编译XXX模块......

如果make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。而make参数“-s”或“--slient”则是全面禁止命令的显示。

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:

示例一:

exec:

cd /home/btest

pwd

示例二:

exec:

cd /home/btest; pwd

当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如“.exe”、“.com”、“.bat”、“.sh”等后缀。

4. 条件判断

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。

1). ifeq (<arg1>, <arg2>)

ifeq ($(CC),gcc)

libs=$(libs_for_gcc)

else

libs=$(normal_libs)

endif

2).ifneq (<arg1>, <arg2>)

3).ifdef <variable-name>

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name>同样可以是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子:

示例一:

bar =

foo = $(bar)

ifdef foo

frobozz = yes

else

frobozz = no

endif

示例二:

foo =

ifdef foo

frobozz = yes

else

frobozz = no

endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

4).ifndef <variable-name>

5. 宏定义

Makefile中允许使用简单的宏指代源文件及其相关编译信息,在Linux中也称宏为变量。在引用宏时只需在变量前加$符号,但值得注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号()。

下面都是有效的宏引用:

$(CFLAGS)

$2

$Z

$(Z)

其中最后两个引用是完全一致的。

# Define a macro for the object files

OBJECTS= filea.o fileb.o filec.o

# Define a macro for the library file

LIBES= -LS

# use macros rewrite makefile

prog: $(OBJECTS)

    cc $(OBJECTS) $(LIBES) -o prog

此时如果执行不带参数的make命令,将连接三个目标文件和库文件LS;但是如果在make命令后带有新的宏定义:

make "LIBES= -LL -LS"

6.

样例

CC=gcc

INCLUDES=

LIBS=-lpthread

SRC = test.c

OBJS =test.o

EXE = test

OPT = -O0

CFLAGS = -static -Wall -Werror -g $(OPT)

$(EXE).o: $(EXE).c
        $(CC) $(CFLAGS) -c $(SRC) -o $(OBJS)

$(EXE): $(EXE).o
        $(CC) $(LDFLAGS) $(OBJS) -o $(EXE)

all: $(EXE)

clean:
        rm -f *.o $(EXE) output.run output.pcap

.PHONY: all clean

参考文献:

1. GNU make 中文手册 http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html/index.html

原文地址:https://www.cnblogs.com/kevincode/p/3829454.html