makefile: foreach "make -C" call

10,160

That's because you are calling a single make, with lots of arguments including the subsequent make commands. You need to add a shell command separator between the invocations of make. Something like this will work:

all:
        $(foreach c,$(ALLMAKES),$(MAKE) -C $(c) && ) true

On second look, there are other issues with your makefile. You should not set a make variable named PATH, because that will override the subshell's $PATH variable. You can just use the make built-in variable $(CURDIR) rather than running $(shell pwd). You don't actually need to prefix your directories with the path at all, because running $(MAKE) -C ... does not change the shell's working directory, just make's, and when make exits it will be set back, so you can just use a relative path.

Also invoking sub-makes in a loop has some issues. First, you reduce the amount of parallelization you can get. Second, the -k option can't be properly supported (without a lot of unpleasant effort). A better way to handle this issue is to take advantage of the fact that make already knows how to build lots of targets:

all: $(EFFECTS)
$(EFFECTS):
        $(MAKE) -C $@
.PHONY: all $(EFFECTS)

If you have specific ordering issues between the different subdirectories, you can define them as well:

impl/thing2: impl/thing1

etc. This ensures maximum parallelization opportunities, while still preserving important ordering.

To add alternative rules, say for clean, you can do something like this:

CLEAN_EFFECTS := $(addsuffix .clean,$(EFFECTS))
clean: $(CLEAN_EFFECTS)
$(CLEAN_EFFECTS):
        $(MAKE) -C $(basename $@) clean
.PHONY: clean $(CLEAN_EFFECTS)

This is nice because you can also build a single subdirectory (and all its prerequisites) by running make impl/thing1 for example. Or clean them by running make impl/thing1.clean

If you have more of these you can also get fancy with pattern rules, etc. to avoid repeating this for every type of target. It gets more cumbersome.

Share:
10,160
Olivier Trahan
Author by

Olivier Trahan

Updated on June 04, 2022

Comments

  • Olivier Trahan
    Olivier Trahan almost 2 years

    here is part of my makefile:

    PATH := $(shell pwd)
    EDIR := impl
    EFFECTS := $(filter-out $(EDIR), $(shell find $(EDIR) -maxdepth 1 -type d))
    ALLMAKES := $(patsubst %, $(PATH)/%, $(EFFECTS))
    
    
    all:
          $(foreach c,$(ALLMAKES),$(MAKE) -C $(c))
    

    So essentially, I want to call make for all directories in the "impl" directory without "impl" itself. I understand that make will remember the last directory it was at when last called with the -C argument which is why I give the absolute path everytime. What make echos seems to be what I want:

    make -C <projectdir>/impl/thing1 make -C <projectdir>/impl/thing2 make -C <projectdir>/impl/thing3
    

    The issue is that make doesn't accomplish the command and simply prints:

    make: make: Command not found. 
    

    I can call "make -C <path>" for each of the directories individually outside of the makefile but it doesn't work in the foreach call. I have tried this instead but it doesn't work either:

    $(foreach c,$(ALLMAKES),$(shell make -C $(c)))
    

    Any ideas?

  • MadScientist
    MadScientist about 11 years
    Generally in my makefiles I don't like to run sub-makes in a shell loop like this, because (a) it reduces opportunities for parallelism with -j and (b) it is almost impossible to make it work properly with respect to the -k option. But, it is a quick and dirty solution that answers the specific question and will work. I'll add an alternative which is "cleaner".
  • Olivier Trahan
    Olivier Trahan about 11 years
    Thank you! That last suggestion did the trick. I will take notice of your other suggestions for future makefiles (since I won't need $(CURDIR) if I use relative paths)
  • Olivier Trahan
    Olivier Trahan about 11 years
    I also want to add a clean rule for this... so call each of those "effects" the same way except by adding a clean at the end of the command like so: "make -C impl/thing1 clean" Is there a way to do this, since I can't duplicate the same target?
  • Olivier Trahan
    Olivier Trahan about 11 years
    For now, I am using your first suggestion with the foreach call and making a separate all and clean rule.