Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make rule that invokes another rule several times with different values for a variable

I have a rule something, that works on the variable VAR. I also have another rule something-all, that needs to run something, with VAR set to each value in vars.

vars = hello world

something:
    echo $(VAR)

something-all:
    $(foreach VAR,$(vars),something)

This doesn't quite work, I get

noob@work:~/Desktop$ make something-all
something something
make: something: No such file or directory
make: *** [something-all] Error 1

It should probably print hello\nworld.

I used to do this with wildcard rules by retrieving VAR from %, but got the feeling that was the wrong way to do it. This looked like this:

vars = hello world

all: $(foreach VAR,$(vars),something-$(VAR))

something-%:
    echo $*
like image 821
Matt Joiner Avatar asked Jan 14 '16 00:01

Matt Joiner


4 Answers

The below should fix your problem

Using foreach (Tried on GNU Make 3.80 on sparc-solaris 2.8 and windows)

vars = hello world

something:
    echo $(VAR)

something-all:
    $(foreach i, $(vars), $(MAKE) something VAR=$i || exit 1;)

Using shell for-loop (Tried on GNU Make 3.80 and cc make on sparc-solaris 2.8)

vars = hello world

something:
    echo $(VAR)

something-all:
    for i in $(vars); do $(MAKE) something VAR=$$i || exit 1; done
like image 167
Vikhram Avatar answered Nov 19 '22 15:11

Vikhram


TL;DR: If you want to program make, drop GNU Make in favor of BSD Make.

This is a personal recommendation. While BSD Make seems more limited than GNU Make, as it offers less programming facilities, it is much easier to program and has a few unique killer features. This is why I propose a solution with GNU Make and another solution for BSD Make:

Doing it in GNU Make

Using GNU Make, you can write a macro to define a target. The canonical way to define a sequence in a Makefile is to add the steps of the sequence as dependencies to a target, as reflected by the snippet below:

vars= hello world

define something_t =
something: something-$(1)
something-$(1):
    @echo $(1)
endef

$(foreach _,$(vars),$(eval $(call something_t,$_)))

It is recommended to use this organisation (rather than defining just one target), because you can work on it to make the task easily resumable if you interrupt the sequence. A Makefile describes a job whose advancement is entirely described by the state of the file system. A task is then easily resumable, if each step is associated to a file, usually a compilation object but sometimes also an empty file which is touch'ed to indicate that important checkpoints have been passed.

Using an auxiliary macro is a flexible solution that can be adapted to more complicated tasks than just echoing a name. Note that this does work with newest versions of GNU Make (4.1). On GNU Make 3.81, you should remove the equal sign from the macro definition.

Adapting your example for BSD Make

If this is an option for you, I recommand dropping the use of GNU Make and replace it by BSD Make, which is way easier to program: it has a short and to the point documentation, while the documentation of GNU Make is very verbose and somewhat unclear, BSD Make has industrial-strength examples of complex rulesets (FreeBSD Build system or BSD Owl), and it has a simple and predictable macro language.

vars= hello world

something:
.for _var in ${vars}
    echo ${_var}
.endfor

This can evolve to support more complicated tasks, just by replacing the echo by the adapted commands, or using intermediary steps.

Allow the user to override some tasks, also in BSD Make

In this slightly more advanced variation, we allow the user to override our own recipes for building targets something-hello and something-world. For each item in our list, a target something-* is created it if it does not already exist, and added to the dependencies of something. The whole operation of defining these targets only happens if something has been left undefined. Therefore, users of these macros can:

  1. Override the recipes for something-hello and something-world
  2. Override the full procedure bound to something.

Implementing such customisation possibilities is mandatory if we want to write useful, reusable, macros for Make. Unluckily, customisation of this sort is nearly impossible in GNU Make.

vars = hello world

.if!target(depend)
.for _var in ${vars}
.if!target(something-${_var})
something-${_var}:
    echo ${_var}
.endif
something: something-${_var}
.endfor
.endif
like image 43
Michaël Le Barbier Avatar answered Nov 19 '22 14:11

Michaël Le Barbier


Here's one way to do it:

VARS := hello world
THINGS := $(addprefix something-, $(VARS))

allthings: $(THINGS)

something-%:
    echo $*
like image 2
Beta Avatar answered Nov 19 '22 15:11

Beta


It should be no surprise that

vars := hello world
something-all:
    $(foreach VAR,$(vars),something)

tries to run something something. That's exactly what the foreach expands to, since you don't reference VAR in the third expression.

All you need to do is reference VAR and use a command such as echo:

vars := hello world
something-all:
    $(foreach VAR,$(vars),echo $(VAR);)

$ make
echo hello; echo world;
hello
world

Note how chaining the commands with a semicolon avoids forking several shells or -- GASP! -- recursive make invocations. It doesn't get more performant than that.

Alternatively, if your command accepts several somethings as arguments,

vars := hello world
something-all:
    echo $(foreach VAR,$(vars),$(VAR))

$ make
echo hello world
hello world

But that is equivalent to the super simple echo $(vars). So it might pay off to think outside the box trying to change your requirements to make this simple solution work.

like image 1
Jens Avatar answered Nov 19 '22 16:11

Jens