Update component logic to support required and optional resolving

This allows resolving a dependency tree up to 10 elements deep, but a different solution may be necessary in the future. A better alternative in the future might be to keep a copy of the unresolved entries and then compare every loop, instead of limiting to a fixed number of cycles.

This currently doesn't address cyclic dependencies, since I'm not quite sure how those would work with the current model anyway.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2023-09-30 04:45:20 +02:00 committed by Xaymar
parent 92b93a2479
commit 0e913edccf
1 changed files with 195 additions and 79 deletions

View File

@ -754,6 +754,7 @@ endif()
define_property(TARGET PROPERTY COMPONENT_LABEL)
define_property(TARGET PROPERTY COMPONENT_NAME)
define_property(TARGET PROPERTY COMPONENT_OPTION)
define_property(TARGET PROPERTY COMPONENT_RESOLVER)
define_property(TARGET PROPERTY COMPONENT_DEPENDS)
function(streamfx_add_library TARGET_NAME TARGET_TYPE)
@ -1025,17 +1026,27 @@ function(streamfx_add_library TARGET_NAME TARGET_TYPE)
endif()
endfunction()
function(streamfx_sanitize_name TEXT _OUTPUT_NAME _OUTPUT_TARGET _OUTPUT_OPTION)
string(REGEX REPLACE "^[ \t]+" "" TEXT "${TEXT}")
string(REGEX REPLACE "[ \t]+$" "" TEXT "${TEXT}")
string(REGEX REPLACE "[^a-zA-Z0-9_-]+" "_" TEXT2 "${TEXT}")
string(REGEX REPLACE "_[_]+" "_" TEXT2 "${TEXT2}")
string(TOUPPER "${TEXT2}" TEXT3)
set(${_OUTPUT_NAME} "${TEXT}" PARENT_SCOPE)
set(${_OUTPUT_TARGET} "${TEXT2}" PARENT_SCOPE)
set(${_OUTPUT_OPTION} "${TEXT3}" PARENT_SCOPE)
endfunction()
function(streamfx_add_component COMPONENT_NAME)
# Sanitize the component name by trimming whitespace.
string(REGEX REPLACE "^[ \t]+" "" COMPONENT_NAME "${COMPONENT_NAME}")
string(REGEX REPLACE "[ \t]+$" "" COMPONENT_NAME "${COMPONENT_NAME}")
cmake_parse_arguments(PARSE_ARGV 1 _ARG
""
"RESOLVER"
""
)
# Generate a sanitized version of the component name for use in targets and options.
string(REGEX REPLACE "[^a-zA-Z0-9_-]+" "_" COMPONENT_SANITIZED_NAME "${COMPONENT_NAME}")
string(REGEX REPLACE "_[_]+" "_" COMPONENT_SANITIZED_NAME "${COMPONENT_SANITIZED_NAME}")
# Convert the sanitized version to upper case.
string(TOUPPER "${COMPONENT_SANITIZED_NAME}" COMPONENT_OPTION_NAME)
# Sanitize name for further use.
streamfx_sanitize_name("${COMPONENT_NAME}" COMPONENT_NAME COMPONENT_SANITIZED_NAME COMPONENT_OPTION_NAME)
set(COMPONENT_OPTION "${PREFIX}COMPONENT_${COMPONENT_OPTION_NAME}")
set(COMPONENT_OPTION "${COMPONENT_OPTION}" PARENT_SCOPE)
@ -1059,13 +1070,20 @@ function(streamfx_add_component COMPONENT_NAME)
# Register the component globally.
get_target_property(_DEPENDS StreamFX COMPONENT_DEPENDS)
if(_DEPENDS)
list(APPEND _DEPENDS "${COMPONENT_TARGET}")
list(APPEND _DEPENDS "${COMPONENT_SANITIZED_NAME}")
else()
set(_DEPENDS "${COMPONENT_TARGET}")
set(_DEPENDS "${COMPONENT_SANITIZED_NAME}")
endif()
set_target_properties(StreamFX PROPERTIES
COMPONENT_DEPENDS "${_DEPENDS}"
)
# If there is a resolver function, register it.
if(_ARG_RESOLVER)
set_target_properties(${COMPONENT_TARGET}
COMPONENT_RESOLVER "${_ARG_RESOLVER}"
)
endif()
# Allow disabling this component.
set(${COMPONENT_OPTION} ON CACHE BOOL "Enable the ${COMPONENT_NAME} component?")
@ -1153,26 +1171,76 @@ function(streamfx_add_component COMPONENT_NAME)
)
endfunction()
# Use this to add a dependency on another component,
function(streamfx_add_component_dependency _NAME)
get_target_property(DEPENDS ${COMPONENT_TARGET} COMPONENT_DEPENDS)
list(APPEND DEPENDS "${_NAME}")
set_target_properties(${COMPONENT_TARGET} PROPERTIES COMPONENT_DEPENDS "${DEPENDS}")
function(streamfx_has_component _NAME _OUTPUT)
streamfx_sanitize_name("${_NAME}" _NAME _TARGET _OPTION)
set(${_OUTPUT} OFF PARENT_SCOPE)
if(TARGET "StreamFX_${_TARGET}")
set(${_OUTPUT} ON PARENT_SCOPE)
endif()
endfunction()
function(streamfx_disable_component _NAME _REASON)
function(streamfx_enabled_component _NAME _OUTPUT)
streamfx_sanitize_name("${_NAME}" _NAME _TARGET _OPTION)
if(NOT TARGET "StreamFX_${_TARGET}")
message(FATAL_ERROR "Unknown component '${_NAME}'.")
endif()
get_target_property(_OPTION "StreamFX_${_TARGET}" COMPONENT_OPTION)
set(${_OUTPUT} OFF PARENT_SCOPE)
if(${_OPTION} AND NOT (${_OPTION}_DISABLED))
set(${_OUTPUT} ON PARENT_SCOPE)
endif()
endfunction()
# Use this to add a dependency on another component.
function(streamfx_add_component_dependency _NAME)
cmake_parse_arguments(PARSE_ARGV 1 _ARG
"OPTIONAL"
""
""
)
streamfx_sanitize_name("${_NAME}" _NAME _TARGET _OPTION)
set(DEPENDENCY "${_TARGET}")
if(_ARG_OPTIONAL)
list(APPEND DEPENDENCY "OPTIONAL")
endif()
string(REPLACE ";" "\\;" DEPENDENCY "${DEPENDENCY}")
get_target_property(DEPENDS "${COMPONENT_TARGET}" COMPONENT_DEPENDS)
if(DEPENDS)
list(APPEND DEPENDS "${DEPENDENCY}")
else()
set(DEPENDS "${DEPENDENCY}")
endif()
set_target_properties("${COMPONENT_TARGET}" PROPERTIES COMPONENT_DEPENDS "${DEPENDS}")
endfunction()
# Use this to disable a component via script.
function(streamfx_disable_component _NAME)
cmake_parse_arguments(PARSE_ARGV 1 _ARG
""
"REASON"
""
)
streamfx_sanitize_name("${_NAME}" _NAME _TARGET _OPTION)
set(_TARGET "StreamFX_${_TARGET}")
# If the component doesn't exist, skip it.
if(NOT TARGET ${_NAME})
message(WARNING "Not disabling invalid component '${COMPONENT}'.")
if(NOT TARGET ${_TARGET})
message(WARNING "Not disabling invalid component '${_NAME}'.")
return()
endif()
get_target_property(_NAME ${COMPONENT} COMPONENT_LABEL)
get_target_property(_OPTION ${COMPONENT} COMPONENT_OPTION)
get_target_property(_LABEL ${_TARGET} COMPONENT_LABEL)
get_target_property(_OPTION ${_TARGET} COMPONENT_OPTION)
CacheSet(${_OPTION}_DISABLED ON)
if(_REASON)
message(STATUS "[${_NAME}] Disabled due to: ${_REASON}")
if(_ARG_REASON)
message(STATUS "[${_LABEL}] Disabled due to: ${_ARG_REASON}")
endif()
endfunction()
@ -1363,73 +1431,121 @@ foreach(COMPONENT ${COMPONENTS})
endif()
endforeach()
get_target_property(_DEPENDS StreamFX COMPONENT_DEPENDS)
get_target_property(_UNRESOLVED StreamFX COMPONENT_DEPENDS)
set(_RESOLVED "")
set(_DISABLED "")
#- Resolving
if(_DEPENDS)
foreach(COMPONENT ${_DEPENDS})
# If the component doesn't exist, skip it.
if(NOT TARGET ${COMPONENT})
message(WARNING "Encountered invalid component '${COMPONENT}' in list of all components.")
continue()
endif()
#- Cleanup
list(REMOVE_DUPLICATES _UNRESOLVED)
foreach(_ENTITY ${_UNRESOLVED})
# Remove any invalid entries.
if(NOT TARGET "StreamFX_${_ENTITY}")
message(WARNING "Encountered invalid component '${_ENTITY}', removing...")
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
continue()
endif()
endforeach()
get_target_property(_NAME ${COMPONENT} COMPONENT_LABEL)
#- Resolve Dependencies in a loop
set(_UNRESOLVED_LOOP 0)
while(_UNRESOLVED)
MATH(EXPR _UNRESOLVED_LOOP "${_UNRESOLVED_LOOP}+1")
if(_UNRESOLVED_LOOP GREATER_EQUAL 10)
message(FATAL_ERROR "Infinite loop while resolving components: ${_UNRESOLVED}")
endif()
# If the component is disabled, skip it.
get_target_property(_OPTION ${COMPONENT} COMPONENT_OPTION)
if(NOT ${_OPTION})
continue()
elseif(${_OPTION}_DISABLED)
continue()
endif()
# Attempt to resolve while there are still unresolved entries.
foreach(_ENTITY ${_UNRESOLVED})
set(RENTITY "StreamFX_${_ENTITY}")
# Test if all dependencies are valid.
set(_HASDEPENDENCY ON)
get_target_property(_CDEPENDS ${COMPONENT} COMPONENT_DEPENDS)
foreach(_DEPEND ${_CDEPENDS})
get_target_property(_DNAME ${COMPONENT} COMPONENT_LABEL)
get_target_property(_DOPTION ${COMPONENT} COMPONENT_OPTION)
if((NOT ${_DOPTION}) OR (${_DOPTION}_DISABLED))
message(STATUS "[${_NAME}] Missing or disabled dependency on '${_DNAME}'.")
set(_HASDEPENDENCY OFF)
continue()
endif()
endforeach()
if(NOT _HASDEPENDENCY)
message(STATUS "[${_NAME}] Missing dependencies, disabling...")
set(${_OPTION}_DISABLED TRUE)
get_target_property(_LABEL "${RENTITY}" COMPONENT_LABEL)
get_target_property(_OPTION "${RENTITY}" COMPONENT_OPTION)
get_target_property(_DEPENDS "${RENTITY}" COMPONENT_DEPENDS)
get_target_property(_RESOLVER "${RENTITY}" COMPONENT_RESOLVER)
# Remove any that have been disabled.
if(NOT ${_OPTION})
message(STATUS "[${_LABEL}] Disabled by user.")
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
list(APPEND _DISABLED "${_ENTITY}")
continue()
elseif(${_OPTION}_DISABLED) # Test for pre-resolve disabling.
message(STATUS "[${_LABEL}] Disabled by build script.")
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
list(APPEND _DISABLED "${_ENTITY}")
continue()
endif()
# Check if all dependencies are resolved.
set(_HAS_UNRESOLVED_DEPENDS OFF)
set(_HAS_DISABLED_DEPENDS OFF)
set(_HAS_UNRESOLVED_OPTIONAL_DEPENDS OFF)
if(_DEPENDS)
foreach(_DEPEND ${_DEPENDS})
list(GET _DEPEND 0 _ENTITY2)
set(RENTITY2 "StreamFX_${_ENTITY2}")
get_target_property(_ENTITY2_LABEL "${RENTITY2}" COMPONENT_LABEL)
if(NOT ("OPTIONAL" IN_LIST _DEPEND))
if("${_ENTITY2}" IN_LIST _DISABLED)
message("[${_LABEL}] Required dependency '${_ENTITY2_LABEL}' is disabled.")
set(_HAS_DISABLED_DEPENDS ON)
endif()
if("${_ENTITY2}" IN_LIST _UNRESOLVED)
set(_HAS_UNRESOLVED_DEPENDS ON)
endif()
else()
if("${_ENTITY2}" IN_LIST _UNRESOLVED)
set(_HAS_UNRESOLVED_OPTIONAL_DEPENDS ON)
endif()
endif()
endforeach()
list(JOIN _DEPENDS ", " _DEPENDS)
else()
set(_DEPENDS "")
endif()
if(_HAS_DISABLED_DEPENDS) # A required dependency is disabled, so disable this entry.
message(STATUS "[${_LABEL}] Disabled by dependency.")
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
list(APPEND _DISABLED "${_ENTITY}")
streamfx_disable_component(${_ENTITY})
continue()
elseif(_HAS_UNRESOLVED_DEPENDS)
continue()
elseif(_HAS_UNRESOLVED_OPTIONAL_DEPENDS AND (_UNRESOLVED_LOOP LESS 8))
# Temporarily skip this element while there are still remaining loops.
continue()
endif()
# Call Resolver function
if(_RESOLVER)
cmake_language(CALL ${_RESOLVER})
endif()
if(${_OPTION}_DISABLED) # Test for resolve disabling.
message(STATUS "[${_LABEL}] Disabled by resolver.")
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
list(APPEND _DISABLED "${_ENTITY}")
continue()
endif()
# Finally, if everything went well, we now have a resolved entity.
list(REMOVE_ITEM _UNRESOLVED "${_ENTITY}")
list(APPEND _RESOLVED "${_ENTITY}")
message(STATUS "[${_LABEL}] Enabled. Depends: ${_DEPENDS}")
endforeach()
endif()
endwhile()
#- Linking
target_link_libraries(StreamFX PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,StreamFX_Core>)
if(_DEPENDS)
foreach(COMPONENT ${_DEPENDS})
if(NOT TARGET ${COMPONENT})
continue()
endif()
get_target_property(_NAME ${COMPONENT} COMPONENT_LABEL)
# If the component is disabled, skip it.
get_target_property(_OPTION ${COMPONENT} COMPONENT_OPTION)
if(NOT ${_OPTION})
message(STATUS "[${_NAME}] Disabled by developer.")
continue()
elseif(${_OPTION}_DISABLED)
message(STATUS "[${_NAME}] Disabled by build script.")
continue()
endif()
# Finally if everything is correct, do things.
message(STATUS "[${_NAME}] Enabled.")
target_link_libraries(StreamFX PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,${COMPONENT}>)
endforeach()
endif()
foreach(_ENTITY ${_RESOLVED})
if(NOT TARGET StreamFX_${_ENTITY})
continue()
endif()
# Finally if everything is correct, do things.
target_link_libraries(StreamFX PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,StreamFX_${_ENTITY}>)
endforeach()
################################################################################
# Installation