14

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 $*
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526

4 Answers4

5

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
Vikhram
  • 4,294
  • 1
  • 20
  • 32
  • In the number 2, you need to add `|| exit 1` after the make invocation, so that the error from sub-make is propagated. See also my comment to Jens' answer: in the number 1 you ignore all make errors except the last one. – Dummy00001 Jan 19 '16 at 16:32
  • @Dummy00001 Thanks. I have updated the answer. As the OP did not ask for it, I did not include that in the sample; but OP might have just overlooked it. – Vikhram Jan 19 '16 at 18:06
3

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
Community
  • 1
  • 1
Michaël Le Barbier
  • 6,103
  • 5
  • 28
  • 57
  • 2
    IIRC dropping the = from the syntax will make it work on older versions of GNU make as well. – hkBst Jan 24 '16 at 08:06
  • @hkBst I just gave it a try. You are right, indeed! – Michaël Le Barbier Jan 24 '16 at 08:43
  • Just an additional note: if using niche software is not an issue, omake is one of the most advanced make utilities. http://omake.metaprl.org – Michaël Le Barbier Jan 25 '16 at 10:47
  • 1
    If I'm not mistaken, on Debian-based systems the BSD make is available as `bmake` (previously as `pmake`). – Dummy00001 Jan 25 '16 at 12:30
  • "Doing it in GNU Make". I have tested the trick and it is useless, since `$(eval)` forces premature evaluation of the variables like `$@`/`$<`/etc. To make it usable one has to add additional layer(s) of recursive make invocation (iow even more mess). – Dummy00001 Feb 23 '16 at 12:28
  • @Dummy00001 This is one of the various reasons why I avoid to use GNU Make and prefer BSD Make (usually `bmake`) over it, as it is [much easier to program](https://github.com/michipili/bsdowl). – Michaël Le Barbier Feb 23 '16 at 14:18
2

Here's one way to do it:

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

allthings: $(THINGS)

something-%:
    echo $*
Beta
  • 96,650
  • 16
  • 149
  • 150
1

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.

Jens
  • 69,818
  • 15
  • 125
  • 179