waLBerlaModuleDependencySystem.cmake 17.6 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
34
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
205
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
#    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} )
#  target_link_libraries ( myApp  walberlaModule1 core-field  lbm-boundary ) 
# 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 ) 
     	      target_link_libraries( ${target} ${libraryName} )
     	   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()

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