########################################################################################## # # # OpenSpace # # # # Copyright (c) 2014-2025 # # # # Permission is hereby granted, free of charge, to any person obtaining a copy of this # # software and associated documentation files (the "Software"), to deal in the Software # # without restriction, including without limitation the rights to use, copy, modify, # # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # # permit persons to whom the Software is furnished to do so, subject to the following # # conditions: # # # # The above copyright notice and this permission notice shall be included in all copies # # or substantial portions of the Software. # # # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF # # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE # # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ########################################################################################## include(${PROJECT_SOURCE_DIR}/support/cmake/global_variables.cmake) include(${PROJECT_SOURCE_DIR}/ext/ghoul/support/cmake/message_macros.cmake) # This function takes a list of module paths and returns the list of include paths that # need to be added to the moduleregistration file. Each module files parent directory is # considered, but with the internal module path ignored function (get_unique_include_paths module_paths internal_module_path result) set(res "") foreach (p ${all_module_paths}) get_filename_component(base_dir_parent "${p}/.." ABSOLUTE) get_filename_component(base_dir_grandparent "${p}/../.." ABSOLUTE) get_filename_component(int_dir "${internal_module_path}/.." ABSOLUTE) if (NOT ${base_dir_grandparent} STREQUAL ${int_dir}) # We need to add both parent and grantparent directory: # The grandparent has to be added if we have an external module in the path # x/modules/name and we need to add 'x' to the module search path such that # people can write #include # # The parent needs to be included for modules in subdirectories of the form # openspace/modules/dir/name such that 'dir' is included in the search path list(APPEND res ${base_dir_parent}) list(APPEND res ${base_dir_grandparent}) endif () endforeach () list(REMOVE_DUPLICATES res) set(${result} ${res} PARENT_SCOPE) endfunction () # Returns whether the module located at 'path' is a default module or not function (get_module_attribute_default path result) set(${result} OFF PARENT_SCOPE) if (EXISTS "${path}/include.cmake") unset(DEFAULT_MODULE) include(${path}/include.cmake) if (DEFINED DEFAULT_MODULE) set(${result} ${DEFAULT_MODULE} PARENT_SCOPE) endif () endif () endfunction() # Returns the list of dependencies of the module located at 'path' function (get_module_attribute_dependencies path result) set(${result} "" PARENT_SCOPE) if (EXISTS "${path}/include.cmake") unset(OPENSPACE_DEPENDENCIES) include(${path}/${dir}/include.cmake) if (DEFINED OPENSPACE_DEPENDENCIES) set(${result} ${OPENSPACE_DEPENDENCIES} PARENT_SCOPE) endif () endif () endfunction() # Returns the state whether the module located at 'path' is supported on this machine function (get_module_attribute_supported path result) set(${result} ON PARENT_SCOPE) if (EXISTS "${path}/include.cmake") unset(SUPPORTED) include(${path}/${dir}/include.cmake) if (DEFINED SUPPORTED) set(${result} ${SUPPORTED} PARENT_SCOPE) endif () endif () endfunction() # Returns the path for the 'module_name'. If the module has not been seen before by # get_individual_modules, an empty string is returned function (find_path_for_module module_name module_names module_paths result) list(FIND module_names ${module_name} i) if (i EQUAL -1) # Did not find the name in the list set(${result} "" PARENT_SCOPE) else () # We found the name in the list, so the path is at the same location list(GET module_paths ${i} path) set(${result} ${path} PARENT_SCOPE) endif () endfunction () # Gets the names of the dependencies of 'module_name' recursively and returns them in # 'result'. For a dependency chain of a -> b -> c get_recursive_depdencies(a) will # return "b,c" function (get_recursive_dependencies module_name module_path module_names module_paths result) set(result_aggregate "") get_module_attribute_dependencies(${module_path} deps) if (deps) foreach (name ${deps}) find_path_for_module(${name} "${module_names}" "${module_paths}" path) if (path) get_recursive_dependencies( ${name} ${path} "${module_names}" "${module_paths}" res ) # 1. We add "base" to the list of dependencies as we always want it # 2. We add dependencies in this order such that when we later traverse # this list, we automatically get them in the correct order (meaning # that we include a dependency first) set(result_aggregate ${result_aggregate} "base" ${res} ${name}) else () message(FATAL_ERROR "Could not find dependency ${name} for module ${module_name}") endif () endforeach () endif () set(${result} ${result_aggregate} PARENT_SCOPE) endfunction() # Returns a list of all modules contained in the folder 'path' function (get_individual_modules path module_names module_paths) file(GLOB moduleDirs RELATIVE ${path} ${path}/*) set(names "") set(paths "") foreach (dir ${moduleDirs}) if (EXISTS "${path}/${dir}/CMakeLists.txt") list(APPEND names ${dir}) list(APPEND paths ${path}/${dir}) else () # The CMakeLists.txt does not exist, so it might be a group of subdirectories get_individual_modules(${path}/${dir} _mod_names _mod_paths) list(APPEND names ${_mod_names}) list(APPEND paths ${_mod_paths}) # message(STATUS "Skipping ${dir} as ${dir}/CMakeLists.txt does not exist") endif () endforeach () set(${module_names} ${names} PARENT_SCOPE) set(${module_paths} ${paths} PARENT_SCOPE) endfunction () option(OPENSPACE_ENABLE_ALL_MODULES "If this is ON, all modules will be enabled by default") set(internal_module_path "${PROJECT_SOURCE_DIR}/modules") set(all_enabled_modules "") # # Step 1: Get a list of all modules # # First get all the internal module get_individual_modules(${internal_module_path} all_module_names all_module_paths) list(LENGTH all_module_names all_module_names_count) math(EXPR all_module_names_count "${all_module_names_count} - 1") # # Step 2: Create options for all modules with correct default values # set(enabled_module_names "") set(enabled_module_paths "") foreach(val RANGE ${all_module_names_count}) list(GET all_module_names ${val} name) list(GET all_module_paths ${val} path) get_module_attribute_supported(${path} is_module_supported) if (NOT ${is_module_supported}) message(STATUS "Skipping module ${name} (${path}) as it is not supported on this machine") continue() endif () get_module_attribute_default(${path} is_default_module) create_option_name(${name} optionName) if (${OPENSPACE_ENABLE_ALL_MODULES}) option(${optionName} "Build ${path} Module" ON) else () option(${optionName} "Build ${path} Module" ${is_default_module}) endif () if (${optionName}) list(APPEND enabled_module_names ${name}) list(APPEND enabled_module_paths ${path}) endif () endforeach () # # Step 3: For each module that is default or enabled by the user, get the dependencies # set(dependencies "") list(LENGTH enabled_module_names enabled_module_count) if (${enabled_module_count} EQUAL 0) message(STATUS "No modules selected") return() endif () math(EXPR enabled_module_count "${enabled_module_count} - 1") foreach (val RANGE ${enabled_module_count}) list(GET enabled_module_names ${val} name) list(GET enabled_module_paths ${val} path) get_recursive_dependencies( ${name} ${path} "${all_module_names}" "${all_module_paths}" deps ) set(dependencies ${dependencies} ${deps}) endforeach() # We can remove the duplicates here. We constructed the list such that nested # dependencies are order left to right. REMOVE_DUPLICATES will keep the left most # value in the case of duplicates, so that will still work list(REMOVE_DUPLICATES dependencies) # # Step 4: Check each dependency and set it, if necessary # foreach (dep ${dependencies}) create_option_name(${dep} optionName) if (NOT ${optionName}) set(${optionName} ON CACHE BOOL "Build ${dep} Module" FORCE) message(STATUS "Due to dependencies, the '${dep}' was enabled") find_path_for_module(${dep} "${all_module_names}" "${all_module_paths}" path) list(APPEND enabled_module_names ${dep}) list(APPEND enabled_module_paths ${path}) endif () endforeach () # Print all the enabled modules message(STATUS "Enabled modules:") list(LENGTH enabled_module_names enabled_module_count) math(EXPR enabled_module_count "${enabled_module_count} - 1") foreach (val RANGE ${enabled_module_count}) list(GET enabled_module_names ${val} name) list(GET enabled_module_paths ${val} path) message(STATUS "\t${name} (${path})") endforeach () # # Step 5: Add the subdirectories of all enabled modules # add_library(openspace-module-collection INTERFACE) set(module_class_names "") set(module_external_libraries "") foreach (val RANGE ${enabled_module_count}) list(GET enabled_module_names ${val} name) list(GET enabled_module_paths ${val} path) create_library_name(${name} library_name) list(APPEND all_enabled_modules "${library_name}") begin_header("Module ${name} (${library_name})") add_subdirectory(${path}) # Make sure that the code generator always runs before a module add_dependencies(${library_name} run_codegen) end_header("End: Module ${name}") message(STATUS "") target_link_libraries(openspace-module-collection INTERFACE ${library_name}) # This is just a hack as a dependency from kameleon->core has been introduced that # should be removed if (${library_name} STREQUAL "openspace-module-kameleon") cmake_policy(SET CMP0079 NEW) target_link_libraries(openspace-core PRIVATE openspace-module-kameleon) endif () create_define_name(${name} define_name) target_compile_definitions(openspace-module-collection INTERFACE "${define_name}") get_property(class_name GLOBAL PROPERTY CurrentModuleClassName) list(APPEND module_class_names ${class_name}) get_property(ext_lib GLOBAL PROPERTY CurrentModuleExternalLibraries) list(APPEND module_external_libraries ${ext_lib}) endforeach () list(REMOVE_DUPLICATES module_external_libraries) add_external_library_dependencies("${module_external_libraries}") # # Step 6: Create the moduleregistration.h file with the header file paths, class # names and the external module paths. # The class list needs to be topologically sorted, based on # the dependency graph, in order to guarantee that modules are # initialized after their dependencies. # # We generate a list of the names of the enabled modules taking # the topologically sorted dependencies and adding the rest of the modules. # REMOVE_DUPLICATES will keep the left most value in the case of duplicates set(topologically_sorted ${dependencies} ${enabled_module_names}) list(REMOVE_DUPLICATES topologically_sorted) set(MODULE_HEADERS "") set(MODULE_CLASSES "") set(MODULE_DOCUMENTATION "") foreach (key RANGE ${enabled_module_count}) list(GET topologically_sorted ${key} name) list(FIND enabled_module_names ${name} val) list(GET enabled_module_paths ${val} path) list(GET module_class_names ${val} class_name) string(TOUPPER ${name} module_upper) string(TOLOWER ${name} module_lower) create_module_header_filepath(${name} ${path} header_filepath) list(APPEND MODULE_HEADERS "#include <${header_filepath}>\n") list(APPEND MODULE_CLASSES " new ${class_name},\n") list(APPEND MODULE_DOCUMENTATION " ${class_name}::Documentation(),\n") endforeach () get_unique_include_paths( "${enabled_module_paths}" "${internal_module_path}" module_paths ) foreach (path ${module_paths}) list(APPEND MODULE_PATHS " \"${path}\",\n") # The module path should include the 'modules' directory, which is removed for the # include path to make all of the includes look the same list(APPEND MODULE_PATHS " \"${path}/modules\",\n") target_include_directories(openspace-module-collection INTERFACE ${path}) endforeach () if (NOT "${MODULE_HEADERS}" STREQUAL "") string(REPLACE ";" "" MODULE_HEADERS ${MODULE_HEADERS}) endif () if (NOT "${MODULE_CLASSES}" STREQUAL "") string(REPLACE ";" "" MODULE_CLASSES ${MODULE_CLASSES}) endif () if (NOT "${MODULE_DOCUMENTATION}" STREQUAL "") string(REPLACE ";" "" MODULE_DOCUMENTATION ${MODULE_DOCUMENTATION}) endif () configure_file( ${PROJECT_SOURCE_DIR}/support/cmake/module_registration.template ${CMAKE_BINARY_DIR}/_generated/include/openspace/moduleregistration.h ) # Return the list of enabled modules to the caller set(all_enabled_modules ${all_enabled_modules} PARENT_SCOPE)