Following Recipe 7, Limiting scope with add_subdirectory, we will discuss the CMake structure from the bottom up, from the individual CMakeLists.txt files defining each library, such as src/evolution/CMakeLists.txt:
add_library(evolution "")
target_sources(evolution
PRIVATE
empty.f90
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/ancestors.f90
${CMAKE_CURRENT_LIST_DIR}/evolution.f90
)
These individual CMakeLists.txt files define libraries as close as possible to the sources, following the same reasoning as in previous two recipes: code developers with knowledge of this library and possibly limited knowledge of the CMake framework only need to edit files in this directory: divide and conquer.
We first define the library name with add_library and then define its sources and include directories, as well as their target visibility. In this case, both ancestors.f90 and evolution.f90 are PUBLIC since their module interfaces are accessed outside the library, whereas the module interface of empty.f90 is not accessed outside the file and therefore we mark this source as PRIVATE.
Moving one level up, the libraries are assembled in src/CMakeLists.txt:
add_executable(automata main.f90)
add_subdirectory(evolution)
add_subdirectory(initial)
add_subdirectory(io)
add_subdirectory(parser)
target_link_libraries(automata
PRIVATE
conversion
evolution
initial
io
parser
)
This file, in turn, is referenced in the top-level CMakeLists.txt. This means that we have built our project from a tree of libraries using a tree of CMakeLists.txt files, added using add_subdirectory. As discussed in Recipe 7, Limiting scope with add_subdirectory, this approach scales to large projects without the need to carry lists of source files in global variables across directories, with the added bonus of isolating scopes and namespaces.
Comparing this Fortran example with the C++ version (Recipe 7), we can note that we had to do less CMake work in the Fortran case; we do not have to use target_include_directories since there are no header files and interfaces are communicated via the generated Fortran module files. Also, observe that we neither have to worry about the order of source files listed in target_sources, nor do we have to impose any explicit dependencies between libraries! CMake is able to infer Fortran module dependencies from the source file dependencies. Using target_sources in combination with PRIVATE and PUBLIC allows us to express interfaces in a compact and robust fashion.