makefilecompilationgnu-make

Using foreach in Makefile to compile subdirectories separately


I have the following Makefile sample:

BUILD = build
PATH_POT3 = src/pot/pot3/
MPIFC = mpiifort
FFLAGS = -O3

MOL_LIST = $(shell find $(PATH_POT3) -mindepth 1 -maxdepth 1 -type d)

$(foreach MOL,$(MOL_LIST), \
  $(eval LIST_MOL := $(shell find $(MOL) -name "*.f90")) \
  $(info Fortran files: $(LIST_MOL)) \
  $(foreach SRC,$(LIST_MOL), \
    $(eval OBJ := $(BUILD)/$(notdir $(SRC:.f90=.o))) \
    $(OBJ): $(SRC) ; \
    $(info Compiling: $(SRC)) \
    $(info Output: $(OBJ)) \
    $(MPIFC) $(FFLAGS) -c $(SRC) -o $(OBJ) \
  ) \
)

all: $(foreach MOL,$(MOL_LIST),\
    $(foreach SRC,$(shell find $(MOL) -name "*.f90"),\
        $(BUILD)/$(notdir $(SRC:.f90=.o))\
    )\
)

Here I would like to compile the (recursive) content of each PATH_POT3 subdirectory in a sequential way (one subdirectory after another). Unfortunately, this construction does not provide the desired result as it compiles only the first object file in the list, all the other object files are skipped. The Makefile prints out the following information for my test case:

Fortran files: src/pot/pot3/ar3/pot_ar3.f90 src/pot/pot3/ar3/ar43/pot_ar43.f90
Compiling: mpiifort -O3 -c src/pot/pot3/ar3/pot_ar3.f90 -o build/pot_ar3.o
Output: build/pot_ar3.o
Compiling: mpiifort -O3 -c src/pot/pot3/ar3/ar43/pot_ar43.f90 -o build/pot_ar43.o
Output: build/pot_ar43.o
Fortran files: src/pot/pot3/ar33/pot_ar33.f90 src/pot/pot3/ar33/pot_ar333.f90 src/pot/pot3/ar33/ar53/pot_ar53.f90
Compiling: mpiifort -O3 -c src/pot/pot3/ar33/pot_ar33.f90 -o build/pot_ar33.o
Output: build/pot_ar33.o
Compiling: mpiifort -O3 -c src/pot/pot3/ar33/pot_ar333.f90 -o build/pot_ar333.o
Output: build/pot_ar333.o
Compiling: mpiifort -O3 -c src/pot/pot3/ar33/ar53/pot_ar53.f90 -o build/pot_ar53.o
Output: build/pot_ar53.o
make: 'build/pot_ar3.o' is up to date.

Everything seems to be set correctly, but the compilation is executed only for build/pot_ar3.o.

How should I change this file to obtain the required behavior?


Solution

  • The issue is with how you're trying to create rule definitions inside the foreach loop. Your info statements show everything is being detected correctly, but only the first object file is being compiled. This happens because Make doesn't process dynamically defined rules within loops the way you're expecting.

    Here's a working solution:

    BUILD = build
    PATH_POT3 = src/pot/pot3/
    MPIFC = mpiifort
    FFLAGS = -O3
    
    SRCS := $(shell find $(PATH_POT3) -name "*.f90")
    OBJS := $(patsubst %.f90,$(BUILD)/%.o,$(notdir $(SRCS)))
    
    all: $(OBJS)
    
    $(BUILD):
        mkdir -p $(BUILD)
    
    define make-depend
    $(BUILD)/$(notdir $(basename $(1))).o: $(1) | $(BUILD)
        @echo "Compiling $$< -> $$@"
        $(MPIFC) $(FFLAGS) -c $$< -o $$@
    endef
    
    $(foreach src,$(SRCS),$(eval $(call make-depend,$(src))))
    
    .PHONY: all clean
    
    clean:
        rm -rf $(BUILD)
    

    The key here is using eval to actually create proper Make rules from the make-depend function. Your original approach was printing the commands during the parsing phase but wasn't correctly setting up the actual rules.

    This works by finding all source files up front and creating a proper rule for each file using eval with the make-depend function. The | symbol ensures the build directory exists before compilation starts.

    Hope this will work for you.

    ** Edit **

    This is what we've discussed in chat:

    BUILD = build
    PATH_POT3 = src/pot/pot3
    MPIFC = mpiifort
    FFLAGS = -O3
    LDFLAGS =
    
    MOL_LIST = $(shell find $(PATH_POT3) -mindepth 1 -maxdepth 1 -type d)
    
    SRCS := $(shell find $(PATH_POT3) -name "*.f90")
    OBJS := $(patsubst %.f90,$(BUILD)/%.o,$(notdir $(SRCS)))
    
    all: $(OBJS) $(patsubst %,$(BUILD)/%.exe,$(notdir $(MOL_LIST)))
    
    $(BUILD):
       mkdir -p $(BUILD)
    
    define make-depend
    $(BUILD)/$(notdir $(basename $(1))).o: $(1) | $(BUILD)
       @echo "Compiling $$< -> $$@"
       $(MPIFC) $(FFLAGS) -c $$< -o $$@
    endef
    
    $(foreach src,$(SRCS),$(eval $(call make-depend,$(src))))
    
    define make-executable
    MOL_SRCS_$(1) := $$(shell find $(1) -name "*.f90")
    MOL_OBJS_$(1) := $$(patsubst %.f90,$(BUILD)/%.o,$$(notdir $$(MOL_SRCS_$(1))))
    
    $(BUILD)/$$(notdir $(1)).exe: $$(MOL_OBJS_$(1)) | $(BUILD)
       @echo "Linking $$@ from $$^"
       $(MPIFC) $(LDFLAGS) $$^ -o $$@
    endef
    
    $(foreach mol,$(MOL_LIST),$(eval $(call make-executable,$(mol))))
    
    .PHONY: all clean
    
    clean:
       rm -rf $(BUILD)