Refdoc
Refdoc

Reputation: 21

Makefile with foreach loop - *** missing separator

I am new to makefiles.

I have a large number of fairly similar directories each of which contains a makefile and some source, all of which I want to address from the directory above with an overarching makefile. The makefiles in the directories work. The number of subdirectories will grow in the future, so whatever I design should be able to grow indefinitely.

If I call the subdirectory makefiles directly I have no problem,

mytarget:
    cd a && $(MAKE) mytarget
    cd b && $(MAKE) mytarget
    cd c && $(MAKE) mytarget

but I do get a **** missing separator error when I try the below attached. I have used tabs, no spaces, so I do not understand my error.

LIST = a b c d e

mytarget:
    $(foreach var,$(LIST),$(eval cd $(var) && $(MAKE) mytarget))

Upvotes: 1

Views: 3044

Answers (2)

MadScientist
MadScientist

Reputation: 100916

Another option that might be more understandable (or might not...) is to use a shell loop, not a makefile loop. Also this results in a shorter script which could be important if you get enough subdirectories and you want to work on systems with more limited command line length restrictions:

LIST = a b c d e

mytarget:
        for var in $(LIST); do $(MAKE) -C $$var $@; done

Or if you would prefer to use cd instead of the GNU make -C option, something like this:

LIST = a b c d e

mytarget:
        for var in $(LIST); do (cd $$var && $(MAKE) $@); done

Note we use parentheses here to start the make in a subshell, so we don't have to use something like cd .. (which will not work if some element in LIST is a path rather than a simple directory, e.g. f/g/h or similar).

The big problem with these solutions is the error handling is all broken. What if the sub-make for the a target fails? These makefiles will not notice that. Also, parallelism is not ideal: in all cases here only a single sub-make will be invoked at a time.

The best way to write loops in makefiles is using multiple prerequisites, not a loop inside a recipe. Something like this:

LIST = a b c d e

mytarget: $(addprefix mytarget-,$(LIST))

mytarget-%:
        $(MAKE) -C $* mytarget

(or, cd $* && $(MAKE) mytarget)

Upvotes: 0

Etan Reisner
Etan Reisner

Reputation: 80969

You don't want $(eval) there. It is wrong. The contents being passed to it are not makefile contents they are shell contents.

You want:

LIST = a b c d e

mytarget:
        $(foreach var,$(LIST),cd $(var) && $(MAKE) mytarget; cd ..;)

Because you want the result of the $(foreach) call to be the recipe body.

Upvotes: 1

Related Questions