How to prevent CMake usage of an outdated fortran module

I’m using CMake to build a moderate-sized Fortran project. I noticed that sometimes when I refactor modules I ran into weird compilation errors that are gone after clean rebuild in a fresh build directory.

I’ve managed to reproduce the problem in the following minimal example. Consider a project with two libraries and a single executable

├── build
├── CMakeLists.txt
├── liba
│   ├── CMakeLists.txt
│   ├── first.f90
│   └── second.f90
├── libb
│   ├── CMakeLists.txt
│   └── third.f90
└── main.f90

The main.f90 is simply

program test
  use first, only: x
  use second, only: y
  use third, only: z
  implicit none
  print *, x, y, z
end program

and the root CMakeLists.txt is

project(test)
enable_language(Fortran)

add_subdirectory(liba)
add_subdirectory(libb)

add_executable(main main.f90)
target_link_libraries(main a b)

Each module just exports a single parameter value

! first.f90
module first
  integer, parameter :: x = 1
end module first
! second.f90
module second
  integer, parameter :: y = 2
end module second
! third.f90
module third
  integer, parameter :: z = 3
end module third

The libraries are described similarly as

# liba/CMakeLists.txt
add_library(a
    first.f90
    second.f90
)
target_include_directories(a PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
# libb/CMakeLists.txt
add_library(b
    third.f90
)

target_include_directories(b PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

Building and running I get 1 2 3, as expected.

Now the problem. If I move the second.f90 to libb and change y = 2 to y = 42 I still have 1 2 3 as the output, even if I do full rebuild using make -C build clean all. This is because liba output directory contains second.mod with outdated parameter value y = 2 and it is used instead of the correct module in the libb build directory. The Ninja generator behaves just the same.

I greatly appreciate any suggestions how to fix my build scripts to avoid this kind of errors.

Just in case you want to try it yourself, here is the example on GitHub:

$ git clone https://github.com/uranix/cmake-fortran-modules
$ cd cmake-fortran-modules
$ git checkout a90ddfc
$ cmake -B build
$ make -C build
$ ./build/main 
           1           2           3
$ git checkout 736b738
$ make -C build
$ ./build/main 
           1           2           3
$ make -C build clean all
$ ./build/main 
           1           2           3
$ cmake -B build2
$ make -C build2
$ ./build2/main 
           1          42           3

Can you please try 3.27.1? I recently fixed some dependency bits for the Fortran module logic.

Tried 3.27.20230809-e525f856, the issue persists. Directory build/liba still contains an outdated version for second.mod.

Oh, right. So the issue here is that Fortran searches for modules using -I and searching instead of getting more direct “this is module a’s file” information. I don’t know exactly what we can do to clean up modules that we don’t know about anymore :confused: . We’d have to store the information in a “previous” file, diff it, then remove things no longer expected to be generated.

I am, in general, wary about build systems deleting files that are (no longer) mentioned in the build graph because we don’t actually know what its purpose is or how to get it back.

And if Fortran is completely removed from a target, we wouldn’t generate the command for module collation in the first place, so the cleanup bridge would be lost as well.

I think it is possible to maintain a separate module directory with symlinks or copies of .mod files. And keep it up to date by removing outdated links / copies

I suppose the collator could:

  • prepare a dedicated directory for .mod files
  • upon finding out what modules will be created, delete all other module files

Issues:

  • users can currently control this directory (via Fortran_MODULE_DIRECTORY and if these overlap, we don’t know what else is supposed to be here
  • users need to know where they exist at configure time to write install() rules (or we go with C++'s solution which generates install script snippets in the collator)