How to programmatically define targets in GNU Make?
Asked Answered
B

2

4

I am not aware of any way to define programatically targets in GNU Make. How is this possible?

Sometimes one can go away with alternate methods. The ability to define programatically targets in Makefiles is however a very important to write and organise complex production rules with make. Examples of complex production rules are found in the build system of FreeBSD or in Makefile libraries such as BSD Owl

The main differences between shell scripts and Makefiles are:

  • In a Makefile, the state of the program is given by the command line and the filesystem, so it is possible to resume a job after it has been interrupted. Of course, this requires to properly write the Makefiles, but even if this is rather hard, it is considerably easier than to achieve a similar effect with a shell script.

  • In a Makefile, it is ridiculously easy to decorate a procedure with advises or decorate it with hooks, while this is essentially impossible in shell scripts.

For instance, a very simple and useful pattern is the following:

build: pre-build
build: do-build
build: post-build

This presents the build target as a composite of three targets, one containing the actual instructions do-build and two other that are hooks, executed before and after do-build. This pattern is used by many build systems written for BSD Make, which incidentally allows programmatic definition of targets, so that one can write in a batch:

.for _target in configure build test install
.if !target(${_target})
${_target}: pre-${_target}
${_target}: do-${_target}
${_target}: post-${_target}
.endif
.endfor

The condition introduced by the .if/.endif block enables the user to use its own definition of any ${_target}.

What would be the translation of that snippet for GNU Make?

Berners answered 2/3, 2015 at 16:44 Comment(3)
It should be noted that in GNU make you do not get an in-order guarantee between target prerequisites (though without -j I believe it is generally safe to assume you do) so your "hooks" don't necessarily do what you want.Brogdon
@EtanReisner Of course, but this is not directly related to the question and I omitted this to keep things simple. In BSD Make, one says, for instance, .ORDER: pre-build do-build post-build to enforce serial processing of the mentioned targets.Nth
With or without -j, make will always walk the prerequisites list in the same order. It's just that with -j, make won't wait for previous (non-dependent) prerequisites to finish before starting new ones (of course this can cause some jobs to run in a different order due to prerequisites not completing). Without -j make does guarantee the in-order build.Inscribe
S
7

FWIW here is the make equivalent syntax for

.for _target in configure build test install
.if !target(${_target})
${_target}: pre-${_target}
${_target}: do-${_target}
${_target}: post-${_target}
.endif
.endfor

Basically, you want make to see something like this snippet:

build: pre-build
build: do-build
build: post-build

and similarly for configure, test and install. This suggests a loop with an eval somewhere:

define makerule =
  $1: pre-$1
  $1: do-$1
  $1: post-$1
endef

targets := configure build test install

$(foreach _,${targets},$(eval $(call makerule,$_)))

(to play with this, change eval to info). Careful with those closures!

FWIW, here's the expansion of the foreach:

  • make expands the list to be iterated over
    • ${targets} becomes configure, build, test and install
    • We have $(foreach _,configure build test install,$(eval $(call makerule,$_)))
  • _ is set to the first value, configure.
  • make expands $(eval $(call makerule,configure))
  • To evaluate the eval, make expands $(call makerule,configure)
    • It does this by setting 1 to configure, and expanding ${makerule} which produces 3 lines of text:
      configure: pre-configure
      configure: do-configure
      configure: post-configure
  • $(eval) goes to work, reading this text as make syntax
  • Note that the expansion of the $(eval) is empty! All its work is done as a side effect. Wash, lather, rinse, repeat.

Please note: I have to agree with all the other commenters: your pattern is bad make. If your makefile is not -j safe, then it is broken (missing dependencies).

Schoolmarm answered 9/3, 2015 at 16:52 Comment(1)
Thank you for your useful answer! As I stated in some other comments, I am well aware of parallelism issues and omitted them from my question because I was only interested in the programmatic aspects. But it's good that beginners read your parallelism banner! :DNth
I
3

First this structure is invalid if you ever want to support parallel builds; if you invoke make with the -j option it will run all three prerequisite rules at the same time, because while all of them must be complete before build, none of them depend on each other so there's no ordering defined (that is, you don't say that pre-build must be complete before do-build can run).

Second, GNU make has a number of facilities for programmatically defining rules. One thing GNU make does not have, currently, is the ability to search the targets which are already defined, so there's no direct analogy to .if !target(...).

However, you CAN search whether a variable has been defined or not using the .VARIABLES variable. So one workaround would be to define a variable if you want your own target and then have your rule generator check that.

Inscribe answered 2/3, 2015 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.