How do you get the list of targets in a makefile?

非 Y 不嫁゛ 提交于 2019-11-27 16:44:06

This is an attempt to improve on @nobar's great approach as follows:

  • uses a more robust command to extract the target names, which hopefully prevents any false positives (and also does away with the unnecessary sh -c)
  • does not invariably target the makefile in the current directory; respects makefiles explicitly specified with -f <file>
  • excludes hidden targets - by convention, these are targets whose name starts neither with a letter nor a digit
  • makes do with a single phony target
  • prefixes the command with @ to prevent it from being echoed before execution

Curiously, GNU make has no feature for listing just the names of targets defined in a makefile. The -p option produces output that includes all targets, but buries them in a lot of other information.

Place the following rule in a makefile for GNU make to implement a target named list that simply lists all target names in alphabetical order - i.e.: invoke as make list:

.PHONY: list
list:
    @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'

Important: On pasting this, make sure that the last line is indented by exactly 1 actual tab char. (spaces do not work).

Note that sorting the resulting list of targets is the best option, since not sorting doesn't produce a helpful ordering in that the order in which the targets appear in the makefile is not preserved.
Also, the sub-targets of a rule comprising multiple targets are invariably output separately and will therefore, due to sorting, usually not appear next to one another; e.g., a rule starting with a z: will not have targets a and z listed next to each other in the output, if there are additional targets.

Explanation of the rule:

  • .PHONY: list
    • declares target list a phony target, i.e., one not referring to a file, which should therefore have its recipe invoked unconditionally
  • $(MAKE) -prRn -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null

    • Invokes make again in order to print and parse the database derived from the makefile:
      • -p prints the database
      • -Rr suppresses inclusion of built-in rules and variables
      • -q only tests the up-to-date-status of a target (without remaking anything), but that by itself doesn't prevent execution of recipe commands in all cases; hence:
      • -f $(lastword $(MAKEFILE_LIST)) ensures that the same makefile is targeted as in the original invocation, regardless of whether it was targeted implicitly or explicitly with -f ....
        Caveat: This will break if your makefile contains include directives; to address this, define variable THIS_FILE := $(lastword $(MAKEFILE_LIST)) before any include directives and use -f $(THIS_FILE) instead.
      • : is a deliberately invalid target that is meant to ensure that no commands are executed; 2>/dev/null suppresses the resulting error message. Note: This relies on -p printing the database nonetheless, which is the case as of GNU make 3.82. Sadly, GNU make offers no direct option to just print the database, without also executing the default (or given) task; if you don't need to target a specific Makefile, you may use make -p -f/dev/null, as recommended in the man page.
  • -v RS=

    • This is an awk idiom that breaks the input into blocks of contiguous non-empty lines.
  • /^# File/,/^# Finished Make data base/
    • Matches the range of lines in the output that contains all targets (true as of GNU make 3.82) - by limiting parsing to this range, there is no need to deal with false positives from other output sections.
  • if ($$1 !~ "^[#.]")
    • Selectively ignores blocks:
      • # ... ignores non-targets, whose blocks start with # Not a target:
      • . ... ignores special targets
    • All other blocks should each start with a line containing only the name of an explicitly defined target followed by :
  • egrep -v -e '^[^[:alnum:]]' -e '^$@$$' removes unwanted targets from the output:
    • '^[^[:alnum:]]' ... excludes hidden targets, which - by convention - are targets that start neither with a letter nor a digit.
    • '^$@$$' ... excludes the list target itself

Running make list then prints all targets, each on its own line; you can pipe to xargs to create a space-separated list instead.

Under Bash (at least), this can be done automatically with tab completion:

make(space)(tab)(tab)
nobar

I combined these two answers: https://stackoverflow.com/a/9524878/86967 and https://stackoverflow.com/a/7390874/86967 and did some escaping so that this could be used from inside a 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

This obviously won't work in many cases, but if your Makefile was created by CMake you might be able to run 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

If you have bash completion for make installed, the completion script will define a function _make_target_extract_script. This function is meant to create a sed script which can be used to obtain the targets as a list.

Use it like this:

# Make sure bash completion is enabled
source /etc/bash_completion 

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

As mklement0 points out, a feature for listing all Makefile targets is missing from GNU-make, and his answer and others provides ways to do this.

However, the original post also mentions rake, whose tasks switch does something slightly different than just listing all tasks in the rakefile. Rake will only give you a list of tasks that have associated descriptions. Tasks without descriptions will not be listed. This gives the author the ability to both provide customized help descriptions and also omit help for certain targets.

If you want to emulate rake's behavior, where you provide descriptions for each target, there is a simple technique for doing this: embed descriptions in comments for each target you want listed.

You can either put the description next to the target or, as I often do, next to a PHONY specification above the target, like this:

.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

Which will yield

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

...
help                Generate list of targets with descriptions

You can also find a short code example in this gist and here too.

Again, this does not solve the problem of listing all the targets in a Makefile. For example, if you have a big Makefile that was maybe generated or that someone else wrote, and you want a quick way to list its targets without digging through it, this won't help.

However, if you are writing a Makefile, and you want a way to generate help text in a consistent, self-documenting way, this technique may be useful.

mklement0

@nobar's answer helpfully shows how to use tab completion to list a makefile's targets.

  • This works great for platforms that provide this functionality by default (e.g., Debian, Fedora).

  • On other platforms (e.g., Ubuntu) you must explicitly load this functionality, as implied by @hek2mgl's answer:

    • . /etc/bash_completion installs several tab-completion functions, including the one for make
    • Alternatively, to install only tab completion for make:
      • . /usr/share/bash-completion/completions/make
  • For platforms that don't offer this functionality at all, such as OSX, you can source the following commands (adapated from here) to implement it:
_complete_make() { COMPREPLY=($(compgen -W "$(make -pRrq : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($1 !~ "^[#.]") {print $1}}' | egrep -v '^[^[:alnum:]]' | sort | xargs)" -- "${COMP_WORDS[$COMP_CWORD]}")); }
complete -F _complete_make make
  • Note: This is not as sophisticated as the tab-completion functionality that comes with Linux distributions: most notably, it invariably targets the makefile in the current directory, even if the command line targets a different makefile with -f <file>.

This one was helpful to me because I wanted to see the build targets required (and their dependencies) by the make target. I know that make targets cannot begin with a "." character. I don't know what languages are supported, so I went with egrep's bracket expressions.

cat Makefile | egrep "^[[:alnum:][:punct:]]{0,}:[[:space:]]{0,}[[:alnum:][:punct:][:space:]]{0,}$"

This is far from clean, but did the job, for me.

make -p 2&>/dev/null | grep -A 100000 "# Files" | grep -v "^$" | grep -v "^\(\s\|#\|\.\)" | grep -v "Makefile:" | cut -d ":" -f 1

I use make -p that dumps the internal database, ditch stderr, use a quick and dirty grep -A 100000 to keep the bottom of the output. Then I clean the output with a couple of grep -v, and finally use cut to get what's before the colon, namely, the targets.

This is enough for my helper scripts on most of my Makefiles.

EDIT: added grep -v Makefile that is an internal rule

Plenty of workable solutions here, but as I like saying, "if it's worth doing once, it's worth doing again." I did upvote the sugestion to use (tab)(tab), but as some have noted, you may not have completion support, or, if you have many include files, you may want an easier way to know where a target is defined.

I have not tested the below with sub-makes...I think it wouldn't work. As we know, recursive makes considered harmful.

.PHONY: list ls
ls list :
    @# search all include files for targets.
    @# ... excluding special targets, and output dynamic rule definitions unresolved.
    @for inc in $(MAKEFILE_LIST); do \
    echo ' =' $$inc '= '; \
    grep -Eo '^[^\.#[:blank:]]+.*:.*' $$inc | grep -v ':=' | \
    cut -f 1 | sort | sed 's/.*/  &/' | sed -n 's/:.*$$//p' | \
    tr $$ \\\ | tr $(open_paren) % | tr $(close_paren) % \
; done

# to get around escaping limitations:
open_paren := \(
close_paren := \)

Which I like because:

  • list targets by include file.
  • output raw dynamic target definitions (replaces variable delimiters with modulo)
  • output each target on a new line
  • seems clearer (subjective opinion)

Explanation:

  • foreach file in the MAKEFILE_LIST
  • output the name of the file
  • grep lines containing a colon, that are not indented, not comments, and don't start with a period
  • exclude immediate assignment expressions (:=)
  • cut, sort, indent, and chop rule-dependencies (after colon)
  • munge variable delimiters to prevent expansion

Sample Output:

 = Makefile = 
  includes
  ls list
 = util/kiss/snapshots.mk = 
  rotate-db-snapshots
  rotate-file-snapshots
  snap-db
  snap-files
  snapshot
 = util/kiss/main.mk = 
  dirs
  install
   %MK_DIR_PREFIX%env-config.php
   %MK_DIR_PREFIX%../srdb

This is a modification to jsp's very helpful answer (https://stackoverflow.com/a/45843594/814145). I like the idea of getting not only a list of targets but also their descriptions. jsp's Makefile puts the description as the comment, which I found often will be repeated in the target's description echo command. So instead, I extract the description from the echo command for each target.

Example Makefile:

.PHONY: all
all: build
        : "same as 'make build'"

.PHONY: build
build:
        @echo "Build the project"

.PHONY: clean
clean:
        @echo "Clean the project"

.PHONY: help
help:
        @echo -n "Common make targets"
        @echo ":"
        @cat Makefile | sed -n '/^\.PHONY: / h; /\(^\t@*echo\|^\t:\)/ {H; x; /PHONY/ s/.PHONY: \(.*\)\n.*"\(.*\)"/    make \1\t\2/p; d; x}'| sort -k2,2 |expand -t 20

Output of make help:

$ make help
Common make targets:
    make all        same as 'make build'
    make build      Build the project
    make clean      Clean the project
    make help       Common make targets

Notes:

  • Same as jsp's answer, only PHONY targets may be listed, which may or may not work for your case
  • In addition, it only lists those PHONY targets that have a echo or : command as the first command of the recipe. : means "do nothing". I use it here for those targets that no echo is needed, such as all target above.
  • There is an additional trick for the help target to add the ":" in the make help output.

Yet another additional answer to above.

tested on MacOSX using only cat and awk on terminal

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

will output of the make file like below:

target1

target2

target3

in the Makefile, it should be the same statement, ensure that you escape the variables using $$variable rather than $variable.

Explanation

cat - spits out the contents

| - pipe parses output to next awk

awk - runs regex excluding "shell" and accepting only "A-z" lines then prints out the $1 first column

awk - yet again removes the last character ":" from the list

this is a rough output and you can do more funky stuff with just AWK. Try to avoid sed as its not as consistent in BSDs variants i.e. some works on *nix but fails on BSDs like MacOSX.

More

You should be able add this (with modifications) to a file for make, to the default bash-completion folder /usr/local/etc/bash-completion.d/ meaning when you "make tab tab" .. it will complete the targets based on the one liner script.

not sure why the previous answer was so complicated:

list:
    cat Makefile | grep "^[A-z]" | awk '{print $$1}' | sed "s/://g" 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!