1

I have been trying to create a makefile that takes multiple sources in a file tree and produces an individual binary for each source. My current project is structured like so:

topdir/
       bin/
       [Other Directories]
       Gamma/
           Makefile
           5a5/
               prog1.5.f03
               prog2.5.f03
               .
               .
               .
           5a10/
               prog1.10.f03
               prog2.10.f03
               .
               .
               .
           5a15/
           .
           .
           .

I would like to produce a binary for every source file in the subdirectories and place those in bin/. Below is my current Makefile:

#Compliler
FC=gfortran

#Directories for build and sources
BIN=topdir/bin
FROOT=topdir/Gamma
#Source files
SRCF =$(wildcard $(FROOT)/5a5/*.f03)
SRCF+=$(wildcard $(FROOT)/5a10/*.f03)
SRCF+=$(wildcard $(FROOT)/5a15/*.f03)
SRCF+=$(wildcard $(FROOT)/5a20/*.f03)
SRCF+=$(wildcard $(FROOT)/5a25/*.f03)
#Binary targets
EXES=$(addprefix $(BIN)/,$(notdir $(patsubst %.f03,%,$(SRCF))))

#Flag to place mods with binaries
FCFLAGS= -J $(BIN)

all: $(EXES)

$(EXES): $(SRCF)
        $(FC) $(FCFLAGS) -o $@ $<

This works but only compiles the first file in $(SRCF) to all targets. I would like to find a way for this to run my list of sources and match one target to one source of the same name.

I looked through these three other questions to get me to this point, but I am missing a final piece to get me to my goal.

*C++ Single Makefile Multiple Binaries

*Compile multiple executables from multiple source directories to single bin directory using makefile

*Makefile: Compiling from directory to another directory

Thanks in advance!

David
  • 11
  • 2
  • Have you considered using `vpath`, and if so is that not suitable in some way? – francescalus Jul 26 '21 at 19:28
  • I'm assuming that in your examples of the directory structure where you say the files end in `.f` that that's an error, and that they really end in `.f03` as your makefile looks for them. – MadScientist Jul 26 '21 at 19:38
  • @MadScientist That was an error the file extensions are all ```.f03```. @francescalus I tried using ```vpath```, I will admit I'm not familiar with it so I may have misapplied it. However when I do implement ```vpath``` I still end up with the same issues. – David Jul 26 '21 at 19:49

1 Answers1

3

This error is one of the single most common I see on StackOverflow. But I can't figure out how to prevent people from making it.

$(EXES): $(SRCF)
        $(FC) $(FCFLAGS) -o $@ $<

What do folks expect make to do when it sees this rule? What make actually does is the same thing it does whenever it expands any other rule: first it expands the variables, then it interprets the rule.

After expanding the variables we get this:

bin/prog1.5 bin/prog2.5 ... bin/prog1.10 bin/prog2.10 ... : topdir/Gamma/5a5/prog1.5.f03 topdir/Gamma/5a5/prog2.5.f03 ... topdir/Gamma/5a10/prog1.10.f03 topdir/Gamma/5a10/prog2.10.f03 ...
        $(FC) $(FCFLAGS) -o $@ $<

So now we have a rule that lists all the executables as targets and all the source files as prerequisites. How do people think make will interpret this? I guess they expect make to somehow match up the first executable with the first source, the second executable with the second source, or something like that?

The way make actually interprets this is that it creates one rule for every target, with all the prerequisites to every rule. So, the above is interpreted by make exactly as if you'd typed:

bin/prog1.5 : topdir/Gamma/5a5/prog1.5.f03 topdir/Gamma/5a5/prog2.5.f03 ... topdir/Gamma/5a10/prog1.10.f03 topdir/Gamma/5a10/prog2.10.f03 ...
        $(FC) $(FCFLAGS) -o $@ $<
bin/prog2.5 : topdir/Gamma/5a5/prog1.5.f03 topdir/Gamma/5a5/prog2.5.f03 ... topdir/Gamma/5a10/prog1.10.f03 topdir/Gamma/5a10/prog2.10.f03 ...
        $(FC) $(FCFLAGS) -o $@ $<
bin/prog1.10 : topdir/Gamma/5a5/prog1.5.f03 topdir/Gamma/5a5/prog2.5.f03 ... topdir/Gamma/5a10/prog1.10.f03 topdir/Gamma/5a10/prog2.10.f03 ...
        $(FC) $(FCFLAGS) -o $@ $<
bin/prog2.10 : topdir/Gamma/5a5/prog1.5.f03 topdir/Gamma/5a5/prog2.5.f03 ... topdir/Gamma/5a10/prog1.10.f03 topdir/Gamma/5a10/prog2.10.f03 ...
        $(FC) $(FCFLAGS) -o $@ $<

And also for all the rest of the bin/* files. Perhaps you can understand the problem you're having now: the $< is always expanding to the first prerequisite and the first prerequisite is always the first file in the list. Clearly that can't work.

In make you have to write a rule that builds one target from only its prerequisites.

The problem you have is that you are building targets into one directory from a lot of different directories. That means you can't write one pattern rule because there's no pattern that can match this. You could write one pattern rule for each directory, like this:

$(BIN)/% : $(FROOT)/5a5/%.f03
        $(FC) $(FCFLAGS) -o $@ $<
$(BIN)/% : $(FROOT)/5a10/%.f03
        $(FC) $(FCFLAGS) -o $@ $<

etc. for each directory.

Or you could use VPATH, as mentioned in the comments above, and write a single pattern rule; it would be something like this:

VPATH := $(dir $(SRCF))

$(BIN)/% : %.f03
        $(FC) $(FCFLAGS) -o $@ $<
MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • I recommend you switch to using `:=` for your assignments: it will save make a lot of duplicated work. – MadScientist Jul 26 '21 at 19:53
  • I don't understand. You should replace **ONLY** the one rule I mentioned in my comment. You should not delete or replace the rest of your makefile. In particular, you must keep the `all: $(EXES)` target. – MadScientist Jul 26 '21 at 20:07
  • When I implement the line with the ```vpath``` and rule I get the error: ```make: *** No rule to make target `/topdir/bin/prog1.5', needed by `all'. Stop.``` – David Jul 26 '21 at 20:17
  • Oh. In your question you said you wanted to create `bin/foo` but looking at your makefile, you actually want to create `topdir/bin/foo`. You should use the variable `$(BIN)` in your pattern rule. However, I can't explain where the initial `/` comes from. Based on the makefile you've provided here, that shouldn't exist. You'll have to print out the value of various variables to see why `$(EXES)` contains starting slashes. You can do this with the `$(info ...)` function. – MadScientist Jul 26 '21 at 20:19
  • There was a rogue keystroke error when I moved my variables from ```=``` to ```:=``` this was the source of my error. The make file compiled properly. Thank you for your help! – David Jul 26 '21 at 20:26