Makefile

Posted lxp-never

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Makefile相关的知识,希望对你有一定的参考价值。

Makefile简介

  很多大型项目的编译都是通过Makefile来组织各种库和代码之间的依赖关系。Makefile不仅可以用来编译项目还可以用来组织我们一些日常操作。

  Makefile是和make命令一起配合使用的,Makefile就像shell脚本一样,同时执行操作系统的命令。

  Makefile的好处就是能够使用一行命令来完成“自动编译”,编译整个工程我们要做的唯一事情就是在shell提示符下输入make命令,整个工程就会自动编译

  make是命令工具,它解释makefile指令规则。

Makefile基本格式如下:

target ... : prerequisites ...
    command
    ...
    ...
  • target         目标文件
  • prerequisties     生成target所需要的文件或目标
  • command         make需要执行的命令(任意的shell命令),makefile中的命令必须以缩进[tab]开头

Makefile的初级语法

规则主要有两个部分:依赖关系 和 生成目标的方法

语法有以下2种

target ... : prerequisites ...
    command
    ...

target ... : prerequisites ; command
    command
    ...

*注* command太长, 可以用 "\\" 作为换行符

规则中的通配符

  • *     :表示任意一个或多个字符
  • ?     :表示任意一个字符
  • [...]   :[abcd] 表示a,b,c,d中任意一个字符, [^abcd]表示除a,b,c,d以外的字符, [0-9]表示 0~9中任意一个数字
  • ~     :表示用户的根目录

路径搜索

将源文件的路径明确在Makefile中,便于编译查找,Makefile中有个特殊的变量 VPATH 就是完成这个功能的。

如果在当前目录中没有找到相应文件或依赖的文件, Makefile 回到 VPATH 指定的路径中再去查找

VPATH 使用方法:

  • vpath <directories>          当前目录中找不到文件时, 就从<directories>中搜索
  • vpath <pattern> <directories>    符合<pattern>格式的文件, 就从<directories>中搜索
  • vpath <pattern>             清除符合<pattern>格式的文件搜索路径
  • vpath                    清除所有已经设置好的文件路径
# 示例1 - 当前目录中找不到文件时, 按顺序从 src目录 ../parent-dir目录中查找文件
VPATH src:../parent-dir   

# 示例2 - .h结尾的文件都从 ./header 目录中查找
VPATH %.h ./header

# 示例3 - 清除示例2中设置的规则
VPATH %.h

# 示例4 - 清除所有VPATH的设置
VPATH

Makefileh中的变量

变量定义 (= or :=)

OBJS = programA.o programB.o
OBJS-ADD = $(OBJS) programC.o
# 或者
OBJS := programA.o programB.o
OBJS-ADD := $(OBJS) programC.o

其中 = 和 := 的区别在于, := 只能使用前面定义好的变量, = 可以使用后面定义的变量

测试 =

# Makefile内容
OBJS2 = $(OBJS1) programC.o
OBJS1 = programA.o programB.o

all:
    @echo $(OBJS2)

# bash中执行 make, 可以看出虽然 OBJS1 是在 OBJS2 之后定义的, 但在 OBJS2中可以提前使用
$ make
programA.o programB.o programC.o

测试 :=

# Makefile内容
OBJS2 := $(OBJS1) programC.o
OBJS1 := programA.o programB.o

all:
    @echo $(OBJS2)

# bash中执行 make, 可以看出 OBJS2 中的 $(OBJS1) 为空
$ make
programC.o

变量替换

# Makefile内容
SRCS := programA.c programB.c programC.c
OBJS := $(SRCS:%.c=%.o)

all:
    @echo "SRCS: " $(SRCS)
    @echo "OBJS: " $(OBJS)

# bash中运行make
$ make
SRCS:  programA.c programB.c programC.c
OBJS:  programA.o programB.o programC.o

变量追加值 +=

# Makefile内容
SRCS := programA.c programB.c programC.c
SRCS += programD.c

all:
    @echo "SRCS: " $(SRCS)

# bash中运行make
$ make
SRCS:  programA.c programB.c programC.c programD.c

变量覆盖 override

作用是使 Makefile中定义的变量能够覆盖 make 命令参数中指定的变量

语法:

  • override <variable> = <value>
  • override <variable> := <value>
  • override <variable> += <value>
# Makefile内容 (没有用override)
SRCS := programA.c programB.c programC.c

all:
    @echo "SRCS: " $(SRCS)

# bash中运行make
$ make SRCS=nothing
SRCS:  nothing

#################################################

# Makefile内容 (用override)
override SRCS := programA.c programB.c programC.c

all:
    @echo "SRCS: " $(SRCS)

# bash中运行make
$ make SRCS=nothing
SRCS:  programA.c programB.c programC.c

目标变量

作用是使变量的作用域仅限于这个目标(target), 而不像之前例子中定义的变量, 对整个Makefile都有效.

语法:

  • <target :>  <variable-assignment>
  • <target :>  override <variable-assignment> (override作用参见 变量覆盖的介绍)
# Makefile 内容
SRCS := programA.c programB.c programC.c

target1: TARGET1-SRCS := programD.c
target1:
    @echo "SRCS: " $(SRCS)
    @echo "SRCS: " $(TARGET1-SRCS)

target2:
    @echo "SRCS: " $(SRCS)
    @echo "SRCS: " $(TARGET1-SRCS)

# bash中执行make
$ make target1
SRCS:  programA.c programB.c programC.c
SRCS:  programD.c

$ make target2     <-- target2中显示不了 $(TARGET1-SRCS)
SRCS:  programA.c programB.c programC.c
SRCS:

Makefile 命令前缀

Makefile 中书写shell命令时可以加2种前缀 @ 和 -, 或者不用前缀.

3种格式的shell命令区别如下:

  • 不用前缀    输出 执行的命令 以及 命令执行的结果, 出错的话停止执行
  • 前缀 @     只输出命令执行的结果, 出错的话停止执行
  • 前缀 -     命令执行有错的话, 忽略错误, 继续执行
# Makefile 内容 (不用前缀)
all:
    echo "没有前缀"
    cat this_file_not_exist
    echo "错误之后的命令"       <-- 这条命令不会被执行

# bash中执行 make
$ make
echo "没有前缀"             <-- 命令本身显示出来
没有前缀                    <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1

###########################################################

# Makefile 内容 (前缀 @)
all:
    @echo "没有前缀"
    @cat this_file_not_exist
    @echo "错误之后的命令"       <-- 这条命令不会被执行

# bash中执行 make
$ make
没有前缀                         <-- 只有命令执行的结果, 不显示命令本身
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1

###########################################################

# Makefile 内容 (前缀 -)
all:
    -echo "没有前缀"
    -cat this_file_not_exist
    -echo "错误之后的命令"       <-- 这条命令会被执行

# bash中执行 make
$ make
echo "没有前缀"             <-- 命令本身显示出来
没有前缀                    <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: [all] Error 1 (ignored)
echo "错误之后的命令"       <-- 出错之后的命令也会显示
错误之后的命令              <-- 出错之后的命令也会执行

伪目标

伪目标并不是一个"目标(target)", 不像真正的目标那样会生成一个目标文件.

典型的伪目标是 Makefile 中用来清理编译过程中中间文件的 clean 伪目标, 一般格式如下:

.PHONY: clean   <-- 这句没有也行, 但是最好加上
clean:
    -rm -f *.o

引用其他的 Makefile

语法: include <filename>  (filename 可以包含通配符和路径)

# Makefile 内容
all:
    @echo "主 Makefile begin"
    @make other-all
    @echo "主 Makefile end"

include ./other/Makefile

# ./other/Makefile 内容
other-all:
    @echo "other makefile begin"
    @echo "other makefile end"

# bash中执行 make
$ ll
total 20K
-rw-r--r-- 1 wangyubin wangyubin  125 Sep 23 16:13 Makefile
-rw-r--r-- 1 wangyubin wangyubin  11K Sep 23 16:15 makefile.org   <-- 这个文件不用管
drwxr-xr-x 2 wangyubin wangyubin 4.0K Sep 23 16:11 other
$ ll other/
total 4.0K
-rw-r--r-- 1 wangyubin wangyubin 71 Sep 23 16:11 Makefile

$ make
主 Makefile begin
make[1]: Entering directory `/path/to/test/makefileother makefile begin
other makefile end
make[1]: Leaving directory `/path/to/test/makefile主 Makefile end

查看C文件的依赖关系

写 Makefile 的时候, 需要确定每个目标的依赖关系.

GNU提供一个机制可以查看C代码文件依赖那些文件, 这样我们在写 Makefile 目标的时候就不用打开C源码来看其依赖那些文件了.

比如, 下面命令显示在内核源码中 virt/kvm/kvm_main.c 中的依赖关系

$ cd virt/kvm/
$ gcc -MM kvm_main.c 
kvm_main.o: kvm_main.c iodev.h coalesced_mmio.h async_pf.h   <-- 这句就可以加到 Makefile 中作为编译 kvm_main.o 的依赖关系

make 退出码

Makefile的退出码有以下3种:

  • 0  表示成功执行
  • 1  表示make命令出现了错误
  • 2  使用了 "-q" 选项, 并且make使得一些目标不需要更新

指定 Makefile, 指定特定目标

默认执行 make 命令时, GNU make在当前目录下依次搜索下面3个文件 "GNUmakefile", "makefile", "Makefile",

找到对应文件之后, 就开始执行此文件中的第一个目标(target). 如果找不到这3个文件就报错.

非默认情况下, 可以在 make 命令中 -f 指定特定的 Makefile 和特定的 目标.

# Makefile文件名改为 MyMake, 内容
target1:
    @echo "target [1]  begin"
    @echo "target [1]  end"

target2:
    @echo "target [2]  begin"
    @echo "target [2]  end"

# bash 中执行 make
$ ls
Makefile
$ mv Makefile MyMake
$ ls
MyMake
$ make                     <-- 找不到默认的 Makefile
make: *** No targets specified and no makefile found.  Stop.
$ make -f MyMake           <-- 指定特定的Makefile
target [1]  begin
target [1]  end
$ make -f MyMake target2   <-- 指定特定的目标(target)
target [2]  begin
target [2]  end

 

Makefile 隐含规则

这里只列一个和编译C相关的.

编译C时,<n>.o 的目标会自动推导为 <n>.c

# Makefile 中
main : main.o
    gcc -o main main.o

#会自动变为:
main : main.o
    gcc -o main main.o

main.o: main.c    <-- main.o 这个目标是隐含生成的
    gcc -c main.c

 

自动变量

Makefile 中很多时候通过自动变量来简化书写, 各个自动变量的含义如下:

自动变量

含义

[email protected] 目标集合
$% 当目标是函数库文件时, 表示其中的目标文件名
$< 第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标
$? 比目标新的依赖目标的集合
$^ 所有依赖目标的集合, 会去除重复的依赖目标
$+ 所有依赖目标的集合, 不会去除重复的依赖目标
$* 这个是GNU make特有的, 其它的make不一定支持

参考文献:

Makefile使用总结

Windows安装GNU编译器使用makefile

 

以上是关于Makefile的主要内容,如果未能解决你的问题,请参考以下文章

Makefile文件语法

使用带有 NMake 样式 Makefile 的 clang-cl 无法回显

如何使用 makefile 使用 Visual Studio 编译代码

重建后的Makefile重新编译代码需要很多时间

Makefile

Makefile步步为营