考察下面的示例代码:
main.c
#include <stdio.h> int main(){ printf("hello world!"); return 0; }正常情况下,通过 gcc 在命令行将其编译后产出相应文件,可执行文件或 object 文件等。
$ gcc -o main.out main.c上面命令编译后运行 main.out 可执行文件。
$ ./main.out hello world!通过 make 命令,可以将上面的编译进行有效自动化管理。通过将从输入文件到输出文件的编译无则编写成 Makefile 脚本,Make 工具将自动处理文件间依赖及是否需要编译的检测。
make 命令所使用的编译配置文件可以是 Makefile,makefile 或 GUNMake。
其中定义任务的基本语法为:
target1 [target2 ...]: [pre-req-1 pre-req-2 ...] [command1 command2 ......]上面形式也可称作是一条编译规则(rule)。
其中,
target 为任务名或文件产出。如果该任务不产出文件,则称该任务为 Phony Targets。make 内置的 phony target 有 all, install 及 clean 等,这些任务都不实际产出文件,一般用来执行一些命令。pre-req123... 这些是依赖项,即该任务所需要的外部输入,这些输入可以是其他文件,也可以是其他任务产出的文件。command 为该任务具体需要执行的 shell 命令。比如文章最开始的编译,可通过编写下面的 Makefile 来完成:
Makefile
all:main.out main.out: main.c gcc -o main.out main.c clean: rm main.out上面的 Makefile 中定义了三个任务,调用时可通过 make <target name> 形式来调用。
比如:
$ make main.out gcc -o main.out main.c产出 main.out 文件。
再比如:
$ make clean rm main.out该 clean 任务清除刚刚生成的 main.out 文件。
三个任务中,all 为内置的任务名,一般一个 Makefile 中都会包含,当直接调用 make 后面没有跟任务名时,默认执行的就是 all。
$ make gcc -o main.out main.c如果一条编译规则中所要执行的 shell 命令有单条很长的情况,可通过 \ 来换行。
main.out: main.c gcc \ -o main.out \ main.c注意 \ 与命令结尾处需要间隔一个空格,否则识别出错。
main.out: main.c gcc\ # ? -o main.out\ # ? main.c前面调用 all 的效果等同于调用 main.out 任务,因为 all 的输入依赖为 main.out 文件。Make 在执行任务前会先检查其输入的依赖项,执行 all 时发现它依赖 main.out 文件,于是本地查找,发现本地没有,再从 Makefile 中查找看是否有相应任务会产生该文件,结果确实有相应任务能产生该文件,所以先执行能够产生依赖项的任务。
使用 Makefile 进行编译有个好处是,在执行任务时,它会先检查依赖项是否比需要产出的文件新,如果说依赖项更新新,则说明我们需要产出的目标文件属于过时的产物,需要重新生成。
什么意思。比如上面的示例,当执行
$ make main.out试图生成 main.out 产出时,会检查这个任务的依赖文件 main.c 是否有修改过。
比如前面我们已经执行过该任务产生过 main.out。再次执行时,会得到如下提示:
$ make main.out make: `main.out' is up to date.NOTE: 上面是 Mac 上最新版本的 Make 工具(GNU Make 3.81)的提示语,老版或其他变种工具得到的可能是 Nothing to be done for \main.out` `。
现在对输入文件 main.c 进行修改:
#include <stdio.h> int main(){ - printf("hello world!"); + printf("hello wayou!"); return 0; }再次执行 make main.out 会发现任务正常执行并产生了新的输出,
$ make main.out gcc -o main.out main.c $ ./main.out hello wayou!⏎这里 main.c 修改后,它在文件上来说,就比 main.out 更新了,所以我们说 main.out 这个目标, 过时(out-dated) 了。
过时的任务才会被重新执行,而未过时的会跳过,并输出相应信息。
以上,Makefile 天然实现了增量编译的效果,在大型项目下会节省不少编译时间,因为它只编译过期的任务。
需要注意的是,phony 类型的任务永远都属于过时类型,即,每次 make 都会执行。因为这种类型的任务它没有文件产出,就无所谓检查,它的主体只是调用了另外的命令而以。
拿这里的 all 来说,当我们执行 make 或 make all 时,得到:
$ make make: Nothing to be done for `all'.这里看不出来 all 有没有执行,因为目前它还没有包含任何一句命令,调用 all 后实际执行的是它的依赖文件 main.out 中的任务,而因为后者已经是最新的了,所以无须执行,所以得到了如上的输出。
为了验证 phony 类型任务是否每次都执行,向 all 及 main.out 中添加 echo 命令打印一些信息、
all:main.out + echo "[all] done" main.out: main.c gcc -o main.out main.c + echo "[main.out] done" clean: rm main.out再次执行:
$ make echo "[all] done" [all] done $ make echo "[all] done" [all] done $ make main.out make: `main.out' is up to date.可以看到,属于 phony 类型的任务 all 每次都会执行其中定义的 shell 命令,而非 phony 类型的任务 main.out 则走了增量编译的逻辑。
Makefile 中可使用变量(宏)来让脚本更加灵活和减少冗余。
其中变量使用 $ 加圆括号或花括号的形式来使用,$(VAR),定义时类似于 C 中定义宏,所以变量也可叫 Makefile 中的宏,
CC=gcc这里定义 CC 表示 gcc 编译工具。然后在后续编译命令中,就可以使用 $(CC) 代替 gcc 来书写 shell 命令了。
+ CC=gcc all:main.out main.out: main.c - gcc -o main.out main.c + $(CC) -o main.out main.c clean: rm main.out这样做的好处是什么?因为编译工具可能随着平台或环境或需要编译的目标不同,而不同。比如 gcc 只是用来编译 C 代码的,如果是 C++ 你可能要用 g++ 来编译。如果是编译 WebAssembly 则需要使用 emcc。
无论怎样变,我们只需要修改定义在文件开头的 CC 变量即可,无须修改其他地方。这当然只是其中一点好处。
自动变量/Automatic Variables 是在编译规则匹配后工具进行设置的,具体包括:
$@:代表产出文件名$*:代表产出文件名不包括扩展名$<:依赖项中第一个文件名$^:空格分隔的去重后的所有依赖项$+:同上,但没去重$?:同上,但只包含比产出更新的那些依赖这些变量都只有一个符号,区别于正常用字母命名的变量需要使用 $(VAL) 的形式来使用,自动变量无需加括号。
利用自动变量,前面示例可改造成:
CC=gcc TARGET=main.out all:$(TARGET) $(TARGET): main.c $(CC) -o $@ $^ clean: rm $(TARGET)减少了重复代码,更加易于维护,需要修改时,改动比较小。
可通过 VPATH 指定依赖文件及产出文件的搜索目录。
VPATH = src include通过小写的 vpath 可指定具体的文件名及扩展名类型,
vpath %.c src vpath %.h include此处 % 表示文件名。
像这种,只定义了产出与依赖没包含任务命令的无则,叫作依赖无则。因为它只定义了某个产出依赖哪些输入,故名。
这种规则可达到这种效果,即,右边任何文件有变更,左边的产出便成为过时的了。
区别于明确指定了产出与依赖,如果一条规则包含通配符,则称作匹配规则(Pattern Rules)。
比如,
%.o: %.c gcc -o $@ $^上面定义了这么一条编译规则,将所有匹配到的 c 文件编译成 Object 产出。
有什么用?
这种规则一般不是直接调用的,是被其他它规则触间接使用。比如上面的依赖规则。
%.o : %.cpp g++ -g -o $@ -c $< Main.o : Main.h Test1.h Test2.h Test1.o : Test1.h Test2.h Test2.o : Test2.h当右侧这些头文件有变动时,左边的产出会在 make 时被检测到过时,于是会被执行。当执行时匹配规则 %.o 会被匹配到,所以匹配规则里面的命令会执行,从而将 cpp 文件编译成相应 Object 文件。达到了依赖更新后批量更新产出的目的,而不写成这样:
Main.o : Main.h Test1.h Test2.h g++ -g -o $@ -c $< Test1.o : Test1.h Test2.h g++ -g -o $@ -c $< Test2.o : Test2.h g++ -g -o $@ -c $<添加 lame 依赖到项目并将其编译打包。
首先下载并解压 lame 到项目目录:
$ wget https://sourceforge.net/projects/lame/files/lame/3.100/lame-3.100.tar.gz $ tar -zxvf lame-3.100.tar.gz主程序中调用 lame,这只仅简单地打印其版本信息:
main.c
#include <stdio.h> #include "./lame-3.100/include/lame.h" int main() { const char* ver = get_lame_version(); printf("lame ver: %s", *ver); return 0; }编译项目的 Makefile:
Makefile
CC=gcc TARGET=main.out ENTRY=main.c LAME_DIR=lame-3.100 all: $(TARGET) $(TARGET): $(ENTRY) $(LAME_DIR)/libmp3lame/.libs/libmp3lame.a $(CC) -o $@ \ $^ $(LAME_DIR)/libmp3lame/.libs/libmp3lame.a: cd $(LAME_DIR); ./configure ; make clean: rm $(TARGET)编译并运行:
$ make $ ./main.out lame ver: 3⏎转载于:https://www.cnblogs.com/Wayou/p/gnu_make_tutorial.html