Write generic Makefile rule for subdirectories

Viewed 95

I have a project with a directory tree that looks like this:

.
├── modules
│   ├── mod1
│   │   └── mod1.f90
│   ├── mod2
│   │   └── mod2.f90
│   └── mod.f90
└── src
    └── main.f90
├── bin
└── Makefile

main.f90 uses all the modules found in modules and in the 'submodules' that are mod1/ and mod2/.

A simple Makefile I can write to compile the project is something like:

F90      = gfortran
FLAGS    = -g -I$(BINDIR)
MODFLAGS = -J$(BINDIR)

BINDIR = bin
SRCDIR = src
MODDIR = modules
SMODDIR = $(dir $(wildcard $(MODDIR)/*/.))

MODFILES  = $(wildcard $(MODDIR)/*f90)
MODOBJ    = $(join $(addsuffix $(MODFROMBIN)/, $(dir $(MODFILES))), \
          $(notdir $(MODFILES:.f90=.o)))
SMODFILES = $(foreach smoddir, $(SMODDIR), $(wildcard $(smoddir)*f90))
SMODOBJ   = $(join $(addsuffix $(SMODFROMBIN)/, $(dir $(SMODFILES))), \
          $(notdir $(SMODFILES:.f90=.o)))
SRCFILES  = $(wildcard $(SRCDIR)/*f90)
SRCOBJ    = $(join $(addsuffix $(SRCFROMBIN)/, $(dir $(SRCFILES))), \
          $(notdir $(SRCFILES:.f90=.o)))
ALLOBJ    = $(MODOBJ) $(SMODOBJ) $(SRCOBJ)
BINOBJ   = $(addprefix $(BINDIR)/, $(sort $(notdir $(ALLOBJ))))

all: main

$(MODDIR)/../bin/%.o: $(MODDIR)/%.f90
        $(F90) $(FLAGS) -c $^ -o $@ $(MODFLAGS)
modules/mod1/../../bin/%.o: modules/mod1/%.f90
        $(F90) $(FLAGS) -c $^ -o $@ $(MODFLAGS)
modules/mod2/../../bin/%.o: modules/mod2/%.f90
        $(F90) $(FLAGS) -c $^ -o $@ $(MODFLAGS)
$(SRCDIR)/../bin/%.o: $(SRCDIR)/%.f90
        $(F90) $(FLAGS) -c $^ -o $@
main: $(ALLOBJ)
        $(F90) $(FLAGS) -o ./bin/main $(BINOBJ)

clean:
        @rm bin/*.o bin/*.mod

But now I want to write a generic rule to be able to compile all modules located in directories that are inside modules directory (the code I am working on have more than two submodules and I am not willing to write as many rules as I have subdirectories).

My first try was to write something like this:

$(SMODDIR)/../../%.o: $(SMODDIR)/%.f90                                                                                                                                                             
       $(F90) $(FLAGS) -c $^ -o $@ $(MODFLAGS)                                                                                                                                                              

but it fails; from what I understand, it will put all the subdirectories paths and I would actually end up with a rule that would looks like this:

modules/mod1/../../bin/ modules/mod2/../../bin/%.o: modules/mod1/ modules/mod2/%.f90

that indeed looks funny.

As shown in the original Makefile, it is possible to retrieve specificaly with

SMODDIR = $(dir $(wildcard $(MODDIR)/*/.))
SMODFILES = $(foreach smoddir, $(SMODDIR), $(wildcard $(smoddir)*f90))

so I guess it may be possible to use something similar to have more generic rules. I couldn't see however how to use such a syntax to write a rule that makes sense.

Any help would be appreciated!

1 Answers

You have two choices to avoid writing lots of rules.

One is, you can use VPATH and put all your source directories in it, something like this:

VPATH := $(MODDIR) $(SMODDIR) $(SRCDIR)

$(BINDIR)/%.o: %.f90
        $(F90) $(FLAGS) -c $< -o $@ $(MODFLAGS)

Or you can put all your object files into equivalent subdirectories of $(BINDIR), something like this:

MODOBJ    = $(MODFILES:%.f90=$(BINDIR)/%.o)
SMODOBJ   = $(SMODFILES:%.f90=$(BINDIR)/%.o)
SRCOBJ    = $(SRCFILES:%.f90=$(BINDIR)/%.o)

$(BINDIR)/%.o: %.f90
        @mkdir -p $(@D)
        $(F90) $(FLAGS) -c $< -o $@ $(MODFLAGS)
Related