《跟我一起写Makefile》学习笔记(一)

  • 本文是学习陈皓先生《跟我一起写Makefile》系列blog的笔记。感谢陈皓先生撰写技术资料,为学习者作出的贡献。原文地址:跟我一起写Makefile

一. 概述

  • makefile关系到整个工程的编译规则。在一个工程中,源文件不计其数。makefile指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译等。
  • makefile像一个shell脚本一样,其中可以执行操作系统的命令。
  • makefile的好处:自动化编译。
  • make是一个用来解释makefile中指令的命令工具。

二. 关于程序的编译和链接

  • 首先把源文件编译成中间代码文件(Object File),在UINX下为(.o)。这个动作就是编译。
  • 将Object File合成可执行文件,这个动作就是链接(link)。
  • 编译时,编译器需要的是语法、函数与变量的声明的正确。只要这些正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应一个中间目标文件。
  • 链接时,主要是链接函数和全局变量。连接器只管函数的中间目标文件。由于中间目标文件太多,所以给中间目标文件打包。这种包在Windows下叫库文件(Library File),即 .lib 文件。在UNIX下叫Archive File,即 .a 文件。

三. Makefile介绍

  • make命令执行时,需要一个Makefile文件,告诉make命令需要怎样去编译和链接程序
  • 示例:工程有8个C文件,3个头文件。写一个Makefile来告诉make命令如何编译和链接这几个文件。
    • 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
    • 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
    • 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

1. Makefile规则

target ... : prerequisites ...
    command
    ...
    ...
  • 参数说明
    • target:目标文件。可能为:Object File、执行文件、标签(Label)
    • prerequisites:依赖文件
    • command:make需要执行的命令(任意shell命令)。
  • 这是一个文件依赖关系。target(目标文件,一个或多个)依赖于prerequisites(依赖文件,一个或多个)。如果prerequisites中有文件比target要新,command所定义的命令就会执行。

2. 一个示例

# edit是默认目标文件,冒号后面的.o是依赖文件
# /是换行符
edit : main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o

    # command要以Tab开始
    # cc是UNIX下用的编译命令,gcc是Linux下用的编译命令。很多makefile文件是在Unix下写的,编译用cc。但很多人喜欢用linux来编译,但把makefile中的所有cc改为gcc太麻烦,所以用连接的方法将cc连接在gcc命令上,运行cc就是运行gcc。
    # gcc中,-c表示只编译(compile)源文件但不链接,会把.c或.cc的c源程序编译成目标文件,一般是.o文件。gcc -c test.c将生成test.o的目标文件
    # -o用于指定输出(out)文件名。不用-o的话,一般会在当前文件夹下生成默认的a.out文件作为可执行程序。gcc -o app test.c将生成可执行程序app
    cc -o edit main.o kbd.o command.o display.o /
            insert.o search.o files.o utils.o

main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h comamand.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c

clean :
    rm edit main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o
  • make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期比targets文件的要新,或者target不存在,make就会执行后续定义的命令。
  • clean不是一个文件,它是一个动作的名字,有点像C语言中的lable。由于其没有依赖文件,所以make就不会自动寻找文件的依赖性,也就不会自动执行其后定义的命令。要执行其后的命令,就要在make后面显式地之处这个lable的名字。

3. make是如何工作的

  • 默认方式下(只输入make命令):
    1. make会在当前目录下寻找名为”Makefile”或”makefile”的文件。
    2. 如果找到上述文件,它会找文件中的第一个目标文件(target),并将其作为最终的目标文件(在上例中为edit)。
    3. 如果edit不存在,或者edit的依赖文件的文件修改时间比edit新,就会执行后面所定义的命令来生成edit文件。
    4. 如果edit所依赖的 .o 文件也存在,那么make会在当前的文件中寻找目标为 .o 文件的依赖性,如果找到,根据这个规则生成 .o 文件。
    5. .o文件所依赖的 .h 和 .c 文件必然是存在的,于是make生成 .o 文件,再用 .o 文件去完成make命令的终极任务——生成edit。
  • make会层层寻找文件的依赖关系,直到编译出第一个目标文件。在寻找过程中,如果出现错误(譬如最后被依赖的文件找不到),make就会直接退出并报错。对于所定义的命令的错误,或是编译不成功,make根本不管。make只管文件的依赖性,也就是说,我找了依赖关系之后,冒号后面的文件还不存在,那就不工作了。
  • 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面定义的命令不会自动执行。如果需要,要显式地加上clean。

4. makefile中使用变量

  在如下代码中,我们可以看到所有的 .o 文件都被重复了两次。如果我们要加上一个新的 .o 依赖文件,那么要在这两处都修改。那么如果有一百处呢?在这种情况下,我们可以使用变量。

edit : main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o /
            insert.o search.o files.o utils.o
  • 变量类似于C语言中的宏定义。使用变量的格式为”$(变量名)”。
    譬如,我们在makefile一开始就作如下定义:
objects = main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o
  • 在程序中,可以使用这个变量代替大量的 .o。如果有新的 .o 文件加入,只需要修改 objects 变量即可。
objects = main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)

5. 让make自动推导

  • make 可以自动推导文件以及文件依赖关系后面的命令,所以没必要再每一个 .o 文件后面都写上类似的命令。
  • 只要 make 命令看到一个 .o 文件,就会自动地把 .c 文件添加在依赖关系中,譬如 make 找到一个 whatever.o,就会自动将 whatever.c 添加在依赖文件中,并且 cc -c whatever.c 也会被推导出来。这样,上文的 makefile 可以被修改为如下形式:
objects = main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
    rm edit $(objects)
  • 上面这种方法,也就是 make 的“隐晦规则”。“.PHONY”表示 clean 是个伪目标文件。

6. 另类风格的makefile

  • 之前的 makefile 是以目标文件分条写的,我们也可以以依赖文件来分条写,但这样每个目标文件具体依赖哪几个文件就不是很明晰了。示例如下:
objects = main.o kbd.o command.o display.o /
        insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
    rm edit $(objects)

7. 清空目标文件的规则

  • 每个 makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,这不仅便于重编译,也利于保持文件的清洁。通常格式如下:
clean :
    rm edit $(objects)
  • 更为稳健的做法是:
.PHONY : clean
clean :
    rm edit $(objects)
  • rm 命令前面加 - 的意思是:也许某些文件会出现问题,但是不要管,继续坐后面的事。
  • clean 放在文件的最后。

你可能感兴趣的:(linux)