Multiple Build Invocations Required to Compile?

I’m running into an issue where multiple build invocations are required to complete compilation (because modules are not being compiled in the correct order). I’m using CMake 3.27.7 and Ninja 1.11.1 on Windows with the latest OneAPI Intel classic Fortran compiler (ifort).

I’m new to CMake, so this is almost certainly a PEBCAK issue, but I’m at a loss as to how best to resolve it. I’ve made a MWE, but unfortunately lack the permissions to attach it. But the primary structure is:

src
     library1
        CMakeLists.txt
        module1A.f90
        module1B.f90
    library2
        CMakeLists.txt
        module2A.f90
test
    library1
        CMakeLists.txt
        test_library1.f90
    library2
        CMakeLists.txt
        test_library1.f90
CMakeLists.txt        

The library1 CMakeLists.txt file looks like:

add_library(${library1Name} module1A.f90 module1B.f90)
set_target_properties(${library1Name} PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(${library1Name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

The library2 CMakeLists.txt is basically identical:

add_library(${library2Name} module2A.f90)
set_target_properties(${library2Name} PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/library2)
target_include_directories(${library2Name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/library2)

There are dependencies between the libraries via modules as (module1A.f90):

module module1A
    implicit none
    integer, parameter :: MODULE_1A_PARAMETER = 1
end module module1A

and module1B.f90:

module module1B
    use module2A, only : MODULE_2A_PARAMETER
    implicit none
    integer, parameter :: MODULE_1B_PARAMETER = MODULE_2A_PARAMETER + 2
end module module1B

and module2A.f90:

module module2A
    use module1A, only : MODULE_1A_PARAMETER
    implicit none
    integer, parameter :: MODULE_2A_PARAMETER = MODULE_1A_PARAMETER + 1
end module module2A

The test\library1\CMakeLists.txt only adds library1:

add_executable(test_library1 test_library1.f90)
target_link_libraries(test_library1 PUBLIC ${library1Name})

And the test_library1.f90 is simply:

program test_library1
    use module1A, only : MODULE_1A_PARAMETER
    use module1B, only : MODULE_1B_PARAMETER

    implicit none

    write(*,*) "MODULE_1A_PARAMETER = ", MODULE_1A_PARAMETER
    write(*,*) "MODULE_1B_PARAMETER = ", MODULE_1B_PARAMETER
end program test_library1

The test\library2 CMakeLists.txt is basically identical:

add_executable(test_library2 test_library2.f90)
target_link_libraries(test_library2 ${library2Name})

As is test_library2.f90:

program test_library2
    use module2A, only : MODULE_2A_PARAMETER

    implicit none

    write(*,*) "MODULE_2A_PARAMETER = ", MODULE_2A_PARAMETER
end program test_library2

The issue is obviously that library1 and library2 depend on each other. The root CMakeLists.txt attempts to resolve this as:

cmake_minimum_required(VERSION 3.27.7)

project(moduleTest LANGUAGES Fortran)

set(library1Name library1)
set(library2Name library2)

add_subdirectory(src)
add_subdirectory(test)

target_link_libraries(${library1Name} ${library2Name})
target_link_libraries(${library2Name} ${library1Name})

Obviously this is wrong, since although it can compile, it requires multiple build invocations to successfully do so (the first build invocation will complain that MODULE_1A_PARAMETER in module2A.f90 does not exist).

Is there a canonical or “best practices” way of handling this kind of circular dependency?

There’s no support for cycles in the module dependency graph at the target level. In the build graph, this would end up as a cycle between the collation outputs of each target (as they’d need each others’ output to know about the other). I suggest that you break up the module1 modules into separate libraries so that the cycle can be broken.

1 Like