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
Makefile
s.
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: *//'))
"$(PROJECT) $(VERSION)"
> @echo .PHONY. version
The 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 Makefile
s so
that they are easier to maintain.
define get_version
$(shell grep '^version:' $(CABAL_FILE) | sed 's/^version: *//')
endef
Note 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
"$(PROJECT) $(call get_version)"
> @echo .PHONY: version
In 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: deb
Note 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/"$$//')
endef
This 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
"base.sh $(call get_version, base.sh)"
> @echo "base_activate.sh $(call get_version, base_activate.sh)"
> @echo .PHONY: version