Makefile 是一个用于自动化构建和编译源代码的工具,可以帮助自动处理软件项目中的复杂依赖关系,并生成最终的可执行文件、库或其他类型的目标文件。Makefile 最初设计为在 Unix 系统上使用,但现在也可以在 Windows 和其他操作系统上使用。
Makefile 中包含一组规则,这些规则描述了如何从源代码创建目标文件。每个规则都由以下三部分组成:
当运行 Makefile 时,Make 工具将按照规则中指定的顺序递归地处理所有依赖关系,并执行与每个目标相关联的命令。
下面是一个简单的 Makefile 示例,其中包含一个规则来生成名为 hello
的可执行文件:
makefileCC = gcc
CFLAGS = -Wall -Werror
SRC = hello.c
OBJ = $(SRC:.c=.o)
all: hello
hello: $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o hello
这个 Makefile 包含三个规则:
all
规则:默认规则,会在没有指定目标时被执行。在这个例子中,它只是指向了另一个规则 hello
。hello
规则:生成名为 hello
的可执行文件,它依赖于 hello.o
目标。%.o
规则:对每个 .c 文件创建一个 .o 文件,这些文件是创建可执行文件所需的依赖项。当运行 make hello
命令时,Make 工具将从 hello.c
创建 hello.o
,然后使用 hello.o
生成名为 hello
的可执行文件。如果 hello.c
没有被修改,则不需要重新编译和链接。make会根据文件修改的时间戳来检查是否被修改
当然也可以通过在 Makefile 中定义自己的目标来扩展 Makefile 的功能。如果想要将最新的代码推送到 Git 存储库中,可以按照以下步骤进行操作:
在 Makefile 中定义一个名为 push
的规则,如下所示:
makefilepush:
git add .
git commit -m "Update"
git push
运行 make push
命令,即可将最新的代码推送到存储库中。
在 Makefile 中,自动化变量是一组特殊的变量,用于表示当前正在处理的规则中的依赖项和目标文件。其中,$<
表示当前规则的第一个依赖项,$^
表示当前规则的所有依赖项,而 $@
则表示当前规则的目标文件。
例如,在 .o
规则中,可以使用以下命令将源代码编译为目标文件:
makefilehello: $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
在上面的命令中,$<
表示当前正在编译的 .c
文件,$^
表示当前的依赖项$(OBJ)
,$@
则表示当前正在生成的 .o
文件。言外之意%.o: %.c
中的 $<
也可换成$^
,效果是一样的
隐式规则是一组预定义规则,用于根据文件名模式自动生成目标文件。例如,如果需要将 .c
文件编译为 .o
文件,则可以使用以下隐式规则:
makefile%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
在此示例中,%.o
和 %.c
是文件名模式,Makefile 会自动将这些规则应用于所有符合模式的文件。因此,如果存在 foo.c
文件,则 Makefile 将使用上述规则编译并生成 foo.o
目标文件。
相比之下,显式规则是您在 Makefile 中显式指定的规则,其中包含目标、依赖项和命令。例如,以下是一个显式规则的示例:
makefilemain: main.o helper.o
$(CC) -o $@ $^
在此示例中,main
是目标文件,main.o
和 helper.o
是依赖文件,而 $(CC) -o $@ $^
是要执行的命令。
显式规则通常用于更复杂的 Makefile,其中需要手动编写大量规则来管理源代码和目标文件之间的依赖关系。
静态链接将所需的库文件嵌入到最终可执行文件中。这意味着可执行文件不依赖于系统上已安装的库文件,并且可以作为一个独立的单元进行移植和分发。
另外,静态链接还有以下优点:
但是,静态链接的缺点是:
如果有多个可执行文件使用相同的库文件,则每个文件都必须嵌入该库文件的副本,导致浪费空间和资源。
每次更新库文件时,必须重新编译和链接所有依赖库文件的可执行文件。
以下是更新后的 Makefile 示例:
静态链接
在 Makefile 中定义 LIBS
和 INCLUDES
变量,这些变量分别存储库文件名和头文件路径。
makefileLIBS = -lm
INCLUDES = -I ./include
更新 $(CC)
命令,以便使用 -static
标志进行静态链接,并且将头文件路径传递给编译器。
makefile$(CC) -static -o $@ $^ $(INCLUDES) $(LIBS)
动态链接将所需的库文件作为单独的文件提供,并且在运行时通过共享库文件在内存中加载它们。这意味着可执行文件不包含库文件,而是在运行时从系统上已安装的共享库中获取所需的函数和代码。
动态链接的优点包括:
但是,动态链接的缺点是:
动态链接
在 Makefile 中定义 LDFLAGS
和 INCLUDES
变量,这些变量分别存储库文件名和头文件路径。
makefileLDFLAGS = -lm
INCLUDES = -I ./include
更新 $(CC)
命令,以便使用 -shared
标志进行动态链接,并且将头文件路径传递给编译器。
makefile$(CC) -shared -o $@ $^ $(INCLUDES) $(LDFLAGS)
在编译完毕后自动运行可执行文件。这可以通过 Makefile 中的 .PHONY
和 run
规则来实现。具体步骤如下:
定义 run
规则,该规则依赖于可执行文件,并在命令中使用 ./
运行可执行文件。
makefilerun: hello
./hello
在 .PHONY
规则中将 run
添加为一个伪目标,并确保它不会被误认为是一个文件或目录名称。
makefile.PHONY: all clean run
在上述示例中,.PHONY
规则指定了三个伪目标:all
、clean
和 run
。其中 run
是我们新添加的伪目标。
最后,在 Makefile 的结尾处添加 run
规则作为默认目标,以便在编译并生成可执行文件后自动运行它。
makefileall: hello
hello: $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o hello
run: hello
./hello
现在,当运行 make
命令时,Makefile 将依次执行 all
规则和 run
规则。all
规则将编译并生成可执行文件,而 run
规则将自动运行该文件。此外,也可以在 Makefile 中的其他规则中添加其他操作,例如清理过程或其他构建步骤。
.PHONY
是一个特殊的规则,用于指定伪目标。它告诉 Make 工具,该目标不是实际的文件或目录名称,而只是一个标记,用于表示要执行的操作。.PHONY
规则通常与伪目标一起使用,例如 all
、clean
和 run
等。
在 Makefile 中,如果某个目标的名称与已存在的文件或目录名称相同,则 Make 工具可能会将其误认为是文件或目录,并不会执行该目标所指定的命令。因此,通过在 .PHONY
规则中指定这些目标名称,可以确保它们被正确地处理为伪目标,从而使得 Make 工具能够正确地执行它们所指定的命令。
下面是一个示例:
makefile.PHONY: all clean
all:
$(CC) -o hello main.c
clean:
-rm -f hello
在上述示例中,.PHONY
规则指定了两个伪目标:all
和 clean
。当您运行 make all
或 make clean
命令时,Make 工具将会执行对应的命令。由于 all
和 clean
不是实际的文件或目录名称,因此需要通过 .PHONY
规则来指定它们为伪目标。
注意,.PHONY
规则不会生成任何实际的文件或目录,它只是告诉 Make 工具哪些目标名称应该被视为伪目标。在大多数情况下,并不需要使用 .PHONY
规则,除非项目的 Makefile 中存在与文件或目录名称相同的目标名称。
前面说过, .PHONY
表示 clean
是一个“伪目标”。而在 rm
命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然, clean
的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。
VPATH,即文件搜寻。在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
Makefile文件中的特殊变量 VPATH
就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
vpath <pattern> <directories>
为符合模式的文件指定搜索目录。
vpath <pattern>
清除符合模式的文件的搜索目录。
vpath
清除所有已被设置好了的文件搜索目录。
vpath使用方法中的需要包含 %
字符。 %
的意思是匹配零或若干字符,(需引用 %
,使用 \
)例如, %.h
表示所有以 .h
结尾的文件。指定了要搜索的文件集,而则指定了< pattern>的文件集的搜索的目录。例如:
vpath %.h ../headers
该语句表示,要求make在“../headers”目录下搜索所有以 .h
结尾的文件。(如果某文件在当前目录没有找到的话)
可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 ,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如:
vpath %.c foo vpath % blish vpath %.c bar
其表示 .c
结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
vpath %.c foo:bar vpath % blish
而上面的语句则表示 .c
结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
Makefile里有什么?
Makefile里主要包含了五个东西:显式规则、隐式规则、变量定义、指令和注释。
#
字符,这个就像C/C++中的 //
一样。如果你要在你的Makefile中使用 #
字符,可以用反斜杠进行转义,如: \#
。最后,还值得一提的是,在Makefile中的命令,必须要以 Tab
键开始。
Makefile的文件名
默认的情况下,make命令会在当前目录下按顺序寻找文件名为 GNUmakefile
、 makefile
和 Makefile
的文件。在这三个文件名中,最好使用 Makefile
这个文件名,因为这个文件名在排序上靠近其它比较重要的文件,比如 README
。最好不要用 GNUmakefile
,因为这个文件名只能由GNU make
,其它版本的 make
无法识别,但是基本上来说,大多数的 make
都支持 makefile
和 Makefile
这两种默认文件名。
当然,可以使用别的文件名来书写Makefile,比如:“Make.Solaris”,“Make.Linux”等,如果要指定特定的Makefile,可以使用make的 -f
或 --file
参数,如: make -f Make.Solaris
或 make --file Make.Linux
。如果使用多条 -f
或 --file
参数,你可以指定多个makefile。
包含其它Makefile
在Makefile使用 include
指令可以把别的Makefile包含进来,这很像C语言的 #include
,被包含的文件会原模原样的放在当前文件的包含位置。 include
的语法是:
include <filenames>...
<filenames>
可以是当前操作系统Shell的文件模式(可以包含路径和通配符)。
在 include
前面可以有一些空字符,但是绝不能是 Tab
键开始。 include
和 <filenames>
可以用一个或多个空格隔开。举个例子,有这样几个Makefile: a.mk
、 b.mk
、 c.mk
,还有一个文件叫 foo.make
,以及一个变量 $(bar)
,其包含了 bish
和 bash
,那么,下面的语句:
include foo.make *.mk $(bar)
等价于:
include foo.make a.mk b.mk c.mk bish bash
make命令开始时,会找寻 include
所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的 #include
指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
-I
或 --include-dir
参数,那么make就会在这个参数所指定的目录下去寻找。<prefix>/include
(一般是 /usr/local/bin
)、 /usr/gnu/include
、 /usr/local/include
、 /usr/include
。环境变量 .INCLUDE_DIRS
包含当前 make 会寻找的目录列表。你应当避免使用命令行参数 -I
来寻找以上这些默认目录,否则会使得 make
“忘掉”所有已经设定的包含目录,包括默认目录。
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
-include <filenames>...
其表示,无论include过程中出现什么错误,都不要报错继续执行。如果要和其它版本 make
兼容,可以使用 sinclude
代替 -include
。
环境变量MAKEFILES
如果当前环境中定义了环境变量 MAKEFILES
,那么make会把这个变量中的值做一个类似于 include
的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 include
不同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
建议不要使用这个环境变量,因为只要这个变量一被定义,那么当使用make时,所有的Makefile都会受到它的影响。
make的工作方式
GNU的make工作时的执行步骤如下:
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
本文作者:phae
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!