如何在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个回答

1

这是对上述问题的另一个额外答案。

在MacOSX上只使用终端上的cat和awk进行测试。

cat Makefile | awk '!/SHELL/ && /^[A-z]/ {print $1}' | awk '{print substr($0, 1, length($0)-1)}'

将会输出如下所示的Makefile:

target1

target2

target3

在Makefile中,应该使用相同的语句,确保使用$$variable转义变量而不是$variable。

解释

cat - 输出内容

| - 管道将输出解析到下一个awk中

awk - 运行正则表达式,排除"shell"并仅接受"A-z"行,然后打印出第一列$1

awk - 再次从列表中删除最后一个字符“:”

这是一个粗略的输出,您可以使用AWK做更多有趣的事情。尽量避免使用sed,因为它在BSD变体中不太一致,即在*nix上运行良好但在类似MacOSX的BSD上失败。

更多信息

你应该能够将此内容(经过修改)添加到一个文件中,用于 make,放置在默认的bash-completion文件夹 /usr/local/etc/bash-completion.d/ 中。 这意味着当你输入 "make tab tab" 时,它将基于单行脚本完成目标。

你也可以使用make -qp而不是cat。这样它就会变成 make -qp | awk '/:/ && !/:=/ && /^[A-z]/ && !/PATH/ && !/DISPLAY/ && !/TERM_SESSION/ && !/USER_TEXT_ENCODING/ && !/Makefile/ {print substr($0, 1, length($0)-1) | "sort -u"}' - mirageglobe

1

对于不喜欢AWK的人,或者为了简化,请看看这个我用的东西:

help:
  make -qpRr $(lastword $(MAKEFILE_LIST)) | egrep -v '(^(\.|:|#|\s|$)|=)' | cut -d: -f1

(如果在 Makefile 之外使用,只需删除 $(lastword ...) 或将其替换为 Makefile 路径即可。)

这种解决方案对于大多数简单的设置都有效,但如果您有“有趣”的规则名称,则无法正常工作。基于 make -qp 的解决方案的主要缺点是(如其他答案中所述),如果 Makefile 使用函数定义变量值,则不管是否使用了 -q,它们仍将被执行;如果使用 $(shell ...),则 shell 命令仍将被调用并且其副作用将发生。在我的设置中,运行 shell 函数的副作用通常是输出到标准错误,因此我在 make 命令后添加了 2>/dev/null


1

我受到@jsp答案的启发,对他的文件解析技术进行了改进,这个版本具有以下优势。

  • 自动检测Makefile名称
  • 支持节标题
  • 在每个节的所有目标下方放置.PHONY,并在目标后面放置注释,使其更清晰易读。

给定如下的Makefile

#=Default Command

.PHONY: help

help: # Shows the available make commands.
    @sed -nr \
        -e 's|^#=(.*)|\n\1:|p' \
        -e 's|^([a-zA-Z-]*):((.*?)# (.*))?|  \1 \4|p' \
        $(lastword $(MAKEFILE_LIST)) \
        | expand -t20

#=Project Commands

.PHONY: install build

install: # Installs the thing
    @echo install

build: install # Builds the thing
    @echo build

#=Other Commands

.PHONY: foo bar

foo:
    @echo foo

bar:
    @echo bar

你会得到这样的输出

Default Command:
  help              Shows the available make commands.

Project Commands:
  install           Installs the thing
  build             Builds the thing

Other Commands:
  foo
  bar

正则表达式解释: 's|^#=(.*)|\n\1:|p':捕获在部分声明#=之后括号内的所有内容,并在其前面加上换行符和冒号进行打印。 's|^([a-zA-Z-]*):((.*?)# (.*))?| \1 \4|p':捕获目标名称,并包含一个可选的捕获组()?用于描述。可选组包围了另外两个捕获组,第一个捕获组捕获了:# 之间的所有内容,第二个捕获组捕获了帮助文本(顺便说一下,这个正则表达式有点复杂,但它允许我们输出带有和不带有注释的目标。欢迎提供更好的方法)。最后,它将目标名称\1和描述\4缩进2个空格以便放在部分标题下方。

0

试试这个:

make -qp | awk -F':' '/^[^ \t.%][-A-Za-z0-9_]*:/ {split($0,A,/ /);for(i in A)if(match(A[i],/^[^.%][-A-Za-z0-9_]*/))print substr(A[i],1,RLENGTH)}' | sort -u

0

简而言之,我个人会为我构建的每个 Makefile 复制粘贴相同的 help 目标。

.SILENT:

.PHONY: help
## This help screen
help:
    printf "Available targets\n\n"
    awk '/^[a-zA-Z\-\_0-9]+:/ { \
        helpMessage = match(lastLine, /^## (.*)/); \
        if (helpMessage) { \
            helpCommand = substr($$1, 0, index($$1, ":")-1); \
            helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
            printf "%-30s %s\n", helpCommand, helpMessage; \
        } \
    } \
    { lastLine = $$0 }' $(MAKEFILE_LIST)

我也在这个 Github gist 中维护了一份副本: https://gist.github.com/Olshansk/689fc2dee28a44397c6e31a0776ede30


0
非常简单的 AWK 解决方案:
all:
    @awk -F'[ :]' '!/^all:/ && /^([A-z_-]+):/ {print "make " $$1}' Makefile

(注:这并不涵盖所有边角情况,如接受的答案所述,如在此解释。)

0
这里是使用cat、grep和cut的版本。
cat Makefile | grep : | grep -v '^\t' | cut -d: -f1

-1

我通常会执行:

grep install_targets Makefile

它会返回类似以下内容:

install_targets = install-xxx1 install-xxx2 ... etc

希望这能有所帮助


-5

不确定为什么之前的答案那么复杂:

list:
    cat Makefile | grep "^[A-z]" | awk '{print $$1}' | sed "s/://g" 

9
前面的回答很复杂,因为它涵盖了你的回答所不包括的所有情况:(a) 包含通过变量定义的目标(例如 $(SHELLS): ...),(b) 包含以数字开头的目标,(c) 不包含变量定义,(d) 如果使用显式的 makefile 路径指定了 make,则需要定位正确的 makefile。其中 (a) 是关键性缺陷: 即使你可靠地定位了“正确”的 makefile,在一般情况下解析原始文件将无法工作,因为缺少变量扩展。 - mklement0
6
顺便提一下,“复杂的”问题:你的命令可以简化为以下内容,但主要问题在于它不够健壮:awk -F: '/^[A-z]/ {print $$1}' Makefile。最后一个主要是学术性质的问题:使用 [A-z] 而不是 [:alpha:] 意味着你会错过以外语字符(如 á)开头的目标。 - mklement0
@mklement0,你的第一个awk命令运行得很好,而且迄今为止是最简单的,但是使用[:alpha:]似乎有些问题-它在测试文件中错过了一些目标。 - Brian Burns
@bburns.km:我的错,我应该说[[:alpha:]]而不是[:alpha:][:alpha:]代表字符类([...])内部的区域感知字母集,因此需要使用双重[[/]] - mklement0
4
为什么这个答案如此复杂?在这个简短的片段中,您设法收集了几个无用的(http://www.iki.fi/era/unix/award.html)罪恶。 awk -F: '/ ^ [A-Za-z] / {print $ 1}' Makefile使用一个单一的进程和更正确的正则表达式(虽然您当然也可以有包含下划线、数字等内容的目标,如早期评论所述)。 - tripleee

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