1

make recompiles source code files even if they are unchanged. To reproduce this behavior, I need to do:

  1. make clean, so that only *.90 files are left
  2. make
  3. save one of the *.f90 file, e.g. "touch bands.f90"
  4. make: bands.f90 is recompiled, which is correct, since it was changed
  5. make: bands.f90 is recompiled again, which is not correct, since bands.f90 was not touched

In this example, after 4. the file bands.f90 will be recompiled at every make call. If I additionally change any other source code file, the behavior also applies to that file. So after some time I end up recompiling all of my source code. I can partially fix this by calling make clean, which resets the loop. But it is not a permanent fix.

Below you see the output for the five steps above:

1: find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

2: [triggered by changes in const.f90]
   mpifort -c const.f90

   [triggered by changes in system.f90 const.mod]
   mpifort -c system.f90

   [triggered by changes in io.f90 system.mod const.mod]
   mpifort -c io.f90

   [triggered by changes in parallel.f90]
   mpifort -c parallel.f90

   [triggered by changes in bands.f90 system.mod io.mod const.mod parallel.mod]
   mpifort -c bands.f90

   [triggered by changes in polmob.f90 io.mod system.mod bands.mod parallel.mod]
   mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

3: "no output"

4: [triggered by changes in bands.f90]
   mpifort -c bands.f90

5: [triggered by changes in bands.f90]
   mpifort -c bands.f90

At step 5 make should actually say that there is nothing to compile. But is says that there were changes in bands.f90 and it therefore needs to be recompiled.

Here is my Makefile:

polmob: polmob.f90 io.mod system.mod bands.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

io.mod: io.f90 system.mod const.mod
    @echo [triggered by changes in $?]
    mpifort -c io.f90

system.mod: system.f90 const.mod
    @echo [triggered by changes in $?]
    mpifort -c system.f90

bands.mod: bands.f90 system.mod io.mod const.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -c bands.f90

const.mod: const.f90
    @echo [triggered by changes in $?]
    mpifort -c const.f90

parallel.mod: parallel.f90
    @echo [triggered by changes in $?]
    mpifort -c parallel.f90

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

The flag $? tells me, that the recompilation is triggered by changes in the particular .f90 file. So somehow make relates to old changes. Does anybody know that the cause for this behavior might be?

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
CiPoint
  • 55
  • 3
  • 1
    As I repeated many times, *make * is not an adequate tool for modern Fortran and it is PITA to keep it up-to-date when developing a complex code. There are so many modern alternatives... cmake, scons, fobis, waf,... I personally use scons and I don't have to care about the dependencies and unneeded recompilation at all. – Vladimir F Героям слава May 13 '19 at 13:18

2 Answers2

1

Does anybody know that the cause for this behavior might be?

I found the cause: If I replace "mod" by "o" everywhere in my Makefile, the problem disappears. However, I don't quite understand why.

The only plausible explanation is that the timestamps of the existing .mod files are not updated when the original sources are rebuilt. I presume that extends only as long as the module interface is unchanged -- i.e. no functions or subroutines are added or removed, or modified in a way that affects their external interface, and similarly no module variables are added, removed, or changed in type.

I can see that it might be considered desirable to avoid updating .mod files when nothing in them has actually changed, particularly in conjunction with module implementations stored in external libraries. To the best of my knowledge, however, there is no general standardization of even the existence of .mod files, much less the circumstances under which they are created or updated. Your compiler may offer an option that forces them to be updated when their corresponding source is compiled; if so, that adding that option to your compile commands ought to resolve your issue.

Otherwise, the thing that your compiler does surely promise is that if you ask it to compile a source file to an object file, then on success, it will write a new object file. Moreover, although I would not necessarily have guessed that the same would be untrue of the .mod files, it is important to understand that the latter normally describe the interfaces to your modules, not their implementations, so your main rule is semantically incorrect:

polmob: polmob.f90 io.mod system.mod bands.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

You need to rebuild if any of the implementations change, regardless of whether module interfaces change, and your rule does not adequately capture that requirement.

At this point I observe also that when you rebuild via that rule, you rebuild all the sources, making the per-source build rules pointless. If that's what you want, then a simpler solution would be to rewrite your while makefile to this:

SOURCES = polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

polmob: $(SOURCES)
    @echo [triggered by changes in $?]
    mpifort -o $@ $(SOURCES)

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

There, the target is actually built from the specified prerequisites, and the build recipe is sufficient to build the target from those prerequisites. However, if any of the sources change, it will rebuild them all (similar to what your original makefile does).

The alternative is to build individual objects and then link them together in a separate step. Your original makefile seems to lean in that direction, but then it throws it away with the main rule. If you want to take that approach, then it becomes complicated by the facts that

  • it really is the module interfaces on which the individual objects depend (in addition to their own sources), so expressing prerequisites in terms of other object files is incorrect in these cases;
  • both the .o and .mod files are built by the same process (i.e. it has multiple outputs);
  • and apparently, that process uses different logic than make does to determine whether the .mod files are out of date.

The multiple outputs issue is probably the hairiest one; you can find a discussion and alternative solutions of varying rigor in the Automake documentation (but not specific to Automake).

Here's a way you could approach that, inspired by the Automake docs but accounting for the peculiarity you discovered in your Fortran implementation:

# All the object files contributing to the program:
OBJECTS = bands.o const.o io.o parallel.o polmob.o system.o

# Link all the objects together to form the final program
# (.mod files are typically not needed for this step)
polmob: $(OBJECTS)
    mpifort -o $@ $(OBJECTS)

# Rules for building the objects

bands.o : bands.f90 const.mod io.mod parallel.mod system.mod
    mpifort -c bands.f90 -o $@

const.o : const.f90
    mpifort -c const.f90 -o $@

io.o : io.f90 const.mod system.mod
    mpifort -c io.f90 -o $@

parallel.o : parallel.f90
    mpifort -c parallel.f90 -o $@

polmob.o : polmob.f90 bands.mod const.mod io.mod parallel.mod system.mod
    mpifort -c polmob.f90 -o $@

system.o : system.f90 const.mod
    mpifort -c system.f90 -o $@

# Rules for ensuring that .mod files are (re)created when needed, and
# that their timestamps do not fall behind those of their corresponding
# sources

bands.mod : bands.o
    @if test -f $@; then touch $@; else \
      rm -f bands.o; \
      $(MAKE) bands.o; \
    fi

const.mod : const.o
    @if test -f $@; then touch $@; else \
      rm -f const.o; \
      $(MAKE) const.o; \
    fi

io.mod : io.o
    @if test -f $@; then touch $@; else \
      rm -f io.o; \
      $(MAKE) io.o; \
    fi

parallel.mod : parallel.o
    @if test -f $@; then touch $@; else \
      rm -f parallel.o; \
      $(MAKE) parallel.o; \
    fi

system.mod : system.o
    @if test -f $@; then touch $@; else \
      rm -f system.o; \
      $(MAKE) system.o; \
    fi

####

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

This exhibits the correct dependencies:

  • The main program depends (only) on all the objects.
  • Each object depends on its own source and on the .mod files for those modules it uses.

It also accounts for the fact that the same compilation process generates both an object file and (where appropriate) a corresponding .mod file, and it takes care of updating .mod timestamps where that is needed to satisfy make. It may occasionally cause files to be rebuilt when they didn't really need to be, because it will thwart any attempt by the compiler to avoid updating .mod files' timestamps. But that should be on a one-time basis, not every subsequent build.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

I found the cause: If I replace "mod" by "o" everywhere in my Makefile, the problem disappears. However, I don't quite understand why.

CiPoint
  • 55
  • 3