Makefile Lazy Definitions
One of the challenges of using Make is
avoiding unnecessary execution of shell commands. If the value of a
top-level variable is defined by executing shell commands, then the
shell commands are executed no matter what rule is run, even in cases
when the variable is not used. This issue is avoided by executing the
shell commands in the definition of the appropriate rules, not at the
top-level. A macro can be used to minimize differences between
Makefiles.
As an example, consider the definition of a VERSION
variable. A Haskell project may specify the current version in the
.cabal file, and the VERSION variable can be
defined in the Makefile as follows:
VERSION := $(shell grep '^version:' $(CABAL_FILE) | sed 's/^version: *//')This definition executes two shell commands: one to select the line
in the .cabal file that specifies the version, and another
to parse the line. They are executed with every invocation of the
Makefile, even in cases when the variable is not used. It
does not really matter if these commands are executed
unnecessarily, as they do not have any side effects, but it is wasteful
to do so.
To avoid executing such commands unnecessarily, the definition can be
moved to the rules that need it. Here is an example, using the
> character as the .RECIPE_PREFIX instead
of the default tab:
version: # show current version
> $(eval VERSION := $(shell \
    grep '^version:' $(CABAL_FILE) | sed 's/^version: *//'))
> @echo "$(PROJECT) $(VERSION)"
.PHONY. versionThe value of VERSION is set to the result of executing
the shell commands only when the version rule is run. When
VERSION is used in many rules, however, it is desirable to
move the definition to a macro. This is not so much about being DRY,
but rather minimizing the differences between Makefiles so
that they are easier to maintain.
define get_version
$(shell grep '^version:' $(CABAL_FILE) | sed 's/^version: *//')
endefNote that the body of the get_version macro is
not indented because Make macros use text-substitution and
whitespace is significant. The version rule above can be
rewritten as follows:
version: # show current version
> @echo "$(PROJECT) $(call get_version)"
.PHONY: versionIn cases when the variable may be overridden by a user or is used
more than once in the same rule, eval may be used as usual.
In the following example, a deb rule allows the user to
specify which version to build a package for.
deb: # build .deb package for VERSION in a Debian container
> $(eval VERSION := $(call get_version))
...
.PHONY: debNote that use of a macro also allows parameters to be used. For example, the base project has two files where the project version is specified. An optional parameter allows you to specify the file to get the version from.
define get_version
$(shell grep ^BASE_VERSION $(if $(origin 1) == undefined,base.sh,$(1)) \
        | sed -e 's/^[^"]*"//' -e 's/"$$//')
endefThis macro can be called as in the deb rule above, or it
can be called with an argument. The version rule for this
project shows both versions, which is convenient for confirming
that they are synchronized.
version: # show current version
> @echo "base.sh          $(call get_version, base.sh)"
> @echo "base_activate.sh $(call get_version, base_activate.sh)"
.PHONY: version