6
\$\begingroup\$

My colleagues often ask me, "Dude could you remind me what I have to type to build this target?". So I eventually implemented a nice workaround in my Makefiles.

I wanted my Makefiles to have a help target that give some usage clues to the users. This is achieved with an embedded Perl script and some markers.

Wherever I want a target to be documented, I simply add this trailing comment:

##@<category> <description>

Here is an example:

SCRIPT_VERSION=v1.0
SCRIPT_AUTHOR=John Doe

all:                ##@Build Build all the project
     echo "Hello World!" > all 

clean:              ##@Cleaning Remove all intermediate objects
     rm all

mrproper: clean     ##@Cleaning Remove all output and interemediate objects

HELP_FUN = \
    %help; while(<>){push@{$$help{$$2//'options'}},[$$1,$$3] \
    	if/^(\w+)\s*:.*\#\#(?:@(\w+))?\s(.*)$$/}; \
    print"$$_:\n", map"  $$_->[0]".(" "x(20-length($$_->[0])))."$$_->[1]\n",\
    @{$$help{$$_}},"\n" for keys %help; \

help: ##@Miscellaneous Show this help
    @echo -e "Usage: make [target] ...\n"
    @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
    @echo -e "Written by $(SCRIPT_AUTHOR), version $(SCRIPT_VERSION)"
    @echo -e "Please report any bug or error to the author." 

Here what I get if I type make help:

$ make help
Usage: make [target] ...

Miscellaneous:
  help                Show this help

Build:
  all                 Build all the project

Cleaning:
  clean               Remove all intermediate objects
  mrproper            Remove all output and interemediate objects

Written by John Doe, version v1.0
Please report any bug or error to the author.

In the case the Makefile takes time to parse the dependencies we can bypass the file for the help target:

ifeq (,$(filter help,$(MAKECMDGOALS)))
# Core of the makefile
endif

What do you think of this technique. Is it a good approach? Can I improve it?

\$\endgroup\$
4
\$\begingroup\$

It looks pretty good to me, but I see a few minor things that might be improved.

Specify which perl to run

I'd recommend not leaving it to chance as to which instance of perl is run. Instead, for both safety and consistency, it might be better to do this:

PERL=/usr/bin/perl
##...
@$(PERL) -e '$(HELP_FUN)' $(MAKEFILE_LIST)

Allow multiword category tags

With a slight change in your regex, multiword categories could be supported. If the second line is changed to this:

if/^(\w+)\s*:.*\#\#(?:@([^@]*)@)?\s(.*)$$/}; \

It will support multiword categories, delimited with @,

nothing:         ##@Odds & ends@ Do nothing, but gracefully 

Sort the keys for printing

On my machine, I get a different ordering each time I run make help which is a little confusing. To fix that, sort the keys, rendering the last line:

@{$$help{$$_}},"\n" for sort keys %help 

Note that you shouldn't have a line continuation there, and you don't need the trailing semicolon (although you may prefer one for style reasons).

Omit declaration of hash

Because %help is unambiguously used, you don't need to predeclare it. It will automatically be created as an empty hash, so the first line can become:

while(<>){push@{$$help{$$2//'options'}},[$$1,$$3] \
\$\endgroup\$
2
\$\begingroup\$

This is a minor point, but you should declare all non-file targets as phony. Otherwise make will behave strangely if you have a file named named "all", "clean" etc in your directory. make will only run the command when the file is updated, which is not the desired behaviour.

.PHONY: all clean mrproper help

See What is the purpose of .PHONY in a Makefile? and the GNU documentation.

\$\endgroup\$
2
\$\begingroup\$

You can do this in pure make if you'd like (that is, without requiring an external tool like Perl). John Graham-Cumming has a detailed explanation of it on his blog, but here's the short version.

You can add documentation to a make target by declaring it like this:

include help-system.mk

all: $(call print-help,all,Builds everything)
    # commands go here

lib: $(call print-help,lib,Builds only the static library)
    # commands go here

clean: $(call print-help,clean,Removes intermediate and output files)
    # commands go here

Invoking make help will print out something that looks like:

Makefile:6: all -- Builds everything
Makefile:14: lib -- Builds only the static library
Makefile:20: clean -- Removes intermediate and output files

The contents of "help-system.mk" are listed at the link given above.

Note: the macro "print-help" expands to an empty string so this won't interfere with listing prerequisites for a target.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.