makefile入门1

这篇文章以我的一个小项目为例,阐述了面向 GNU Make 的 Makefile 文件的基本写法。由于我未认真阅读 GNU Make 的文档,并且对于符合 POSIX 标准的 Makefile 格式并不了解,所以我写的 Makefile 可能不甚严肃,还请擅长此道者不吝赐教。

我的小项目

这个项目基于 C 语言实现,但由于我是文式编程爱好者,项目文档与代码交织在扩展名为 .orz 的源文件中。在构建项目时,需要使用我自己写的文式编程处理工具 orez 从 .orz 文件中提取 C 代码,然后用 gcc 进行编译。

项目名曰 agn,其目录结构如下:

agn
├── orez-src   # 存放 .orz 文件
└── src        # 存放由 .orz 文件中提取的 C 源码

从 .orz 文件中提取 C 源码

先看一下 agn/orez-src 目录中有哪些文件:

$ cd agn/orez-src
$ ls
agn_arena.orz          agn_km.orz              agn_simplex.orz
agn_array.orz          agn_linear_algebra.orz  agn_tree.orz
agn_build.orz          agn_list.orz            agn_types.orz
agn_delaunay_mesh.orz  agn_point.orz           agn_vector.orz
agn_hash_table.orz     agn_points.orz
agn_kd_tree.orz        agn_pqueue.orz

以 agn_array.orz 为例,使用以下命令从中提取 C 源码文件:

$ orez -t -e ang_array.h ang_array.orz
$ orez -t -e agn_array.c agn_array.orz

对于每份 .orz 文件,皆可采用以上方式提取相应的 C 源码文件,唯独 agn_build.orz 文件例外,因为它未包含 C 源码。

下面来看如何为这件事在 agn/orez-src 目录内写一份 Makefile。

Makefile 的基本知识

Makefile 本质上是一棵树,它的每个结点称为「目标」,每个结点的子结点称为「依赖」。例如:

all: agn_arena.h agn_arena.c \
     agn_array.h agn_array.c \
     agn_hash_table.h agn_hash_table.c \
     ... ... ...

Makefile 中的第一个目标就是 Makefile 所描述的树的根结点。

目标 all 的依赖是要从上述除 agn_build.orz 之外的所有 .orz 文件中提取的 .h 与 .c 文件。反斜线是续行符。若不用续行符,就只能将所有依赖写在同一行。

all 的每个依赖本身也是 Makefile 所描述的树的结点,它们也可能有子结点(依赖)。例如:

agn_arena.h: agn_arena.orz
agn_arena.c: agn_arena.orz
... ... ...

agn_arena.h 与 agn_arena.c 都依赖 agn_arena.orz。同理,agn_arena.orz 也有依赖,不过它的依赖有些特殊,它的依赖是我,因为所有的 .orz 是我手写的。由于我不能真正地将我放在 Makefile 里作为 .orz 文件的依赖,因此依赖链就到 .orz 文件为止,它们就构成了 Makefile 所描述的树的叶结点。

我的任务是要用 Makefile 从 .orz 文件中提取 .h 与 .c 文件,这个任务需要由附加在 Makefile 目标上的命令来执行。Makefile 允许在每个目标的下面放置一组 Shell 命令。例如,可以将从 agn_array.orz 文件中提取 agn_array.h 与 agn_array.c 的命令分别放在 agn_array.h 与 agn_array.c 这两个目标的下面:

agn_arena.h: agn_arena.orz
    orez -t -e ang_array.h ang_array.orz
agn_arena.c: agn_arena.orz
    orez -t -e ang_array.c ang_array.orz

每条命令之前要用 Tab 键进行缩进。记住,一定用 Tab 键,不要用空格!一定用 Tab 键,不要用空格!一定用 Tab 键,不要用空格!

基于目标、依赖以及命令,便可以写出 Makefile 的一条条规则。这些规则的格式如下:
目标: [依赖列表]
<Tab 缩进>[命令]

规则中的依赖列表与命令是可选的。

有耐心的人写的 Makefile

有了上述基本知识,我就可以写出可以从一组 .orz 文件提取 C 源码的 Makefile:

all: agn_arena.h agn_arena.c \
     agn_array.h agn_array.c \
     agn_delaunay_mesh.h agn_delaunay_mesh.c \
     agn_hash_table.h agn_hash_table.c \
     agn_kd_tree.h agn_kd_tree.c \
     agn_km.h agn_km.c \
     agn_linear_algebra.h agn_linear_algebra.c \
     agn_list.h agn_list.c \
     agn_point.h agn_point.c \
     agn_points.h agn_points.c \
     agn_pqueue.h agn_pqueue.c \
     agn_simplex.h agn_simplex.c \
     agn_tree.h agn_tree.c \
     agn_types.h agn_types.c \
     agn_vector.h agn_vector.c
agn_agn_arena.h: agn_agn_arena.orz
    orez -t -e agn_agn_arena.h agn_agn_arena.orz
agn_agn_arena.c: agn_agn_arena.orz
    orez -t -e agn_agn_arena.c agn_agn_arena.orz
agn_agn_array.h: agn_agn_array.orz
    orez -t -e agn_agn_array.h agn_agn_array.orz
agn_agn_array.c: agn_agn_array.orz
    orez -t -e agn_agn_array.c agn_agn_array.orz
agn_agn_delaunay_mesh.h: agn_agn_delaunay_mesh.orz
    orez -t -e agn_agn_delaunay_mesh.h agn_agn_delaunay_mesh.orz
agn_agn_delaunay_mesh.c: agn_agn_delaunay_mesh.orz
    orez -t -e agn_agn_delaunay_mesh.c agn_agn_delaunay_mesh.orz
agn_agn_hash_table.h: agn_agn_hash_table.orz
    orez -t -e agn_agn_hash_table.h agn_agn_hash_table.orz
agn_agn_hash_table.c: agn_agn_hash_table.orz
    orez -t -e agn_agn_hash_table.c agn_agn_hash_table.orz
agn_agn_kd_tree.h: agn_agn_kd_tree.orz
    orez -t -e agn_agn_kd_tree.h agn_agn_kd_tree.orz
agn_agn_kd_tree.c: agn_agn_kd_tree.orz
    orez -t -e agn_agn_kd_tree.c agn_agn_kd_tree.orz
agn_agn_km.h: agn_agn_km.orz
    orez -t -e agn_agn_km.h agn_agn_km.orz
agn_agn_km.c: agn_agn_km.orz
    orez -t -e agn_agn_km.c agn_agn_km.orz
agn_agn_linear_algebra.h: agn_agn_linear_algebra.orz
    orez -t -e agn_agn_linear_algebra.h agn_agn_linear_algebra.orz
agn_agn_linear_algebra.c: agn_agn_linear_algebra.orz
    orez -t -e agn_agn_linear_algebra.c agn_agn_linear_algebra.orz
agn_agn_list.h: agn_agn_list.orz
    orez -t -e agn_agn_list.h agn_agn_list.orz
agn_agn_list.c: agn_agn_list.orz
    orez -t -e agn_agn_list.c agn_agn_list.orz
agn_agn_point.h: agn_agn_point.orz
    orez -t -e agn_agn_point.h agn_agn_point.orz
agn_agn_point.c: agn_agn_point.orz
    orez -t -e agn_agn_point.c agn_agn_point.orz
agn_agn_points.h: agn_agn_points.orz
    orez -t -e agn_agn_points.h agn_agn_points.orz
agn_agn_points.c: agn_agn_points.orz
    orez -t -e agn_agn_points.c agn_agn_points.orz
agn_agn_pqueue.h: agn_agn_pqueue.orz
    orez -t -e agn_agn_pqueue.h agn_agn_pqueue.orz
agn_agn_pqueue.c: agn_agn_pqueue.orz
    orez -t -e agn_agn_pqueue.c agn_agn_pqueue.orz
agn_agn_simplex.h: agn_agn_simplex.orz
    orez -t -e agn_agn_simplex.h agn_agn_simplex.orz
agn_agn_simplex.c: agn_agn_simplex.orz
    orez -t -e agn_agn_simplex.c agn_agn_simplex.orz
agn_agn_tree.h: agn_agn_tree.orz
    orez -t -e agn_agn_tree.h agn_agn_tree.orz
agn_agn_tree.c: agn_agn_tree.orz
    orez -t -e agn_agn_tree.c agn_agn_tree.orz
agn_agn_types.h: agn_agn_types.orz
    orez -t -e agn_agn_types.h agn_agn_types.orz
agn_agn_types.c: agn_agn_types.orz
    orez -t -e agn_agn_types.c agn_agn_types.orz
agn_agn_vector.h: agn_agn_vector.orz
    orez -t -e agn_agn_vector.h agn_agn_vector.orz
agn_agn_vector.c: agn_agn_vector.orz
    orez -t -e agn_agn_vector.c agn_agn_vector.orz

一个人,无论他是不是写 Makefile,当他看到上面这份 Makefile 的时候,可能会笑起来,这肯定是世界上最糟糕的程序员写出来的 Makefile。虽然很糟糕,但是却很能体现耐心。

将这份 Makefile 放在 agn/orez-src 目录,然后在该目录中执行

$ make

便可从每份 .orz 文件中抽取相应的 .h 与 .c 文件。

模式规则

上一节那份很冗长的 Makefile 中,所有的 .h 与 .c 目标与它们的 .orz 依赖,它们的名称除后缀不同之外,其他部分是相同的。因此,所有的 .h 与 .c 目标都在重复下面这个模式:

%.h: %.orz
    命令
%.c: %.orz
    命令

在 Makefile 中,上述形式的目标与依赖称为模式规则。利用模式规则,可将所有的 .h 与 .c 目标对应的规则替换为上述两条规则。

模式规则虽然能够大幅简化 Makefile,但是带来一个新的问题,在模式规则中怎么写命令?在普通规则中,目标与依赖的名称都是确定的,由依赖产生目标的命令也是确定的,例如:

agn_array.h: agn_array.orz
    orez -t -e ang_array.h ang_array.orz

若将其改写成模式规则,是像下面这样吗?

%.h: %.orz
    orez -t -e %.h %.orz

这是错的!

模式规则中的命令,在使用目标与依赖的名称时,需要使用 Makefile 规定的符号。
$@符号指代目标名称。(依赖可能是一个列表)
$< 符号指代依赖列表中第一个依赖。

这两个符号也可以在普通规则中使用。请记住它们!

上述那个错误的模式规则应改为:

%.h: %.orz
    orez -t -e $@ $<

基于上述知识,现在给出大幅简化的 Makefile 的完整内容:

all: agn_arena.h agn_arena.c \
     agn_array.h agn_array.c \
     agn_delaunay_mesh.h agn_delaunay_mesh.c \
     agn_hash_table.h agn_hash_table.c \
     agn_kd_tree.h agn_kd_tree.c \
     agn_km.h agn_km.c \
     agn_linear_algebra.h agn_linear_algebra.c \
     agn_list.h agn_list.c \
     agn_point.h agn_point.c \
     agn_points.h agn_points.c \
     agn_pqueue.h agn_pqueue.c \
     agn_simplex.h agn_simplex.c \
     agn_tree.h agn_tree.c \
     agn_types.h agn_types.c \
     agn_vector.h agn_vector.c

%.h: %.orz
    orez -t -e $@ $<
%.c: %.orz
    orez -t -e $@ $<

文本中的规律

上一节最后给出的 Makefile,看上去依然很不舒服。既然那么多的 .h 与 .c 目标对应的模式规则语句可以一举简化为 2 行语句,那么有什么理由让 all 目标的依赖如此庞大?

此外,还有一个问题,all 目标的所有依赖都是硬性的,在向 orez-src 目录增加或删除 .orz 文件时,皆需手动维护 all 目标的依赖列表。

这两个问题可归结为一个问题,如何自动生成这个依赖列表?要解决这个问题,方法只有一个,即找规律。有了规律,就可以做自动化。

all 目标的所有依赖都是要从 .orz 文件中抽取的 .h 与 .c 文件,并且这些 .h 与 .c 的文件名与 .orz 文件名相比,只有扩展名不同。用下面这份 Bash 脚本可以很容易生成这些依赖:

#!/bin/bash
echo -n "all: "
for i in $(ls *.orz)
do
    if [ "$i" = "agn_build.orz" ]
    then
        continue
    fi
    target=${i%%.orz}
    echo -n " ${target}.h ${target}.c"
done
echo ""

原理就是用 ls 命令获取当前目录下所有扩展名为 .orz 的文件,剩下的事情就是用字符串替换的把戏,将 .orz 的文件名去掉 .orz 后缀,然后再给它加上 .h 与 .c 后缀。注意,按前文所述,不需要从 agn_build.orz 中产生 .h 与 .c 文件,因此上述 Bash 代码在生成依赖列表时,忽略了 agn_build.orz。

这样的小把戏,Makefile 也能做,而且更为简单。Makefile 中可以使用 Make 工具(GNU Make)提供的内建的函数 wildcard,它的功能类似于上述 Bash 代码中的 ls *.orz,只不过它会将所得结果返回给一个 Makefile 变量。例如:

orz_files = $(wildcard *.orz)

$(wildcard *.orz)

便是对 wildcard 函数的调用,

*.orz

是传给这个函数的参数。wildcard 的返回结果被保存于变量 orz_files 中。

上述 Makefile 语句之所以不具备排除 agn_build.orz 的功能,是因为

$(wildcard *.orz)

的返回结果是 Makefile 所在目录中的全部 .orz 文件名,因此 agn_build.orz 会被包含在内。可以从

$(wildcard *.orz)

的返回结果中剔除 agn_build.orz 解决这个问题,不过这需要调用 Make 内建函数 subst 来实现,即:

原文地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值