Makefile基本使用
Makefile基本使用
泡泡Makefile简介
Makefile 是一个文本文件(默认情况下使用名为 Makefile 的文件),其中列出了按所谓 “target”分组的命令,你可以将其视为应一起执行的命令组。我们用 TAB 缩进字符来表示属于某个目标的命令。
默认情况下,make 会同时打印执行的命令和命令本身的结果。如果需要,你可以关闭这种行为,不过通常情况下,你确实希望看到被执行的内容。一开始,同时看到命令和输出结果可能会让人有点困惑,但你的眼睛很快就会习惯的。
Makefiles支持我们开发工作流,我们希望可以实现以下功能:
- 依赖管理—-跳过已经完成的步骤
- 重新进入—-从特定点重新启动工作流
- 自动化—-使用相同的代码并行运行多个相同的分析
一个Makefile文件的基本结构如下:
1 | foo: |
执行全部代码:
1
make -f Makefile
执行指定代码:
1
make -f Makefile foo
Tip: 如果文件名字是Makefile,也可以直接省略-f参数和文件名。
需要注意的是,Makefile 中的命令必须使用 Tab 键缩进,不能使用空格。不过我们可以通过设置 .RECIPEPREFIX 变量来修改这个行为,例如:
1 | # Sets the prefix for commands. |
Makefile使用技巧
好的默认设置
1 | SHELL := bash |
1 | # Deletes dependencies if the command fails. |
作用:
• 这个指示符告诉 make 如果在执行目标的命令时发生错误,应该删除目标文件。
好处:
• 保持构建环境的整洁:如果某个构建目标在执行过程中失败,它会删除部分生成的文件,从而避免留下半成品或不完整的文件。
• 避免误用不完整的构建:当构建失败时,目标文件可能是部分生成的,保留这些文件可能会导致后续的构建过程误以为这些文件是完整的,从而造成错误。删除这些文件可以避免这种情况。
1 | # Warns about undefined variables. |
作用:
• —warn-undefined-variables: 这个选项会使 make 在使用未定义变量时发出警告。
• —no-builtin-rules: 这个选项会禁用所有的内建规则和变量,使用者需要在 Makefile 中明确地定义所有需要的规则。
好处:
• 提高可调试性:
• —warn-undefined-variables: 当使用未定义变量时发出警告,有助于在构建过程中快速发现和修复拼写错误或遗漏定义的变量。这对于维护大型和复杂的 Makefile 特别有用。
• 增强可控性和可预测性:
• —no-builtin-rules: 禁用内建规则,确保所有的构建规则都是显式定义的,从而提高 Makefile 的可读性和可维护性。这样可以避免一些隐式规则导致的意外行为,使构建过程更加透明和可预测。
1 | SHELL := bash |
作用:
• 这个指示符告诉 make 使用 bash 作为 shell。
1 | .ONESHELL: |
作用:
• 这个指示符告诉 make 在执行目标的命令时,将所有的命令放在同一个 shell 进程中执行。
1 | .SHELLFLAGS := -eu -o pipefail -c |
作用:
-e
: 如果任何命令行返回非零状态(失败),则立即退出脚本。
-u
: 当执行到未设置的变量时,shell将会报错并退出。
-o pipefail
: 这个选项会导致管道(|)中的命令如果有任何一个失败了,整个管道的返回状态都会是失败的。默认情况下,只有最后一个命令的状态会影响整个管道的状态。
-c
: 这个标志告诉shell从字符串中读取命令。
“Dry-run”模式
如果你想要查看 make 命令将执行的命令,而不实际执行它们,可以使用 -n
或 --just-print
选项。这个选项会显示 make 将执行的命令,但不会实际执行它们。
1 | make fastq align -n |
变量命名
Makefile一般通过${foo}来引用变量,这和bash脚本中是一样的。
但在Makefile中,可以使用?=
来定义变量,如此定义的变量我们可以在命令控制行中进行控制。
例如:1
2
3
4
5
6
7
8# Set the value of FOO to bar (if not already set).
NAME ?= Jane
# Sets the prefix for commands.
.RECIPEPREFIX = >
usage:
> echo Hello ${NAME}
我们可以对NAME进行赋值,如:1
make usage NAME=Joe
隐藏命令
一般情况下,make会打印出执行的命令,但有时候我们不希望看到这些命令,可以使用@
符号来隐藏命令。
1 | # Sets the prefix for commands. |
文本替换
有时候我们想从路径中提取目录名或文件名,如:
- PATH = data/reads/abc.txt
则: - DIR = $(dir ${PATH})将包含data/reads/
- FNAME = $(notdir ${PATH})将包含abc.txt
一些常见的使用如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28#
# How to generate patterns
#
# Sets the prefix for commands.
.RECIPEPREFIX = >
# Set a filename
FILE = data/refs/ebola.fa.gz
demo:
# Prints: data/refs/ebola.fa.gz
> @echo ${FILE}
# Prints: data/refs
> @echo $(dir ${FILE})
# Prints: ebola.fa.gz
> @echo $(notdir ${FILE})
# Prints: data/refs/human.fa.gz
# 使用 subst 函数将 FILE 变量中的 ebola 替换为 human,结果为 data/refs/
> @echo $(subst ebola,human,${FILE})
# Prints: data/refs/ebola.fa
# 使用 patsubst 函数将 FILE 变量中的以 .gz 结尾的字符串替换为没有 .gz 的字符串,结果为 data/refs/ebola.fa
> @echo $(patsubst %.gz,%,${FILE})
打印说明
有时候我们想知道自己编写的Makefiles中有哪些作用,我们可以编写一个说明来帮助我们理解。1
2
3
4
5
6
7
8
9
10
11# Set the prefix from tabs to >
.RECIPEPREFIX = >
# Set the project number.
PRJN ?= PRJEB31790
# Default action is to print usage
usage:
> @echo ""
> @echo "Usage: make data trim PRJN=${PRJN}"
> @echo ""
我们可以运行make usage
来查看说明。另外,如果我们没有指定“target“,则默认执行第一个“target”。
依赖管理
Makefile中的依赖管理是非常重要的,我们可以通过依赖管理来跳过已经完成的步骤,从而提高构建速度。
1 | # Set the prefix from tabs to > |
如果不存在results.txt文件,make会先生成counts.txt和names.txt文件,然后再生成results.txt文件。