waLBerlaModuleDependencySystem.cmake 17.7 KB
Newer Older
1 2 3 4 5 6 7 8 9
#######################################################################################################################
#
# This file contains functions that are used by waLBerlaFunctions.cmake.
# The user typically only needs the functions in waLBerlaFunctions.cmake
#
#
# Here is an explanation of the waLBerla module mechanism:        
#  - One folder with a CMakeLists.txt that is a subfolder of one of the directories listed in the variable
#    WALBERLA_MODULE_DIRS can be a module
10
#  - the name of the module is the path relative to a WALBERLA_MODULE_DIRS entry
11 12
#  - waLBerla modules are all placed in the src/ subdirectory, so WALBERLA_MODULE_DIRS contains ${waLBerla_SOURCE}/src/
#  - to create a module call waLBerla_module() inside this folder
13 14
#  - this creates a static library that has the same name as the module, but slashes are replaced by minuses.
#    In case the module contains only header files no static lib is generated, only a custom target is added
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#    to display the module in Visual Studio.
#  - waLBerla_module takes a list of dependent modules. A second list of dependencies is generated by parsing
#    all files in the module for corresponding "#include" lines. This mechanism is not a complete preprocessor
#    and therefore has problems with #ifdef's etc
#    If you forgot to specify a dependency that is detected via this mechanism a warning is printed.
#  - The dependency list is used to store a dependency graph via the WALBERLA_MODULE_DEPENDS_* variables
#    see waLBerla_register_dependency and waLBerla_resolve_dependencies
#  - To add modules (i.e. module libraries ) to an application use target_link_modules
#    which uses the stored module graph to find all dependent modules
#
# The module dependencies are explicitly tracked, instead of using cmake's 
# native mechanism target_link_libraries because of the following reasons:
#  - All modules can be built in parallel
#  - Handling of header only modules
#  - dependencies of folders that are on the same level in directory hierarchy
#  
# This mechanism is just for convenience, one can simply compile an application that uses walberla modules
# by standard cmake mechanisms:
#  add_executable ( myApp  ${mySourceFiles} )
34
#  target_link_libraries ( myApp  ${WALBERLA_LINK_LIBRARIES_KEYWORD} walberlaModule1 core-field  lbm-boundary ) 
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
# The difference here is that all transitively depending modules also have to be specified manually.
# i.e. assume core-field depends on core-stencil, then core-stencil has to be added by hand. 
# If you use waLBerla_add_executable , these dependent modules are added automatically.
# 
#######################################################################################################################




#######################################################################################################################
#
# Determine Module name using the current folder 
# 
# moduleFolder is the current source directory relative to a folder in WALBERLA_MODULE_DIRS
# If more arguments are given, these are prepended to WALBERLA_MODULE_DIR
# Example:
#    If CMAKE_CURRENT_SOURCE_DIR is /src/core/field and /src/ is an element in WALBERLA_MODULE_DIRS,
#    then module name is "core/field"
#
#######################################################################################################################
function ( get_current_module_name  moduleNameOut )

    foreach( moduleDir ${ARGN} ${WALBERLA_MODULE_DIRS} )
        get_filename_component( moduleNameShort ${CMAKE_CURRENT_SOURCE_DIR} NAME_WE )
        file( RELATIVE_PATH moduleFolder ${moduleDir} ${CMAKE_CURRENT_SOURCE_DIR} )
        if ( NOT ${moduleFolder} MATCHES "\\.\\./.*" )
           set ( ${moduleNameOut} ${moduleFolder} PARENT_SCOPE )
           return() 
        endif()
    endforeach()
    
    message (WARNING "Called get_current_module_name, in a directory "
                     "that is not a subdirectory of WALBERLA_MODULE_DIRS\n"
                     "Module Dirs: ${WALBERLA_MODULE_DIRS} \n"
                     "Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}" )
    

endfunction ( get_current_module_name )
#######################################################################################################################




#######################################################################################################################
#
# Since slashes occur in module names, but slashes are not allowed in library names
# the library name is the module name, where all slashes are replaced by dashes
#
#######################################################################################################################

function ( get_module_library_name libraryNameOut moduleName )
    STRING( REPLACE "/" "-" libraryName ${moduleName} )
    
    set ( ${libraryNameOut} ${libraryName} PARENT_SCOPE )
    
endfunction ( get_module_library_name ) 
#######################################################################################################################



#######################################################################################################################
# 
# Registers a dependency between modules
# 
# Example: waLBerla_register_dependency( m1 m2 m3 m4)
#          registers a dependency of m1 to m2,m3 and m4
# 
# See also waLBerla_resolve_dependencies()
# 
#######################################################################################################################

function ( waLBerla_register_dependency moduleName )
    
    get_module_library_name ( moduleLibraryName ${moduleName} )
    
    # use the library name as part of the variable name
    set ( WALBERLA_MODULE_DEPENDS_${moduleLibraryName} ${ARG_DEPENDS} 
          CACHE INTERNAL "All modules that depend on ${moduleName}" FORCE )

endfunction( waLBerla_register_dependency )

#######################################################################################################################






#######################################################################################################################
# 
# Returns a list of all dependencies (transitively) of a given set of modules
#
# Example:
#   waLBerla_register_dependency ( A  B C  )      # A depends on B and C
#   waLBerla_register_dependency ( F  D    )      # F depends on D
#   waLBerla_register_dependency ( D  A    )      # cyclic dependencies are allowed  
#   waLBerla_resolve_dependencies( outlist  A F ) # outlist is now A F B C D 
#
# See also: waLBerla_register_dependency
# 
#######################################################################################################################

function ( waLBerla_resolve_dependencies outputList )
    
    set ( dependencyList ${ARGN} )
    
    set ( newDependencyFound 0)
    
    foreach ( module ${dependencyList} )
        foreach ( depModule ${WALBERLA_MODULE_DEPENDS_${module}} )
            get_module_library_name ( depModLibraryName ${depModule} )
            list(FIND dependencyList ${depModLibraryName} found)
            if ( found LESS 0 )
                list( APPEND dependencyList ${depModLibraryName} )
                set ( newDependencyFound 1)
            endif()      
        endforeach()
    endforeach()

    if ( newDependencyFound )
        waLBerla_resolve_dependencies ( dependencyList ${dependencyList} )
    endif()

    set ( ${outputList} ${dependencyList} PARENT_SCOPE )

endfunction ( waLBerla_resolve_dependencies )

# Testcase:
# waLBerla_register_dependency ( A "B" )
# waLBerla_register_dependency ( B "C" "F")
# waLBerla_register_dependency ( C "D" )
# waLBerla_register_dependency ( F "A" )

# waLBerla_resolve_dependencies ( out "A" ) 
# message ( STATUS "Resolve Dependencies testcase ${out}" ) # should print A;B;C;F;D

#######################################################################################################################





#######################################################################################################################
#
# Links a list of modules to a given target
#
# - Translates module names to library names
# - links transitively all modules that depend on given modules
# 
#######################################################################################################################

function ( target_link_modules target )

    set ( libs  )
    foreach ( module ${ARGN} )
       get_module_library_name ( libraryName ${module} )
       list( APPEND libs ${libraryName} )
    endforeach()
        
    waLBerla_resolve_dependencies ( libs ${libs} )
        
    # The linker needs the modules in the correct order depending on their
    # dependencies. We would have to do a topological sorting here, instead
    # we specify all libs twice -> Could be improved -> faster linking times
    set ( libs ${libs} ${libs} ${libs} )
    
    foreach ( libraryName ${libs} )
        if( TARGET ${libraryName} ) 
     	   get_target_property( target_type ${libraryName} TYPE ) 
     	   if( ${target_type} MATCHES LIBRARY ) 
205
     	      target_link_libraries( ${target} ${WALBERLA_LINK_LIBRARIES_KEYWORD} ${libraryName} )
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
     	   endif( )                    
     	endif( ) 
    endforeach()
        
endfunction ( target_link_modules )
#######################################################################################################################





#######################################################################################################################
# If path contains a CMakeLists.txt, path is returned
# otherwise recursivly the parent directories are checked and returned if they contain a CMakeLists.txt
#######################################################################################################################
    
function ( get_parent_with_cmakelists result dir )
    set ( ${result} NOTFOUND PARENT_SCOPE )
    if ( EXISTS ${dir} )     
        if ( EXISTS "${dir}/CMakeLists.txt" )
            set ( ${result} ${dir} PARENT_SCOPE )
        else()
            get_filename_component( dir ${dir} PATH )
            get_parent_with_cmakelists( res ${dir} )
            set ( ${result} ${res} PARENT_SCOPE )    
        endif()
    endif()
endfunction( get_parent_with_cmakelists)
#######################################################################################################################






#######################################################################################################################
#
# Checks the dependencies of the given files 
# 
# Example: get_dependencies(outList "sourceFile1.cpp" "sourceFile2.cpp" )
#
# First the files are read and all "#include" lines are inspected. Then it is checked
# if the included file is in ${walberla_SOURCE_DIR}/src
# 
# The module-name is defined as the part of the path that come after "src/". 
# If a source file has the following line "#include "core/subModuleName/someFile.cpp"
# a dependency to the modules "core/subModuleName" is detected.
#
# Warning: the detection mechanism is not a full preprocessor, therefore it has problems with:
#     #ifdef SOME_VAR
#     #include "core/subModuleName/someFile.cpp"
#     #endif
# Here there would also be detected a module dependency, even if SOME_VAR is not set.
# 
#######################################################################################################################

function ( get_dependencies dependentModulesOut )
  foreach ( sourceFile ${ARGN} )
  
      if( EXISTS ${sourceFile} ) # guard for generated headers
          
          file ( STRINGS ${sourceFile} includes REGEX "[ ]*#[ ]*include.*" )
          foreach ( includeLine ${includes} )
              # Skip comments
              if ( NOT ${includeLine} MATCHES "[ ]*//.*" )
                  # Extract the filename from the include ( stripping off "" and <> )
                  string( REGEX REPLACE "[ ]*#[ ]*include[ ][<\"]+(.*)[\">]" "\\1" includedFile "${includeLine}" )
                                    
                  # check if included file is from walberla/src directory
                  foreach( modulePath ${WALBERLA_MODULE_DIRS} )
                      if ( EXISTS ${modulePath}/${includedFile}  AND NOT IS_DIRECTORY ${modulePath}/${includedFile} )
                          get_parent_with_cmakelists( parent "${modulePath}/${includedFile}" )
                          file(RELATIVE_PATH dependentModule ${modulePath} ${parent} )
                                                    
                          list( APPEND dependentModules ${dependentModule}  )
                      endif()  
                  endforeach()  
              endif()                        
          endforeach()
      endif()
  endforeach()
  
  if( dependentModules )
     list(REMOVE_DUPLICATES dependentModules )
  endif()
  
  set ( ${dependentModulesOut} ${dependentModules} PARENT_SCOPE )
endfunction()
#######################################################################################################################






#######################################################################################################################
#
# Checks that the module has only dependencies to the specified modules
# 
# Examples:
#    check_dependencies( missingDeps additionalDeps FILES field/Communication.h  EXPECTED_DEPS core field ) 
#
#######################################################################################################################

function ( check_dependencies missingDepsOut additionalDepsOut  )
    set( options )
    set( oneValueArgs )
    set( multiValueArgs FILES EXPECTED_DEPS )
    cmake_parse_arguments( ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )

    get_dependencies( computedDeps ${ARG_FILES} )
    set ( expectedDeps ${ARG_EXPECTED_DEPS} )
        
    # missingDepOut is ( computedDeps - expectedDeps )
    set ( missingDeps ${computedDeps} )
    
    if ( missingDeps )
        foreach ( element ${expectedDeps} )
            list ( REMOVE_ITEM missingDeps ${element} )
        endforeach()
    endif()
    set ( ${missingDepsOut} ${missingDeps} PARENT_SCOPE )
    
    # additionalDepOut is ( expectedDeps - computedDeps)
    set ( additionalDeps ${expectedDeps} )   
    if( expectedDeps )
        foreach ( element ${computedDeps} )
            list ( REMOVE_ITEM additionalDeps ${element} )
        endforeach()
    endif()
    set ( ${additionalDepsOut} ${additionalDeps} PARENT_SCOPE )  
    
    
endfunction ( check_dependencies )
#######################################################################################################################
 
 
 




#######################################################################################################################
# 
# Reports module statistics  ( name, #files, dependencies, ... )
# which are accumulated into a cache variable, and can be later on written out to a file
# using the function waLBerla_write_module_statistics() 
# 
# Example:
#    waLBerla_module_statistics( FILES ${allSourceAndHeaderFilesOfModule}
#                                DEPENDS ${allModulesWhereCurrentModuleDependsOn )
#
#######################################################################################################################

function ( waLBerla_module_statistics  )
    set( options )
    set( oneValueArgs )
    set( multiValueArgs FILES DEPENDS )
    cmake_parse_arguments( ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
    
    get_current_module_name ( moduleName )
    
    
    # Modules that should not appear in the graph can be added to the following list
    set ( excludeModulesList "core" )
    foreach ( element ${excludeModulesList} )
       if ( ARG_DEPENDS )
           list ( REMOVE_ITEM ARG_DEPENDS ${element} )
       endif()
       if ( ${moduleName}  STREQUAL ${element} )
           return()
       endif()
    endforeach()
    
    # Output to statistics file in json format
    #  - short and long module name
    #  - number of files
    #  { "moduleName": "abc", "moduleNameShort: "a", numberOfFiles : 5, dependentModules : [ "module1", "module2" ] }

    list ( LENGTH ARG_FILES nrOfSourceFiles )

    string (REPLACE ";" "\", \"" depModulesJson "${ARG_DEPENDS}" )
    
    list(LENGTH depModulesJson nrDependentModules)
    if ( ${nrDependentModules} GREATER 0 )
        set (depModulesJson "[\"${depModulesJson}\"]" )
    else()
        set (depModulesJson "[]" )
    endif()
    
    set ( statString "{" )
    set ( statString " ${statString}\"moduleName\"      : \"${moduleName}\"    , " )
    set ( statString " ${statString}\"numberOfFiles\"   :   ${nrOfSourceFiles}   , " )
    set ( statString " ${statString}\"dependentModules\":   ${depModulesJson} } \n"   )
    
    set ( WALBERLA_MODULE_DEPENDENCY_FILE ${WALBERLA_MODULE_DEPENDENCY_FILE} "${statString}"
            CACHE INTERNAL "Internal Variable to accumulate module dependency file"  FORCE)
    
endfunction( waLBerla_module_statistics )

#######################################################################################################################



 
#######################################################################################################################
# 
# After having reported all modules using waLBerla_module_statistics() function
# this function writes all the reported information to a single file
#
# This file has json format and can be used to on a webside to display module information
# like dependency graphs, number of files per module, etc. 
#
#######################################################################################################################

function (waLBerla_write_module_statistics file )
    
    file ( WRITE ${file} ${WALBERLA_MODULE_DEPENDENCY_FILE} )
    
    # Clear the cache variable
    set ( WALBERLA_MODULE_DEPENDENCY_FILE  
          CACHE INTERNAL "Internal Variable to accumulate module dependency file"  FORCE)
    
endfunction()

#######################################################################################################################