如何在makefile中获取目标列表?

327

我有使用过rake(一个Ruby编写的make工具),它有一个选项可以获取所有可用目标的列表,例如:

> rake --tasks
rake db:charset      # retrieve the charset for your data...
rake db:collation    # retrieve the collation for your da...
rake db:create       # Creates the databases defined in y...
rake db:drop         # Drops the database for your curren...
...

但是在GNU make中似乎没有这样的选项。

显然,截至2007年,这个功能已经非常接近了 - http://www.mail-archive.com/help-make@gnu.org/msg06434.html

无论如何,我做了一个小技巧来从makefile中提取目标,你可以在makefile中包含它。

list:
    @grep '^[^#[:space:]].*:' Makefile
它会给你一个已定义目标的列表,但这只是个开端 - 比如它并没有过滤依赖关系。
> make list
list:
copy:
run:
plot:
turnin:

4
我快速地阅读了答案,它们很有趣,但是就我自己而言,我更喜欢保持简单、简洁和可移植,使用一个别名(在我的.bashrc文件中):alias makefile-targets='grep "^[^#[:space:]].*:" Makefile'我通常只需要检查当前的makefile,然后bash自动完成会展开我的别名。 - RockyRoad
1
https://unix.stackexchange.com/a/230050/221416 - mrgloom
7
仅使用 grep : Makefile 是不是太过简单了? - cibercitizen1
3
@cibercitizen1 对于非常简单的 Makefile,这个方法是可行的,但在更复杂的文件中,你的命令将包括特殊目标,如 .PHONY,包含 URL 的行,使用 := 定义变量的行,定义目标特定变量的行等等。 - FWDekker
29个回答

283
在Bash下(至少),这可以通过制表符自动完成来完成:
make 空格 制表符 制表符

如果您正在使用一个基本的发行版(可能是一个容器),您可能需要安装一个软件包。

$ apt-get install bash-completion ## Debian/Ubuntu/etc.
$ . /etc/bash_completion ## or just launch bash again

83
+1,但需要注意的是这不是_bash_ 的特性,而是取决于你的_平台_。平台分为三类:一些平台默认安装了这个补全功能(例如Debian 7、Fedora 20),一些平台你可以_选择性地_ 安装它(例如使用 . /etc/bash_completion 在Ubuntu 14上安装),还有一些平台根本不支持这个功能(例如OSX 10.9)。 - mklement0
11
它可以在OS X 10.10上工作(不是用bash,而是用zsh)。 - Florian Oswald
13
很好的提示,但还需要bash-completion软件包。可在Fedora、RHEL、Gentoo等操作系统中找到。 - NoelProf
8
据我所知,这无法适用于以编程方式创建的 make 目标,例如 $(RUN_TARGETS): run-%: ...。我的翻译尽量使内容通俗易懂,但不会改变原意,并且不会提供解释或其他信息。 - mxk
3
这段话可能受到了eval和varname:=...的极大困扰,我的自动完成功能会包括$(foreach f,等内容。目前,我正在使用make -Bnmake -Bn targetname,它会执行默认或targetname作为从头开始的干行。结合一个(可选)打印当前目标及其依赖项的目标,这可以给您所有需要的信息。另外,make --print-data-base会显示所有目标/先决条件/配方,包括通配符目标的每个扩展! - John P
显示剩余11条评论

226
注意:此答案已更新,截至GNU make v4.3仍可使用-如果遇到任何问题,请告诉我们。
这是对Brent Bradburn的优秀方法的改进尝试,具体如下:
  • 使用更强大的命令提取目标名称,希望能防止任何误报(同时也不再需要不必要的sh -c)
  • 不总是针对当前目录中的makefile;而是尊重通过-f 明确指定的makefile
  • 排除隐藏目标-按照约定,这些目标的名称既不以字母开头,也不以数字开头
  • 只使用一个虚拟目标
  • 在命令前加上@符号,以防止在执行之前被回显

奇怪的是,GNU make 没有一个功能可以列出 makefile 中定义的目标的名称。虽然 -p 选项会生成包含所有目标的输出,但它们被埋在大量其他信息中,并且还会执行默认目标(可以使用 -f/dev/null 来禁止执行默认目标)。
在 GNU make 的 makefile 中添加以下规则,以实现一个名为 list 的目标,该目标简单地按字母顺序列出所有目标名称 - 即:调用方式为 make list
.PHONY: list
list:
    @LC_ALL=C $(MAKE) -pRrq -f $(firstword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | grep -E -v -e '^[^[:alnum:]]' -e '^$@$$'

重要提示:在粘贴时,请确保最后一行缩进恰好一个制表符(空格不起作用)。
请注意,对结果列表进行排序是最佳选择,因为不排序会导致无法得到有用的顺序,即目标在makefile中出现的顺序不会被保留。 此外,由于排序的原因,由多个目标组成的规则的子目标通常会分别输出,因此通常不会相邻出现;例如,以a z:开头的规则,如果还有其他目标,则不会在输出中将目标az列在一起
规则的解释如下:
  • .PHONY: list

    • 声明目标列表为虚拟目标,即不指向任何文件的目标,因此应该无条件地调用其命令
  • LC_ALL=C 确保 make 的输出为英文,因为解析输出依赖于此。感谢 Bastian Bittorf

  • $(MAKE) -pRrq -f $(firstword $(MAKEFILE_LIST)) : 2>/dev/null

    • 再次调用 make 以打印和解析从 makefile 派生的数据库:
      • -p 打印数据库

      • -Rr 禁止包含内置规则和变量

      • -q 只测试目标的最新状态(而不重新制作任何内容),但这本身并不能阻止在所有情况下执行配方命令;因此:

      • -f $(firstword $(MAKEFILE_LIST)) 确保与原始调用中的相同的 makefile 成为目标,无论是隐式还是使用 -f ... 显式地指定。
        (由于 MAKEFILE_LIST 还包含了被 include 的 Makefile 列表,因此使用 firstword 仅提取最初的目标文件;这意味着目标文件名/路径不能包含空格,但在实践中不太可能出现这种情况)。

      • : 是一个故意无效的目标,旨在确保不执行任何命令2>/dev/null 抑制生成的错误消息。注意:这依赖于 -p 仍然打印数据库,这是从 GNU make 3.82 开始的情况。遗憾的是,GNU make 没有直接选项可以打印数据库,而不同时执行默认(或给定)任务;如果您不需要针对特定的 Makefile,可以使用 make -p -f/dev/null,如 man 页面中推荐的那样。

  • -v RS=

    • 这是 awk 的习惯用法,将输入分成连续的非空行块。
  • /(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/

    • 匹配输出中包含所有目标的段落范围的行 - 通过限制解析到此范围,无需处理其他输出部分的误报。
    • 注意:在 make 版本 3.x 和 4.3 之间,make 输出中的段落结构发生了变化,因此 (^|\n) / (\n|$$) 确保无论这些行出现在段落的开头、内部还是末尾,都能检测到标识感兴趣的跨段落行范围的起始和结束。
  • if ($$1 !~ "^[#.]")

    • 有选择地忽略
      运行make list然后打印所有目标,每个目标独占一行;你可以使用管道符xargs来创建以空格分隔的列表。

不错!谢谢! 如何在Makefile的主体中避免“include .env”语句? 它会生成make: *** [Makefile:10: list] Error 1。 - Vladimir Perepechenko
@VladimirPerepechenko,您是在说这个语句的存在会影响此答案的解决方案,还是在询问关于破坏您的Makefile的语句的一般性问题?如果是后者,请创建一个新的问题帖子。 - mklement0

71
如果你的Makefile是由CMake创建的,你可能可以运行make help
$ make help
The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... install
etc

如果没有的话,我写了一个补丁来为Make添加对这个显然有用的功能的适当支持。这比这里所有其他答案都要好得多,它们都涉及可怕的hack来grep Makefiles。如果你包含其他Makefiles,使用计算目标名称等,那显然是行不通的。
这个补丁还没有合并,所以你需要从源代码构建。这并不太糟糕,但你确实需要一些陈旧的与autoconf相关的构建工具。
git clone https://github.com/Timmmm/make
cd make
./bootstrap
./configure
make -j4

在Linux上,你可以使用我已经构建好的二进制文件这个二进制文件
然后,你可以使用-l标志来列出目标。
./make -C /path/to/your/project -l

你有没有在CMakeList中开启任何选项来获取?我得到了由CMake生成的makefile,但是我没有伪帮助目标。 - Xavier T.
不是的。在OSX上,至少使用最新版本的CMake(我实际上正在使用夜间版本,但我相信当我写这篇文章时我正在使用3.3),只需运行“touch CMakeLists.txt && cmake . && make help”即可正常工作。我只能猜测您正在使用古老的cmake,或者没有使用Unix Makefiles生成器? - Timmmm
我正在Windows上使用带有Unix生成器的CMake 3.6.2。我会仔细查找解释,因为它似乎很方便。 - Xavier T.

33
我结合了这两个答案:https://dev59.com/MXA75IYBdhLWcg3w3NFe#9524878https://dev59.com/MXA75IYBdhLWcg3w3NFe#7390874,并进行了一些转义以便在 makefile 中使用。
.PHONY: no_targets__ list
no_targets__:
list:
    sh -c "$(MAKE) -p no_targets__ | awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {split(\$$1,A,/ /);for(i in A)print A[i]}' | grep -v '__\$$' | sort"

.

$ make -s list
build
clean
default
distclean
doc
fresh
install
list
makefile ## this is kind of extraneous, but whatever...
run

13
我最近发现Bash下的Tab自动补全功能可以列出可用的目标。 - Brent Bradburn
1
+1 非常好的方法,但还有一些改进可以做:(a) 明确指定 sh -c "..." 是不必要的,因为这是 make 默认用于调用配方命令的;(b) 你使用的命令过于简单,导致误报(正如你所指出的);(c) 如果在命令前加上 @,它将在执行之前不会被回显到标准输出,从而避免了在调用时使用 -s 的需要。 - mklement0
2
有一个更简单的Make目标可以添加: 列表: cat Makefile | grep "^[A-z]" | awk '{print $$1}' | sed "s/://g | sort" - thamster
4
“cat/sed方法在多个方面存在问题。它不会扩展变量,也无法列出作为包含其他文件结果的目标。” - Troy Daniels
1
@mklement0:我更喜欢使用make -s(通过Bash别名)而不是在makefile中添加前缀@的命令。有些情况下,@可能很有用,特别是作为echo命令的前缀(其中命令的显示将是多余的),但通常我不喜欢使用它。这样,当我需要时(通过使用-s),我可以看到“recipes”,但通常不会看到。这里是相关文档:GNU Make手册,5.2 Recipe Echoing - Brent Bradburn
显示剩余3条评论

23

正如mklement0指出,GNU-make缺少列出所有Makefile目标的功能,他和其他人提供了实现此功能的方法。

然而,原帖还提到了rake,其tasks开关做的事情略有不同,它只会列出具有相关描述的任务。没有描述的任务将不会被列出。这使得作者既可以为每个目标提供自定义帮助描述,又可以省略某些目标的帮助信息。

如果您想模仿rake的行为,为每个目标提供描述,有一种简单的技巧:在每个要列出的目标的注释中嵌入描述。

您可以将描述放在目标旁边,也可以像我经常做的那样,在目标上面的PHONY规范旁边,像这样:

.PHONY: target1 # Target 1 help text
target1: deps
    [... target 1 build commands]

.PHONY: target2 # Target 2 help text
target2:
    [... target 2 build commands]

...                                                                                                         

.PHONY: help # Generate list of targets with descriptions                                                                
help:                                                                                                                    
    @grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1 \2/' | expand -t20

会产生什么结果

$ make help
target1             Target 1 help text
target2             Target 2 help text

...
help                Generate list of targets with descriptions

您还可以在这个代码片段这里找到一个简短的代码示例。

但是,这并不能解决列出 Makefile 中所有目标的问题。例如,如果您有一个大型的Makefile,可能是由别人编写或生成的,并且您想要一种快速列出其目标而不必深入挖掘的方法,那么这种方法就无法帮助您。

然而,如果您正在编写Makefile,并且想要以一种一致、自描述的方式生成帮助文本,则此技术可能是有用的。


这是一种很棒的技术 - 简单易懂,同时也提供了描述! - Brian Burns
@PengheGeng 感谢您的帖子。我想澄清一下,@echo "Target 1" 只是一个占位符,而不是“描述 echo 命令”。生成的帮助文本并不来自 @echo。在真正的 Makefile 中,这些 echo 将被构建命令或类似命令所替换。我将编辑帖子以使其更加清晰明了。 - jsp
由于这里使用了.PHONY,因此会防止将该方法用于需要创建与目标名称匹配的文件的目标。可以发明一种特殊的语法,例如以三个###开头的行来标记文档行。 - Mikko Rantalainen
2
@MikkoRantalainen 和其他人,从 StackOverflow 复制/粘贴时,空格会被解释为空格符。在我的情况下,expand -t20 正在寻找制表符,因此我需要将 \1 \2 中的空格更改为制表符才能使其正常工作。 - justinseibert
1
太棒了!采取了进一步的措施,添加了章节。一个章节看起来像是在一组目标之前加上 #=Section,使用 grep -e '^.PHONY: .*#' -e '^#=' makefile | sed -r -e 's|#=(.*)|\n\1:|' -e 's|\.PHONY: (.*) # (.*)| \1 \2|' | expand -t20 将其包含在输出中。 - RiverHeart
显示剩余4条评论

23

我最喜欢的答案是由Unix & Linux Stack Exchange上的Chris Down发布的。我会引用他的话。

This is how the bash completion module for make gets its list:

make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'

It prints out a newline-delimited list of targets, without paging.

用户Brainstone建议使用管道符号到sort -u以删除重复条目:

make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' | sort -u

来源:如何在make中列出所有目标?(Unix&Linux SE)


19
如果您安装了make的bash自动完成功能,则完成脚本将定义一个_make_target_extract_script函数。此函数旨在创建一个sed脚本,可用于作为列表获取目标。
使用方法如下:
# Make sure bash completion is enabled
source /etc/bash_completion 

# List targets from Makefile
sed -nrf <(_make_target_extract_script --) Makefile

3
请注意,并非所有平台都具有脚本/etc/bash_completion和/或函数_make_target_extract_script-你似乎在使用Ubuntu> 12。 如果您的目标是/usr/share/bash-completion/completions/make,则您的方法将适用于其他平台,例如Fedora 20。 有(较旧的)平台具有此文件,但没有该函数(例如Debian 7和Ubuntu 12); 其他平台,例如OSX,根本不提供make的制表符完成功能。也许发布实际的函数定义会有所帮助。 - mklement0
1
感谢您的评论。非常感谢。是的,我已经在Ubuntu 14.04上进行了测试。 - hek2mgl

12

针对描述make目标的简明语法和干净的输出,我选择了这种方法:

help:
    @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^\=]|$$)" Makefile \
     | grep -v -- -- \
     | sed 'N;s/\n/###/' \
     | sed -n 's/^#: \(.*\)###\(.*\):.*/\2###\1/p' \
     | column -t  -s '###'


#: Starts the container stack
up: a b
  command

#: Pulls in new container images
pull: c d 
    another command

make-target-not-shown:

# this does not count as a description, so leaving
# your implementation comments alone, e.g TODOs
also-not-shown:

所以将上面的内容视为Makefile并运行它,会给你类似以下的东西。
> make help
up          Starts the container stack
pull        Pulls in new container images

命令链的解释:


非常好的解决方案。我已经使用了几年了:awk -F ':|##' '/^[^\t].+:.*##/ { printf "\033[36mmake %-28s\033[0m -%s\n", $$1, $$NF }' $(MAKEFILE_LIST) | sort ,其中在目标的同一行上使用##注释。但我认为你的解决方案允许更好的可读性。 - thoroc
一个包含前两行的Makefile在我的OSX 10.15机器上报告了“grep: parentheses not balanced”的错误。 - Malcolm Crum
@Crummy 抱歉,是我的错误。感谢您指出来。已经修复了。make需要转义那些不应该作为变量名开头的美元符号。 - Richard Kiefer
2
注意,StackOverflow在显示上面的代码部分时会将制表符转换为空格,但Makefile需要制表符。您可以编辑帖子,然后复制部分内容,以便保留制表符。 - Richard Kiefer

7

我参考了上面提到的几个答案,并编写了这个方法,它可以为每个目标生成一个漂亮的说明,并且也适用于包含变量的目标。

示例 Makefile:

APPS?=app1 app2

bin: $(APPS:%=%.bin)
    @# Help: A composite target that relies only on other targets

$(APPS:%=%.bin): %.bin:
    @# Help: A target with variable name, value = $*

test:
    @# Help: A normal target without variables

# A target without any help description
clean:

# A hidden target
.hidden:

help:
    @printf "%-20s %s\n" "Target" "Description"
    @printf "%-20s %s\n" "------" "-----------"
    @make -pqR : 2>/dev/null \
        | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \
        | sort \
        | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \
        | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"'

例子结果:

$ make help
Target               Description
------               -----------
app1.bin             A target with variable name, value = app1
app2.bin             A target with variable name, value = app2
bin                  A composite target that relies only on other targets
clean
test                 A normal target without variables

它是如何工作的:

make help目标的顶部部分与mklement0在此处发布的一样 - How do you get the list of targets in a makefile?

在获取目标列表之后,对于每个目标,它通过运行make <target> -nB进行干运行,并解析以@# Help:开头的最后一行以获取该目标的描述。然后,将其打印为漂亮格式的表格中的字符串或空字符串。

正如您所看到的,甚至在描述中也扩展了变量,这对我来说是一个巨大的优势:)


5
将以下内容添加到您的Makefile目标中:
help:
    @echo "\nTARGETS:\n"
    @make -qpRr | egrep -e '^[a-z].*:$$' | sed -e 's~:~~g' | sort
    @echo ""

  • make -qpRr = make --question --print-data-base --no-builtin-variables --no-builtin-rules
  • egrep -e '^[a-z].*:$$': 搜索以小写字母开头且以“:”结尾的行
  • sed -e 's~:~~g': 删除“:”

然后只需要运行:

make help

这对我有效


附注:更多信息请参见...

make --help

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接