diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index 704040514e..44c9808ff0 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -50,9 +50,35 @@ jobs: Set-MpPreference -DisableArchiveScanning $true Set-MpPreference -DisableScanningMappedNetworkDrivesForFullScan $true - if (-not (docker network ls --format '{{.Name}}' | Where-Object { $_ -eq 'docker_default' })) { - docker network create --driver nat docker_default - if ($LASTEXITCODE -ne 0) { exit 1 } + $maxAttempts = 12 + $delaySeconds = 5 + $dockerReady = $false + + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + $networkNames = docker network ls --format '{{.Name}}' + if ($LASTEXITCODE -eq 0) { + if (-not ($networkNames | Where-Object { $_ -eq 'docker_default' })) { + docker network create --driver nat docker_default + if ($LASTEXITCODE -eq 0) { + $dockerReady = $true + break + } + } + else { + $dockerReady = $true + break + } + } + + if ($attempt -lt $maxAttempts) { + Write-Host "Docker not ready yet (attempt $attempt/$maxAttempts), retry in ${delaySeconds}s..." + Start-Sleep -Seconds $delaySeconds + } + } + + if (-not $dockerReady) { + Write-Error "Docker was not ready after $($maxAttempts*$delaySeconds)s total wait" + exit 1 } - name: Set prefix @@ -385,11 +411,11 @@ jobs: if (-not (Test-Path "smoke/build-ct/install")) { throw "smoke/build-ct/install not found" } tree.com smoke /F - - name: Configure Smoke - run: cmake -S smoke -B smoke/out + - name: Smoke Flow MINIMALISTIC + run: cmake -D FLOW=MINIMALISTIC -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake - - name: Build Smoke - run: cmake --build smoke/out --config ${{ matrix.config }} + - name: Smoke Flow CONFIGURE_ONLY + run: cmake -D FLOW=CONFIGURE_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake - - name: CTest Smoke - run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} + - name: Smoke Flow BUILD_ONLY + run: cmake -D FLOW=BUILD_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake diff --git a/.github/workflows/run-nsc.yml b/.github/workflows/run-nsc.yml index d5f9f74c2b..ce050581dc 100644 --- a/.github/workflows/run-nsc.yml +++ b/.github/workflows/run-nsc.yml @@ -51,9 +51,35 @@ jobs: Set-MpPreference -DisableArchiveScanning $true Set-MpPreference -DisableScanningMappedNetworkDrivesForFullScan $true - if (-not (docker network ls --format '{{.Name}}' | Where-Object { $_ -eq 'docker_default' })) { - docker network create --driver nat docker_default - if ($LASTEXITCODE -ne 0) { exit 1 } + $maxAttempts = 12 + $delaySeconds = 5 + $dockerReady = $false + + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + $networkNames = docker network ls --format '{{.Name}}' + if ($LASTEXITCODE -eq 0) { + if (-not ($networkNames | Where-Object { $_ -eq 'docker_default' })) { + docker network create --driver nat docker_default + if ($LASTEXITCODE -eq 0) { + $dockerReady = $true + break + } + } + else { + $dockerReady = $true + break + } + } + + if ($attempt -lt $maxAttempts) { + Write-Host "Docker not ready yet (attempt $attempt/$maxAttempts), retry in ${delaySeconds}s..." + Start-Sleep -Seconds $delaySeconds + } + } + + if (-not $dockerReady) { + Write-Error "Docker was not ready after $($maxAttempts*$delaySeconds)s total wait" + exit 1 } $sendDiscord = "${{ inputs.withDiscordMSG }}" -eq "true" diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index 44b2a1abcb..c91f6dfaec 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -15,6 +15,700 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/NablaExportTargets.cmake") check_required_components(Nabla) +# +# nabla_sync_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] +# [MODE ] +# [RUNTIME_MODULES_SUBDIR ] +# [BUILD_TRIGGER_TARGETS ] +# ) +# +# nabla_apply_runtime_lookup( +# TARGETS +# [RUNTIME_MODULES_SUBDIR ] +# ) +# +# nabla_setup_runtime_install_modules( +# [RUNTIME_MODULES_SUBDIR ] +# ) +# +# nabla_setup_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] +# [APPLY_LOOKUP_TO_TARGETS ] +# [RUNTIME_MODULES_SUBDIR ] +# [MODE ] +# [INSTALL_RULES ] +# [BUILD_TRIGGER_TARGETS ] +# ) +# +# Wrapper around sync + lookup + install helpers. +# +# Config mapping: +# - Runtime source path is resolved from mapped imported config of Nabla::Nabla. +# - MAP_IMPORTED_CONFIG_* and CMAKE_MAP_IMPORTED_CONFIG_* are applied automatically. +# - MODE=CONFIGURE_TIME and MODE=BOTH resolve mapped imported config during configure/generate. +# - For MODE=CONFIGURE_TIME and MODE=BOTH, finalize mapping before calling helpers. +# - If using CMAKE_MAP_IMPORTED_CONFIG_, set it before find_package(Nabla). +# + +function(_nbl_runtime_modules_apply_lookup_definitions _TARGET _RUNTIME_MODULES_SUBDIR) + target_compile_definitions("${_TARGET}" PRIVATE + NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./${_RUNTIME_MODULES_SUBDIR}" + NBL_CPACK_PACKAGE_DXC_DLL_DIR="./${_RUNTIME_MODULES_SUBDIR}" + ) +endfunction() + +function(_nbl_runtime_modules_apply_lookup_definitions_to_targets _TARGETS _RUNTIME_MODULES_SUBDIR) + set(_targets ${_TARGETS}) + list(REMOVE_DUPLICATES _targets) + + foreach(_target IN LISTS _targets) + if(NOT TARGET "${_target}") + message(FATAL_ERROR "Nabla: target \"${_target}\" does not exist") + endif() + _nbl_runtime_modules_apply_lookup_definitions("${_target}" "${_RUNTIME_MODULES_SUBDIR}") + endforeach() +endfunction() + +function(_nbl_runtime_modules_add_install_rules _RUNTIME_MODULES_SUBDIR) + if(NOT DEFINED CMAKE_INSTALL_BINDIR) + include(GNUInstallDirs) + endif() + + set(_nbl_install_modules_dest "${CMAKE_INSTALL_BINDIR}/${_RUNTIME_MODULES_SUBDIR}") + string(MD5 _nbl_install_modules_dest_key "${_nbl_install_modules_dest}") + get_property(_nbl_install_rules_added GLOBAL PROPERTY "NBL_RUNTIME_MODULES_INSTALL_RULES_${_nbl_install_modules_dest_key}") + if(NOT _nbl_install_rules_added) + install(FILES "$" + DESTINATION "${_nbl_install_modules_dest}" + ) + install(DIRECTORY "$,3rdparty,dxc>/" + DESTINATION "${_nbl_install_modules_dest}" + ) + set_property(GLOBAL PROPERTY "NBL_RUNTIME_MODULES_INSTALL_RULES_${_nbl_install_modules_dest_key}" TRUE) + endif() +endfunction() + +function(_nbl_runtime_modules_collect_consumer_configs _OUT_CONFIGS) + if(CMAKE_CONFIGURATION_TYPES) + set(_consumer_configs ${CMAKE_CONFIGURATION_TYPES}) + elseif(CMAKE_BUILD_TYPE) + set(_consumer_configs "${CMAKE_BUILD_TYPE}") + else() + set(_consumer_configs Debug) + endif() + + list(REMOVE_DUPLICATES _consumer_configs) + set(${_OUT_CONFIGS} ${_consumer_configs} PARENT_SCOPE) +endfunction() + +function(_nbl_runtime_modules_expand_destination_pairs _DESTINATION_DEFAULT _DESTINATION_DEBUG _DESTINATION_RELEASE _DESTINATION_RELWITHDEBINFO _OUT_CFG_DST_PAIRS) + _nbl_runtime_modules_collect_consumer_configs(_consumer_configs) + set(_cfg_dst_pairs "") + + foreach(_consumer_config IN LISTS _consumer_configs) + string(TOUPPER "${_consumer_config}" _cfg_upper) + if(_cfg_upper STREQUAL "DEBUG" AND NOT _DESTINATION_DEBUG STREQUAL "") + set(_resolved_destination "${_DESTINATION_DEBUG}") + elseif(_cfg_upper STREQUAL "RELEASE" AND NOT _DESTINATION_RELEASE STREQUAL "") + set(_resolved_destination "${_DESTINATION_RELEASE}") + elseif(_cfg_upper STREQUAL "RELWITHDEBINFO" AND NOT _DESTINATION_RELWITHDEBINFO STREQUAL "") + set(_resolved_destination "${_DESTINATION_RELWITHDEBINFO}") + else() + set(_resolved_destination "${_DESTINATION_DEFAULT}") + endif() + + if(_resolved_destination STREQUAL "") + message(FATAL_ERROR "Nabla: missing destination for consumer config \"${_consumer_config}\". Provide DESTINATION or one of DESTINATION_DEBUG/DESTINATION_RELEASE/DESTINATION_RELWITHDEBINFO.") + endif() + + if(_resolved_destination MATCHES "\\$<") + message(FATAL_ERROR "Nabla: DESTINATION for MODE CONFIGURE_TIME must be a plain path without generator expressions.") + endif() + + cmake_path(IS_ABSOLUTE _resolved_destination _is_abs) + if(NOT _is_abs) + cmake_path(ABSOLUTE_PATH _resolved_destination BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" OUTPUT_VARIABLE _resolved_destination) + endif() + + list(APPEND _cfg_dst_pairs "${_consumer_config}::${_resolved_destination}") + endforeach() + + set(${_OUT_CFG_DST_PAIRS} ${_cfg_dst_pairs} PARENT_SCOPE) +endfunction() + +function(_nbl_runtime_modules_resolve_imported_nabla_file _CONSUMER_CONFIG _OUT_IMPORTED_FILE) + string(TOUPPER "${_CONSUMER_CONFIG}" _cfg_upper) + + # Resolve runtime source from mapped imported config for given consumer config. + set(_mapped_candidates "") + get_target_property(_target_map "Nabla::Nabla" "MAP_IMPORTED_CONFIG_${_cfg_upper}") + if(_target_map AND NOT _target_map STREQUAL "NOTFOUND") + list(APPEND _mapped_candidates ${_target_map}) + endif() + + set(_global_map_var "CMAKE_MAP_IMPORTED_CONFIG_${_cfg_upper}") + if(DEFINED ${_global_map_var} AND NOT "${${_global_map_var}}" STREQUAL "") + list(APPEND _mapped_candidates ${${_global_map_var}}) + endif() + + list(APPEND _mapped_candidates "${_cfg_upper}") + + foreach(_mapped_config IN LISTS _mapped_candidates) + if(_mapped_config STREQUAL "") + get_target_property(_candidate "Nabla::Nabla" IMPORTED_LOCATION) + else() + string(TOUPPER "${_mapped_config}" _mapped_upper) + get_target_property(_candidate "Nabla::Nabla" "IMPORTED_LOCATION_${_mapped_upper}") + endif() + + if(_candidate AND NOT _candidate STREQUAL "NOTFOUND" AND EXISTS "${_candidate}") + set(${_OUT_IMPORTED_FILE} "${_candidate}" PARENT_SCOPE) + return() + endif() + endforeach() + + get_target_property(_imported_configs "Nabla::Nabla" IMPORTED_CONFIGURATIONS) + foreach(_imported_config IN LISTS _imported_configs) + get_target_property(_candidate "Nabla::Nabla" "IMPORTED_LOCATION_${_imported_config}") + if(_candidate AND NOT _candidate STREQUAL "NOTFOUND" AND EXISTS "${_candidate}") + set(${_OUT_IMPORTED_FILE} "${_candidate}" PARENT_SCOPE) + return() + endif() + endforeach() + + get_target_property(_candidate "Nabla::Nabla" IMPORTED_LOCATION) + if(_candidate AND NOT _candidate STREQUAL "NOTFOUND" AND EXISTS "${_candidate}") + set(${_OUT_IMPORTED_FILE} "${_candidate}" PARENT_SCOPE) + return() + endif() + + message(FATAL_ERROR "Nabla: cannot resolve imported runtime location for consumer config \"${_CONSUMER_CONFIG}\"") +endfunction() + +function(_nbl_runtime_modules_resolve_dxc_runtime_file _NABLA_IMPORTED_FILE _OUT_DXC_IMPORTED_FILE) + cmake_path(GET _NABLA_IMPORTED_FILE PARENT_PATH _nabla_runtime_dir) + set(_dxc_runtime_file "${_nabla_runtime_dir}/3rdparty/dxc/${CMAKE_SHARED_LIBRARY_PREFIX}dxcompiler${CMAKE_SHARED_LIBRARY_SUFFIX}") + + if(NOT EXISTS "${_dxc_runtime_file}") + message(FATAL_ERROR "Nabla: DXC runtime module not found at \"${_dxc_runtime_file}\"") + endif() + + set(${_OUT_DXC_IMPORTED_FILE} "${_dxc_runtime_file}" PARENT_SCOPE) +endfunction() + +function(_nbl_runtime_modules_expand_target_configure_sync_pairs _TARGET _RUNTIME_MODULES_SUBDIR _OUT_CFG_DST_PAIRS) + if(NOT TARGET "${_TARGET}") + message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") + endif() + + get_target_property(_runtime_output_dir "${_TARGET}" RUNTIME_OUTPUT_DIRECTORY) + if(_runtime_output_dir) + set(_runtime_output_base "${_runtime_output_dir}") + elseif(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY STREQUAL "") + set(_runtime_output_base "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + else() + if(CMAKE_CONFIGURATION_TYPES) + set(_runtime_output_base "${CMAKE_CURRENT_BINARY_DIR}/$") + else() + set(_runtime_output_base "${CMAKE_CURRENT_BINARY_DIR}") + endif() + endif() + + _nbl_runtime_modules_collect_consumer_configs(_consumer_configs) + set(_cfg_dst_pairs "") + + if(_runtime_output_base MATCHES "\\$") + set(_runtime_output_without_config "${_runtime_output_base}") + string(REPLACE "$" "" _runtime_output_without_config "${_runtime_output_without_config}") + if(_runtime_output_without_config MATCHES "\\$<") + message(FATAL_ERROR "Nabla: MODE CONFIGURE_TIME supports only $ generator expression in runtime output directory") + endif() + + foreach(_consumer_config IN LISTS _consumer_configs) + set(_runtime_output_resolved "${_runtime_output_base}") + string(REPLACE "$" "${_consumer_config}" _runtime_output_resolved "${_runtime_output_resolved}") + cmake_path(IS_ABSOLUTE _runtime_output_resolved _is_abs) + if(NOT _is_abs) + cmake_path(ABSOLUTE_PATH _runtime_output_resolved BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" OUTPUT_VARIABLE _runtime_output_resolved) + endif() + set(_runtime_modules_dst "${_runtime_output_resolved}/${_RUNTIME_MODULES_SUBDIR}") + list(APPEND _cfg_dst_pairs "${_consumer_config}::${_runtime_modules_dst}") + endforeach() + else() + if(_runtime_output_base MATCHES "\\$<") + message(FATAL_ERROR "Nabla: MODE CONFIGURE_TIME supports only plain paths or paths with $ in runtime output directory") + endif() + + list(LENGTH _consumer_configs _consumer_configs_count) + if(_consumer_configs_count GREATER 1) + message(FATAL_ERROR "Nabla: MODE CONFIGURE_TIME with multi-config generators requires $ in runtime output directory") + endif() + + list(GET _consumer_configs 0 _consumer_config) + cmake_path(IS_ABSOLUTE _runtime_output_base _is_abs) + if(NOT _is_abs) + cmake_path(ABSOLUTE_PATH _runtime_output_base BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" OUTPUT_VARIABLE _runtime_output_base) + endif() + set(_runtime_modules_dst "${_runtime_output_base}/${_RUNTIME_MODULES_SUBDIR}") + list(APPEND _cfg_dst_pairs "${_consumer_config}::${_runtime_modules_dst}") + endif() + + set(${_OUT_CFG_DST_PAIRS} ${_cfg_dst_pairs} PARENT_SCOPE) +endfunction() + +function(_nbl_runtime_modules_add_configure_sync_rule_for_pairs _CFG_DST_PAIRS _ENABLE_CONFIGURE_DEPENDS) + set(_cfg_dst_pairs ${_CFG_DST_PAIRS}) + + foreach(_cfg_dst_pair IN LISTS _cfg_dst_pairs) + string(REPLACE "::" ";" _cfg_dst_parts "${_cfg_dst_pair}") + list(GET _cfg_dst_parts 0 _consumer_config) + list(GET _cfg_dst_parts 1 _runtime_modules_dst) + + string(MD5 _runtime_modules_dst_key "${_runtime_modules_dst}") + get_property(_runtime_modules_config_synced GLOBAL PROPERTY "NBL_RUNTIME_MODULES_CONFIG_SYNC_${_runtime_modules_dst_key}") + if(_runtime_modules_config_synced) + continue() + endif() + + _nbl_runtime_modules_resolve_imported_nabla_file("${_consumer_config}" _nabla_runtime_file) + _nbl_runtime_modules_resolve_dxc_runtime_file("${_nabla_runtime_file}" _dxc_runtime_file) + + file(MAKE_DIRECTORY "${_runtime_modules_dst}") + + cmake_path(GET _nabla_runtime_file FILENAME _nabla_runtime_name) + cmake_path(GET _dxc_runtime_file FILENAME _dxc_runtime_name) + set(_nabla_runtime_dst "${_runtime_modules_dst}/${_nabla_runtime_name}") + set(_dxc_runtime_dst "${_runtime_modules_dst}/${_dxc_runtime_name}") + + file(COPY_FILE "${_nabla_runtime_file}" "${_nabla_runtime_dst}" ONLY_IF_DIFFERENT INPUT_MAY_BE_RECENT) + file(COPY_FILE "${_dxc_runtime_file}" "${_dxc_runtime_dst}" ONLY_IF_DIFFERENT INPUT_MAY_BE_RECENT) + + if(_ENABLE_CONFIGURE_DEPENDS) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${_nabla_runtime_file}" + "${_dxc_runtime_file}" + "${_nabla_runtime_dst}" + "${_dxc_runtime_dst}" + ) + endif() + + set_property(GLOBAL PROPERTY "NBL_RUNTIME_MODULES_CONFIG_SYNC_${_runtime_modules_dst_key}" TRUE) + endforeach() +endfunction() + +function(_nbl_runtime_modules_add_configure_sync_rule_for_targets _TARGETS _RUNTIME_MODULES_SUBDIR _ENABLE_CONFIGURE_DEPENDS) + set(_targets ${_TARGETS}) + list(REMOVE_DUPLICATES _targets) + + set(_cfg_dst_pairs "") + foreach(_target IN LISTS _targets) + _nbl_runtime_modules_expand_target_configure_sync_pairs("${_target}" "${_RUNTIME_MODULES_SUBDIR}" _target_cfg_dst_pairs) + list(APPEND _cfg_dst_pairs ${_target_cfg_dst_pairs}) + endforeach() + + _nbl_runtime_modules_add_configure_sync_rule_for_pairs("${_cfg_dst_pairs}" "${_ENABLE_CONFIGURE_DEPENDS}") +endfunction() + +function(_nbl_runtime_modules_add_build_sync_rule_for_target _TARGET _RUNTIME_MODULES_SUBDIR) + if(NOT TARGET "${_TARGET}") + message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") + endif() + + get_target_property(_nbl_runtime_output_dir "${_TARGET}" RUNTIME_OUTPUT_DIRECTORY) + if(_nbl_runtime_output_dir) + set(_nbl_runtime_modules_dest "$") + elseif(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY STREQUAL "") + set(_nbl_runtime_modules_dest "$") + else() + set(_nbl_runtime_modules_dest "$/${_RUNTIME_MODULES_SUBDIR}") + endif() + + string(MD5 _nbl_runtime_modules_dest_key "${_nbl_runtime_modules_dest}") + + get_property(_nbl_runtime_modules_stamp GLOBAL PROPERTY "NBL_RUNTIME_MODULES_BUILD_SYNC_${_nbl_runtime_modules_dest_key}") + if(NOT _nbl_runtime_modules_stamp) + set(_nbl_runtime_modules_stamp "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/nabla_runtime_modules_${_nbl_runtime_modules_dest_key}.stamp") + + add_custom_command( + OUTPUT "${_nbl_runtime_modules_stamp}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_nbl_runtime_modules_dest}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "${_nbl_runtime_modules_dest}/" + COMMAND ${CMAKE_COMMAND} -E copy_directory "$,3rdparty,dxc>" "${_nbl_runtime_modules_dest}" + COMMAND ${CMAKE_COMMAND} -E touch "${_nbl_runtime_modules_stamp}" + DEPENDS + "$" + "$,3rdparty,dxc,${CMAKE_SHARED_LIBRARY_PREFIX}dxcompiler${CMAKE_SHARED_LIBRARY_SUFFIX}>" + "$>" + COMMAND_EXPAND_LISTS + VERBATIM + ) + + set_property(GLOBAL PROPERTY "NBL_RUNTIME_MODULES_BUILD_SYNC_${_nbl_runtime_modules_dest_key}" "${_nbl_runtime_modules_stamp}") + endif() + + set_source_files_properties("${_nbl_runtime_modules_stamp}" PROPERTIES GENERATED TRUE) + target_sources("${_TARGET}" PRIVATE "${_nbl_runtime_modules_stamp}") +endfunction() + +function(_nbl_runtime_modules_add_build_sync_rule_for_targets _TARGETS _RUNTIME_MODULES_SUBDIR) + set(_targets ${_TARGETS}) + list(REMOVE_DUPLICATES _targets) + + foreach(_target IN LISTS _targets) + _nbl_runtime_modules_add_build_sync_rule_for_target("${_target}" "${_RUNTIME_MODULES_SUBDIR}") + endforeach() +endfunction() + +function(_nbl_runtime_modules_add_build_sync_rule_for_destination_pairs _BUILD_TRIGGER_TARGETS _CFG_DST_PAIRS) + set(_build_trigger_targets ${_BUILD_TRIGGER_TARGETS}) + list(REMOVE_DUPLICATES _build_trigger_targets) + + foreach(_target IN LISTS _build_trigger_targets) + if(NOT TARGET "${_target}") + message(FATAL_ERROR "Nabla: BUILD_TRIGGER_TARGETS contains unknown target \"${_target}\"") + endif() + endforeach() + + set(_cfg_dst_pairs ${_CFG_DST_PAIRS}) + + foreach(_cfg_dst_pair IN LISTS _cfg_dst_pairs) + string(REPLACE "::" ";" _cfg_dst_parts "${_cfg_dst_pair}") + list(GET _cfg_dst_parts 0 _consumer_config) + list(GET _cfg_dst_parts 1 _runtime_modules_dst) + + _nbl_runtime_modules_resolve_imported_nabla_file("${_consumer_config}" _nabla_runtime_file) + _nbl_runtime_modules_resolve_dxc_runtime_file("${_nabla_runtime_file}" _dxc_runtime_file) + + cmake_path(GET _nabla_runtime_file FILENAME _nabla_runtime_name) + cmake_path(GET _dxc_runtime_file FILENAME _dxc_runtime_name) + set(_nabla_runtime_dst "${_runtime_modules_dst}/${_nabla_runtime_name}") + set(_dxc_runtime_dst "${_runtime_modules_dst}/${_dxc_runtime_name}") + + string(MD5 _runtime_modules_dst_key "${_consumer_config}::${_runtime_modules_dst}") + + get_property(_sync_target GLOBAL PROPERTY "NBL_RUNTIME_MODULES_BUILD_SYNC_DEST_TARGET_${_runtime_modules_dst_key}") + if(NOT _sync_target) + set(_sync_target "nabla_runtime_modules_dest_sync_${_runtime_modules_dst_key}") + set(_sync_stamp "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_sync_target}.stamp") + + add_custom_command( + OUTPUT "${_sync_stamp}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_runtime_modules_dst}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_nabla_runtime_file}" "${_runtime_modules_dst}/" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_dxc_runtime_file}" "${_runtime_modules_dst}/" + COMMAND ${CMAKE_COMMAND} -E touch "${_sync_stamp}" + DEPENDS + "${_nabla_runtime_file}" + "${_dxc_runtime_file}" + "${_nabla_runtime_dst}" + "${_dxc_runtime_dst}" + VERBATIM + ) + + add_custom_target("${_sync_target}" DEPENDS "${_sync_stamp}") + set_target_properties("${_sync_target}" PROPERTIES + FOLDER "CMakePredefinedTargets" + EXCLUDE_FROM_ALL TRUE + ) + + set_property(GLOBAL PROPERTY "NBL_RUNTIME_MODULES_BUILD_SYNC_DEST_TARGET_${_runtime_modules_dst_key}" "${_sync_target}") + endif() + + foreach(_target IN LISTS _build_trigger_targets) + add_dependencies("${_target}" "${_sync_target}") + endforeach() + endforeach() +endfunction() + +# +# nabla_apply_runtime_lookup( +# TARGETS +# [RUNTIME_MODULES_SUBDIR ] +# ) +# +# Applies runtime lookup compile definitions to executable targets. +# The lookup is always relative to executable directory and does not expose +# absolute paths. +# +# Notes: +# - TARGETS is required. +# - RUNTIME_MODULES_SUBDIR defaults to "Libraries". +# +function(nabla_apply_runtime_lookup) + set(_nbl_runtime_modules_subdir "Libraries") + + cmake_parse_arguments(_NBL_CUSTOM "" "RUNTIME_MODULES_SUBDIR" "TARGETS" ${ARGV}) + + if(_NBL_CUSTOM_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_apply_runtime_lookup: ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}") + endif() + + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") + endif() + + if(NOT _NBL_CUSTOM_TARGETS) + message(FATAL_ERROR "Nabla: nabla_apply_runtime_lookup requires TARGETS ") + endif() + + _nbl_runtime_modules_apply_lookup_definitions_to_targets("${_NBL_CUSTOM_TARGETS}" "${_nbl_runtime_modules_subdir}") +endfunction() + +# +# nabla_setup_runtime_install_modules( +# [RUNTIME_MODULES_SUBDIR ] +# ) +# +# Adds install() rules that copy Nabla and DXC runtime modules into: +# ${CMAKE_INSTALL_BINDIR}/ +# +# Notes: +# - RUNTIME_MODULES_SUBDIR defaults to "Libraries". +# - This helper only adds install rules. +# +function(nabla_setup_runtime_install_modules) + set(_nbl_runtime_modules_subdir "Libraries") + cmake_parse_arguments(_NBL_CUSTOM "" "RUNTIME_MODULES_SUBDIR" "" ${ARGV}) + + if(_NBL_CUSTOM_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_setup_runtime_install_modules: ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}") + endif() + + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") + endif() + + _nbl_runtime_modules_add_install_rules("${_nbl_runtime_modules_subdir}") +endfunction() + +# +# nabla_sync_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] +# [MODE BUILD_TIME|CONFIGURE_TIME|BOTH] +# [RUNTIME_MODULES_SUBDIR ] +# [BUILD_TRIGGER_TARGETS ] +# ) +# +# Synchronizes runtime modules from Nabla package into consumer runtime layout. +# +# Input modes (mutually exclusive): +# - TARGETS mode +# Copies beside each target runtime dir under RUNTIME_MODULES_SUBDIR. +# - DESTINATION mode +# Copies to explicit DESTINATION or DESTINATION_DEBUG/RELEASE/RELWITHDEBINFO paths. +# +# MODE: +# - BUILD_TIME +# Copy during build. +# - CONFIGURE_TIME +# Copy during configure/generate and set configure depends. +# - BOTH +# Run configure-time copy and build-time copy. +# +# Rules: +# - exactly one input mode must be used +# - BUILD_TRIGGER_TARGETS is valid only in DESTINATION mode for BUILD_TIME/BOTH +# +function(nabla_sync_runtime_modules) + set(_nbl_runtime_modules_subdir "Libraries") + set(_nbl_mode BUILD_TIME) + + cmake_parse_arguments(_NBL_CUSTOM "" "MODE;DESTINATION;DESTINATION_DEBUG;DESTINATION_RELEASE;DESTINATION_RELWITHDEBINFO;RUNTIME_MODULES_SUBDIR" "TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) + + if(_NBL_CUSTOM_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_sync_runtime_modules: ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}") + endif() + + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") + endif() + if(DEFINED _NBL_CUSTOM_MODE) + set(_nbl_mode "${_NBL_CUSTOM_MODE}") + endif() + + string(TOUPPER "${_nbl_mode}" _nbl_mode) + if(NOT _nbl_mode MATCHES "^(BUILD_TIME|CONFIGURE_TIME|BOTH)$") + message(FATAL_ERROR "Nabla: invalid MODE='${_nbl_mode}', expected BUILD_TIME, CONFIGURE_TIME or BOTH") + endif() + + set(_has_targets OFF) + if(_NBL_CUSTOM_TARGETS) + set(_has_targets ON) + endif() + + set(_has_destination OFF) + if(DEFINED _NBL_CUSTOM_DESTINATION AND NOT _NBL_CUSTOM_DESTINATION STREQUAL "") + set(_has_destination ON) + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_DEBUG AND NOT _NBL_CUSTOM_DESTINATION_DEBUG STREQUAL "") + set(_has_destination ON) + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_RELEASE AND NOT _NBL_CUSTOM_DESTINATION_RELEASE STREQUAL "") + set(_has_destination ON) + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_RELWITHDEBINFO AND NOT _NBL_CUSTOM_DESTINATION_RELWITHDEBINFO STREQUAL "") + set(_has_destination ON) + endif() + + if(_has_targets AND _has_destination) + message(FATAL_ERROR "Nabla: use either TARGETS mode or DESTINATION mode, not both") + endif() + + if(NOT _has_targets AND NOT _has_destination) + message(FATAL_ERROR "Nabla: nabla_sync_runtime_modules requires TARGETS or DESTINATION/DESTINATION_DEBUG/DESTINATION_RELEASE/DESTINATION_RELWITHDEBINFO") + endif() + + if(_has_targets) + if(_NBL_CUSTOM_BUILD_TRIGGER_TARGETS) + message(FATAL_ERROR "Nabla: BUILD_TRIGGER_TARGETS is valid only in DESTINATION mode") + endif() + + if(_nbl_mode STREQUAL "CONFIGURE_TIME" OR _nbl_mode STREQUAL "BOTH") + set(_enable_configure_depends OFF) + if(_nbl_mode STREQUAL "CONFIGURE_TIME") + set(_enable_configure_depends ON) + endif() + _nbl_runtime_modules_add_configure_sync_rule_for_targets("${_NBL_CUSTOM_TARGETS}" "${_nbl_runtime_modules_subdir}" "${_enable_configure_depends}") + endif() + + if(_nbl_mode STREQUAL "BUILD_TIME" OR _nbl_mode STREQUAL "BOTH") + _nbl_runtime_modules_add_build_sync_rule_for_targets("${_NBL_CUSTOM_TARGETS}" "${_nbl_runtime_modules_subdir}") + endif() + + return() + endif() + + _nbl_runtime_modules_expand_destination_pairs( + "${_NBL_CUSTOM_DESTINATION}" + "${_NBL_CUSTOM_DESTINATION_DEBUG}" + "${_NBL_CUSTOM_DESTINATION_RELEASE}" + "${_NBL_CUSTOM_DESTINATION_RELWITHDEBINFO}" + _cfg_dst_pairs + ) + + if(_nbl_mode STREQUAL "CONFIGURE_TIME" OR _nbl_mode STREQUAL "BOTH") + set(_enable_configure_depends OFF) + if(_nbl_mode STREQUAL "CONFIGURE_TIME") + set(_enable_configure_depends ON) + endif() + _nbl_runtime_modules_add_configure_sync_rule_for_pairs("${_cfg_dst_pairs}" "${_enable_configure_depends}") + endif() + + if(_nbl_mode STREQUAL "BUILD_TIME" OR _nbl_mode STREQUAL "BOTH") + if(NOT _NBL_CUSTOM_BUILD_TRIGGER_TARGETS) + message(FATAL_ERROR "Nabla: DESTINATION mode with MODE ${_nbl_mode} requires BUILD_TRIGGER_TARGETS") + endif() + _nbl_runtime_modules_add_build_sync_rule_for_destination_pairs("${_NBL_CUSTOM_BUILD_TRIGGER_TARGETS}" "${_cfg_dst_pairs}") + elseif(_NBL_CUSTOM_BUILD_TRIGGER_TARGETS) + message(FATAL_ERROR "Nabla: BUILD_TRIGGER_TARGETS is valid only for MODE BUILD_TIME or MODE BOTH") + endif() +endfunction() + +# +# nabla_setup_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] +# [MODE BUILD_TIME|CONFIGURE_TIME|BOTH] +# [RUNTIME_MODULES_SUBDIR ] +# [INSTALL_RULES ON|OFF] +# [APPLY_LOOKUP_TO_TARGETS ] +# [BUILD_TRIGGER_TARGETS ] +# ) +# +# Convenience wrapper that composes: +# - nabla_sync_runtime_modules(...) +# - nabla_apply_runtime_lookup(...) +# - nabla_setup_runtime_install_modules(...) when INSTALL_RULES is enabled +# +# Lookup behavior: +# - if APPLY_LOOKUP_TO_TARGETS is set, lookup is applied to that list +# - else if TARGETS mode is used, lookup is applied to TARGETS +# - else no lookup changes are applied +# +function(nabla_setup_runtime_modules) + set(_nbl_runtime_modules_subdir "Libraries") + set(_nbl_install_rules OFF) + set(_nbl_mode BUILD_TIME) + + cmake_parse_arguments(_NBL_CUSTOM "" "RUNTIME_MODULES_SUBDIR;INSTALL_RULES;MODE;DESTINATION;DESTINATION_DEBUG;DESTINATION_RELEASE;DESTINATION_RELWITHDEBINFO" "TARGETS;APPLY_LOOKUP_TO_TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) + + if(_NBL_CUSTOM_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_setup_runtime_modules: ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}") + endif() + + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") + endif() + if(DEFINED _NBL_CUSTOM_INSTALL_RULES) + set(_nbl_install_rules "${_NBL_CUSTOM_INSTALL_RULES}") + endif() + if(DEFINED _NBL_CUSTOM_MODE) + set(_nbl_mode "${_NBL_CUSTOM_MODE}") + endif() + + set(_sync_args + MODE "${_nbl_mode}" + RUNTIME_MODULES_SUBDIR "${_nbl_runtime_modules_subdir}" + ) + + if(_NBL_CUSTOM_TARGETS) + list(APPEND _sync_args TARGETS ${_NBL_CUSTOM_TARGETS}) + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION AND NOT _NBL_CUSTOM_DESTINATION STREQUAL "") + list(APPEND _sync_args DESTINATION "${_NBL_CUSTOM_DESTINATION}") + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_DEBUG AND NOT _NBL_CUSTOM_DESTINATION_DEBUG STREQUAL "") + list(APPEND _sync_args DESTINATION_DEBUG "${_NBL_CUSTOM_DESTINATION_DEBUG}") + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_RELEASE AND NOT _NBL_CUSTOM_DESTINATION_RELEASE STREQUAL "") + list(APPEND _sync_args DESTINATION_RELEASE "${_NBL_CUSTOM_DESTINATION_RELEASE}") + endif() + if(DEFINED _NBL_CUSTOM_DESTINATION_RELWITHDEBINFO AND NOT _NBL_CUSTOM_DESTINATION_RELWITHDEBINFO STREQUAL "") + list(APPEND _sync_args DESTINATION_RELWITHDEBINFO "${_NBL_CUSTOM_DESTINATION_RELWITHDEBINFO}") + endif() + if(_NBL_CUSTOM_BUILD_TRIGGER_TARGETS) + list(APPEND _sync_args BUILD_TRIGGER_TARGETS ${_NBL_CUSTOM_BUILD_TRIGGER_TARGETS}) + endif() + + nabla_sync_runtime_modules(${_sync_args}) + + set(_lookup_targets "") + if(_NBL_CUSTOM_APPLY_LOOKUP_TO_TARGETS) + set(_lookup_targets ${_NBL_CUSTOM_APPLY_LOOKUP_TO_TARGETS}) + elseif(_NBL_CUSTOM_TARGETS) + set(_lookup_targets ${_NBL_CUSTOM_TARGETS}) + endif() + + if(_lookup_targets) + nabla_apply_runtime_lookup( + TARGETS ${_lookup_targets} + RUNTIME_MODULES_SUBDIR "${_nbl_runtime_modules_subdir}" + ) + endif() + + if(_nbl_install_rules) + nabla_setup_runtime_install_modules( + RUNTIME_MODULES_SUBDIR "${_nbl_runtime_modules_subdir}" + ) + endif() +endfunction() + if(NABLA_FIND_PACKAGE_VERBOSE) message(STATUS "\n-- Nabla_ROOT = ${Nabla_ROOT}" @@ -29,4 +723,4 @@ if(NABLA_FIND_PACKAGE_VERBOSE) "-- Nabla's DXC module git info:" "\n${_nabla_dxc_git_info_raw}" ) -endif() \ No newline at end of file +endif() diff --git a/cmake/common.cmake b/cmake/common.cmake index dbe30dc3d7..48a4098d97 100755 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -1535,7 +1535,6 @@ namespace @IMPL_NAMESPACE@ { endif() set(NBL_NSC_COMPILE_COMMAND - "${CMAKE_COMMAND}" -E env "NBL_RUN_FROM_BUILD_INTERFACE=$<$>>:1>" "$" -Fc "${TARGET_OUTPUT}" ${COMPILE_OPTIONS} ${REQUIRED_OPTIONS} ${IMPL_COMMON_OPTIONS} diff --git a/docs/consume/README.md b/docs/consume/README.md new file mode 100644 index 0000000000..e73a69c310 --- /dev/null +++ b/docs/consume/README.md @@ -0,0 +1,199 @@ +# Consuming Nabla Package + +This document describes how to consume an installed Nabla package from another CMake project. + +## 1. Package API + +After `find_package(Nabla CONFIG REQUIRED)`, the package provides: + +- imported target `Nabla::Nabla` +- helper `nabla_sync_runtime_modules(...)` +- helper `nabla_apply_runtime_lookup(...)` +- helper `nabla_setup_runtime_install_modules(...)` +- wrapper `nabla_setup_runtime_modules(...)` + +On shared builds, runtime modules include Nabla and DXC. + +Implementation and argument docs: + +- package API implementation: `${Nabla_ROOT}/cmake/NablaConfig.cmake` +- source template in Nabla repo: `cmake/NablaConfig.cmake.in` +- each public helper has usage notes in comments directly above its definition + +## 2. Minimal baseline + +```cmake +cmake_minimum_required(VERSION 3.30) +project(MyApp CXX) + +find_package(Nabla REQUIRED CONFIG) + +add_executable(my_app main.cpp) +target_link_libraries(my_app PRIVATE Nabla::Nabla) +``` + +Behavior in this minimal setup: + +- executable loads Nabla/DXC directly from package-provided lookup paths +- this works in consumer build interface without extra copy helpers +- install layout is not configured by this baseline + +If you also need your own install layout, add install rules and relative lookup defines. +Helpers from sections below can do this for you. + +## 3. Runtime setup primitives + +### 3.1 Copy runtime modules + +```cmake +nabla_sync_runtime_modules( + TARGETS my_app + MODE BUILD_TIME + RUNTIME_MODULES_SUBDIR "Libraries" +) +``` + +or with explicit destination(s): + +```cmake +nabla_sync_runtime_modules( + DESTINATION_DEBUG "${CMAKE_BINARY_DIR}/Debug/Libraries" + DESTINATION_RELEASE "${CMAKE_BINARY_DIR}/Release/Libraries" + DESTINATION_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/RelWithDebInfo/Libraries" + MODE CONFIGURE_TIME +) +``` + +Rules: + +- use either `TARGETS` mode or `DESTINATION` / `DESTINATION_DEBUG` / `DESTINATION_RELEASE` / `DESTINATION_RELWITHDEBINFO` mode +- `MODE CONFIGURE_TIME` does copy during configure/generate +- `MODE BUILD_TIME` and `MODE BOTH` in destination mode require `BUILD_TRIGGER_TARGETS` + +### 3.2 Apply runtime lookup defines + +```cmake +nabla_apply_runtime_lookup( + TARGETS my_app + RUNTIME_MODULES_SUBDIR "Libraries" +) +``` + +This sets: + +- `NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./Libraries"` +- `NBL_CPACK_PACKAGE_DXC_DLL_DIR="./Libraries"` + +### 3.3 Install runtime modules + +```cmake +include(GNUInstallDirs) + +nabla_setup_runtime_install_modules( + RUNTIME_MODULES_SUBDIR "Libraries" +) + +install(TARGETS my_app + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +``` + +## 4. Wrapper helper + +`nabla_setup_runtime_modules(...)` composes: + +- `nabla_sync_runtime_modules(...)` +- `nabla_apply_runtime_lookup(...)` +- optional `nabla_setup_runtime_install_modules(...)` + +Example: + +```cmake +nabla_setup_runtime_modules( + TARGETS my_app + MODE CONFIGURE_TIME + RUNTIME_MODULES_SUBDIR "Libraries" + INSTALL_RULES ON +) +``` + +## 5. Split flow global copy and per-exe lookup + +This is the split pattern used by consumers that want one global copy setup and per-exe lookup: + +```cmake +# one global copy setup +nabla_sync_runtime_modules( + DESTINATION_DEBUG "${CMAKE_BINARY_DIR}/3rdparty/shared/Debug/Libraries" + DESTINATION_RELEASE "${CMAKE_BINARY_DIR}/3rdparty/shared/Release/Libraries" + DESTINATION_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/3rdparty/shared/RelWithDebInfo/Libraries" + MODE CONFIGURE_TIME +) + +# per executable target +nabla_apply_runtime_lookup( + TARGETS my_app + RUNTIME_MODULES_SUBDIR "Libraries" +) +``` + +## 6. Config mapping + +Runtime source paths are resolved from mapped imported config of `Nabla::Nabla`. + +Imported-config mapping applies automatically. This includes cross-config usage when one consumer config maps to a different imported config. + +If you override mapping: + +- do it in the same configure run +- if using `CMAKE_MAP_IMPORTED_CONFIG_`, set it before `find_package(Nabla)` +- for `MODE CONFIGURE_TIME` and `MODE BOTH`, set mapping before helper call + +## 7. Troubleshooting + +### `Could not load dxcompiler module` or `Could not load Nabla API` + +Check: + +- lookup defines are applied to executable target(s) +- lookup subdir matches actual runtime layout +- runtime modules exist in build/install runtime directory + +### Build works but installed app fails + +Install rules are usually missing. + +Use either: + +- `nabla_setup_runtime_install_modules(...)` +- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` + +## 8. Design guidance + +For relocatable consumers: + +- keep lookup relative to executable +- never expose absolute paths in public compile definitions +- keep copy setup and lookup setup explicit in CMake + +Note: + +- current Nabla build interface still compiles some runtime lookup data with absolute paths +- this is a known issue on Nabla side and will be refactored +- do not propagate that pattern to package consumers +- consumer-facing package helpers are designed to avoid exposing absolute paths in consumer compile definitions + +## 9. Smoke reference + +`smoke/` is a reference consumer for Nabla package consumption. + +It contains multiple usage flows: + +- `MINIMALISTIC` link-only consumption without helper calls +- `CONFIGURE_ONLY` helper-based configure-time runtime sync +- `BUILD_ONLY` helper-based build-time runtime sync + +Flow selection is done with `NBL_SMOKE_FLOW` in `smoke/CMakeLists.txt` and `FLOW` in `smoke/RunSmokeFlow.cmake`. + +Smoke is also used as CI coverage for package consumption flows. +The `smoke-tests` job in `.github/workflows/build-nabla.yml` runs those flows as end-to-end checks. diff --git a/include/nbl/system/CSystemWin32.h b/include/nbl/system/CSystemWin32.h index 01766ddaa8..7c73525c43 100644 --- a/include/nbl/system/CSystemWin32.h +++ b/include/nbl/system/CSystemWin32.h @@ -2,9 +2,12 @@ #define _NBL_SYSTEM_C_SYSTEM_WIN32_H_INCLUDED_ #include "nbl/system/ISystem.h" +#include "nbl/system/ModuleLookupUtils.h" #ifdef _NBL_PLATFORM_WINDOWS_ +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include #include @@ -51,13 +54,7 @@ class NBL_API2 CSystemWin32 : public ISystem #endif ; // legal & on purpose - const auto executableDirectory = []() -> std::filesystem::path - { - wchar_t path[MAX_PATH] = { 0 }; - GetModuleFileNameW(NULL, path, MAX_PATH); - - return std::filesystem::path(path).parent_path(); - }(); + const auto exeDirectory = executableDirectory(); // load from right next to the executable (always be able to override like this) HMODULE res = LoadLibraryExA(dllName, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR); @@ -80,7 +77,7 @@ class NBL_API2 CSystemWin32 : public ISystem // then relative to the executable's directory { - const auto path = std::filesystem::absolute(executableDirectory / requestModulePath).string(); + const auto path = std::filesystem::absolute(exeDirectory / requestModulePath).string(); if (logRequests) printf("[INFO]: Requesting \"%s\" module load with \"%s\" search path...\n", dllName, path.c_str()); @@ -124,4 +121,4 @@ class NBL_API2 CSystemWin32 : public ISystem #endif -#endif \ No newline at end of file +#endif diff --git a/include/nbl/system/IApplicationFramework.h b/include/nbl/system/IApplicationFramework.h index d91151a2db..53af9c9b94 100644 --- a/include/nbl/system/IApplicationFramework.h +++ b/include/nbl/system/IApplicationFramework.h @@ -15,6 +15,7 @@ #include "nbl/system/CSystemAndroid.h" #include "nbl/system/CSystemLinux.h" #include "nbl/system/CSystemWin32.h" +#include "nbl/system/RuntimeModuleLookup.h" namespace nbl::system { @@ -25,91 +26,33 @@ class IApplicationFramework : public core::IReferenceCounted // this is safe to call multiple times static bool GlobalsInit() { - // TODO: update CMake and rename "DLL" in all of those defines here to "MODULE" or "RUNTIME" - auto resolveDir = [](const char* value) - { - if (!value || (value[0] == '\0')) - return system::path(""); - - const auto candidate = system::path(value); - if (std::filesystem::exists(candidate)) - return candidate; - - return system::path(""); - }; - - auto readEnvFlag = [](const char* key) - { - const char* value = std::getenv(key); - if (!value || (value[0] == '\0')) - return false; - - const std::string_view v(value); - return (v != "0") && (v != "false") && (v != "off") && (v != "no"); - }; - - const auto sdk = resolveDir(std::getenv("NBL_INSTALL_DIRECTORY")); - - #ifdef NBL_RELOCATABLE_PACKAGE - // Relocatable package consumers must use install lookups only. - const bool useInstallLookups = true; - #else - // Build-interface binaries select lookup mode at runtime via NBL_RUN_FROM_BUILD_INTERFACE. - // This is required because the same host-built executable can later be run from an install package. - const bool useInstallLookups = false;// !readEnvFlag("NBL_RUN_FROM_BUILD_INTERFACE"); - #endif // NBL_RELOCATABLE_PACKAGE - - constexpr struct - { - std::string_view nabla, dxc; - } module = - { - #ifdef _NBL_SHARED_BUILD_ - _NABLA_DLL_NAME_ - #else - "" - #endif - , - "dxcompiler" - }; - - struct - { - system::path nabla, dxc; - } install, env, build, rel; - - #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY) && defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY) - - #if defined(_NABLA_INSTALL_DIR_) - install.nabla = std::filesystem::absolute(system::path(_NABLA_INSTALL_DIR_) / NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY); - install.dxc = std::filesystem::absolute(system::path(_NABLA_INSTALL_DIR_) / NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY); - #endif - - //! ABS key is full key to file inside relocatable package - env.nabla = sdk / NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY; - env.dxc = sdk / NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY; - #endif - - #ifdef _NBL_SHARED_BUILD_ - #if defined(_NABLA_OUTPUT_DIR_) - build.nabla = _NABLA_OUTPUT_DIR_; - #endif - #endif - #if defined(_DXC_DLL_) - build.dxc = path(_DXC_DLL_).parent_path(); - #endif - - //! consumer can set this as relative path between exe & DLLs - #ifdef NBL_CPACK_PACKAGE_NABLA_DLL_DIR - rel.nabla = NBL_CPACK_PACKAGE_NABLA_DLL_DIR; - #endif - - #ifdef NBL_CPACK_PACKAGE_DXC_DLL_DIR - rel.dxc = NBL_CPACK_PACKAGE_DXC_DLL_DIR; - #endif - - using RV = const std::vector; - auto load = [](std::string_view moduleName, const RV& searchPaths) + RuntimeModuleLookup lookup; + + const auto exeDirectory = system::executableDirectory(); + lookup.applyInstallOverrides(exeDirectory); + /* + In the current design build interface and install interface cannot share one lookup set. + + Build lookup may point to host-only output folders while install lookup must stay relocatable. + Mixing them can load stale modules from host build trees and break packaged consumers. + Another big issue is Nabla build-system layout because runtime binaries are emitted into + source-side locations instead of a binary-tree runtime prefix that mirrors install layout. + This makes executable-relative lookup ambiguous and forces a split between build and install lookup modes. + There are more issues caused by this non-unified layout than the ones handled in this file. + + Desired end state is that build outputs follow the same relative runtime layout as install so lookup can stay install-style + for both host build and package consumers while still allowing consumer override paths like "./Libraries". + No interface should ever expose any define that contains an absolute path. + All binaries must be emitted into the build directory and Nabla + should remain fully buildable with a read-only source filesystem. + + I cannot address all of that here because it requires a broader Nabla build-system refactor. + */ + const bool useInstallLookups = lookup.chooseInstallLookupMode(exeDirectory); + lookup.finalizeInstallLookups(useInstallLookups); + + using SearchPaths = std::vector; + const auto load = [](std::string_view moduleName, const SearchPaths& searchPaths) { #ifdef _NBL_PLATFORM_WINDOWS_ const bool isAlreadyLoaded = GetModuleHandleA(moduleName.data()); @@ -132,11 +75,11 @@ class IApplicationFramework : public core::IReferenceCounted return true; }; - if (not load(module.dxc, useInstallLookups ? RV{ rel.dxc, env.dxc, install.dxc } : RV{ build.dxc })) + if (not load(lookup.dxc.name, useInstallLookups ? SearchPaths{ lookup.dxc.paths.install } : SearchPaths{ lookup.dxc.paths.build })) return false; #ifdef _NBL_SHARED_BUILD_ - if (not load(module.nabla, useInstallLookups ? RV{ rel.nabla, env.nabla, install.nabla } : RV{ build.nabla })) + if (not load(lookup.nabla.name, useInstallLookups ? SearchPaths{ lookup.nabla.paths.install } : SearchPaths{ lookup.nabla.paths.build })) return false; #endif diff --git a/include/nbl/system/ModuleLookupUtils.h b/include/nbl/system/ModuleLookupUtils.h new file mode 100644 index 0000000000..c763cc8e30 --- /dev/null +++ b/include/nbl/system/ModuleLookupUtils.h @@ -0,0 +1,114 @@ +#ifndef _NBL_SYSTEM_MODULE_LOOKUP_UTILS_H_INCLUDED_ +#define _NBL_SYSTEM_MODULE_LOOKUP_UTILS_H_INCLUDED_ + +#include "nbl/system/path.h" + +#include +#include +#include +#include + +#if defined(_NBL_PLATFORM_WINDOWS_) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace nbl::system +{ +inline bool moduleExistsInDirectory(const system::path& dir, std::string_view moduleName) +{ + if (dir.empty() || moduleName.empty() || !std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) + return false; + + const std::string baseName(moduleName); + const auto hasRegularFile = [&dir](const std::string& fileName) + { + const auto filePath = dir / fileName; + return std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath); + }; + + if (hasRegularFile(baseName)) + return true; + + #if defined(_NBL_PLATFORM_WINDOWS_) + if (hasRegularFile(baseName + ".dll")) + return true; + #elif defined(_NBL_PLATFORM_LINUX_) || defined(_NBL_PLATFORM_ANDROID_) + if (hasRegularFile(baseName + ".so")) + return true; + + const bool hasLibPrefix = (baseName.rfind("lib", 0) == 0); + const std::string libBaseName = hasLibPrefix ? baseName : ("lib" + baseName); + if (hasRegularFile(libBaseName + ".so")) + return true; + + const std::string versionedPrefix = libBaseName + ".so."; + std::error_code ec; + for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) + { + if (ec) + break; + if (!entry.is_regular_file(ec)) + continue; + + const auto fileName = entry.path().filename().string(); + if (fileName.rfind(versionedPrefix, 0) == 0) + return true; + } + #elif defined(__APPLE__) + if (hasRegularFile(baseName + ".dylib")) + return true; + + const bool hasLibPrefix = (baseName.rfind("lib", 0) == 0); + if (!hasLibPrefix && hasRegularFile("lib" + baseName + ".dylib")) + return true; + #endif + + return false; +} + +inline system::path executableDirectory() +{ + #if defined(_NBL_PLATFORM_WINDOWS_) + wchar_t modulePath[MAX_PATH] = {}; + const auto length = GetModuleFileNameW(nullptr, modulePath, MAX_PATH); + if ((length == 0) || (length >= MAX_PATH)) + return system::path(""); + return std::filesystem::path(modulePath).parent_path(); + #elif defined(_NBL_PLATFORM_LINUX_) || defined(_NBL_PLATFORM_ANDROID_) + std::error_code ec; + const auto executablePath = std::filesystem::read_symlink("/proc/self/exe", ec); + if (ec) + return system::path(""); + return executablePath.parent_path(); + #else + return system::path(""); + #endif +} + +inline system::path loadedModuleDirectory(std::string_view moduleName) +{ + #if defined(_NBL_PLATFORM_WINDOWS_) + if (moduleName.empty()) + return system::path(""); + + const auto moduleHandle = GetModuleHandleA(moduleName.data()); + if (moduleHandle == nullptr) + return system::path(""); + + wchar_t modulePath[MAX_PATH] = {}; + const auto length = GetModuleFileNameW(moduleHandle, modulePath, MAX_PATH); + if ((length == 0) || (length >= MAX_PATH)) + return system::path(""); + + return std::filesystem::path(modulePath).parent_path(); + #else + // TODO: implement loaded module directory lookup for non-Windows platforms. + return system::path(""); + #endif +} +} + +#endif diff --git a/include/nbl/system/RuntimeModuleLookup.h b/include/nbl/system/RuntimeModuleLookup.h new file mode 100644 index 0000000000..dd6ce35e67 --- /dev/null +++ b/include/nbl/system/RuntimeModuleLookup.h @@ -0,0 +1,244 @@ +#ifndef _NBL_SYSTEM_RUNTIME_MODULE_LOOKUP_H_INCLUDED_ +#define _NBL_SYSTEM_RUNTIME_MODULE_LOOKUP_H_INCLUDED_ + +#include "nbl/system/ModuleLookupUtils.h" + +namespace nbl::system +{ +struct RuntimeModuleLookup final +{ + struct LookupPaths + { + system::path install; + system::path build; + }; + + struct Module + { + LookupPaths paths; + std::string_view name = ""; + std::string_view buildOutputDir = ""; + std::string_view buildDllPath = ""; + std::string_view installOverrideRel = ""; + std::string_view installBuildFallbackRel = ""; + std::string_view runtimeAbsKey = ""; + }; + + bool sharedBuild = false; + bool relocatablePackage = false; + Module nabla; + Module dxc; + + RuntimeModuleLookup() + { + dxc.name = "dxcompiler"; + #if defined(_NBL_SHARED_BUILD_) + sharedBuild = true; + nabla.name = _NABLA_DLL_NAME_; + #endif + #if defined(NBL_RELOCATABLE_PACKAGE) + relocatablePackage = true; + #endif + #if defined(_NABLA_OUTPUT_DIR_) + nabla.buildOutputDir = _NABLA_OUTPUT_DIR_; + #endif + #if defined(_DXC_DLL_) + dxc.buildDllPath = _DXC_DLL_; + #endif + #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR) + nabla.installOverrideRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR; + #endif + #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR) + dxc.installOverrideRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR; + #endif + #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_BUILD_FALLBACK) + nabla.installBuildFallbackRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR_BUILD_FALLBACK; + #endif + #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR_BUILD_FALLBACK) + dxc.installBuildFallbackRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR_BUILD_FALLBACK; + #endif + #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY) + nabla.runtimeAbsKey = NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY; + #endif + #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY) + dxc.runtimeAbsKey = NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY; + #endif + + applyBuildInterfacePaths(); + } + + inline void applyInstallOverrides(const system::path& exeDirectory) + { + if (hasInstallOverride(nabla)) + nabla.paths.install = absoluteFromExe(exeDirectory, nabla.installOverrideRel); + if (hasInstallOverride(dxc)) + dxc.paths.install = absoluteFromExe(exeDirectory, dxc.installOverrideRel); + } + + inline bool chooseInstallLookupMode(const system::path& exeDirectory) + { + if (relocatablePackage) + { + if (!hasUsableInstallPaths()) + { + if (!tryResolveInstallPathsFromPackageLayout(exeDirectory)) + tryResolveInstallPathsFromBuildFallbackHints(exeDirectory); + } + return true; + } + if (hasUsableInstallPaths()) + return true; + if (tryResolveInstallPathsFromPackageLayout(exeDirectory)) + return true; + return tryResolveInstallPathsFromBuildFallbackHints(exeDirectory); + } + + inline void finalizeInstallLookups(bool useInstallLookups) + { + if (!useInstallLookups) + return; + #if defined(_NBL_PLATFORM_WINDOWS_) && defined(_NBL_SHARED_BUILD_) + if (nabla.paths.install.empty()) + nabla.paths.install = loadedModuleDirectory(nabla.name); + #endif + resolveDxcInstallPathFromLoadedNabla(useInstallLookups); + } + + private: + static inline bool hasInstallOverride(const Module& module) + { + return !module.installOverrideRel.empty(); + } + + static inline bool hasRuntimeAbsKey(const Module& module) + { + return !module.runtimeAbsKey.empty(); + } + + inline void applyBuildInterfacePaths() + { + if (sharedBuild && !nabla.buildOutputDir.empty()) + nabla.paths.build = system::path(nabla.buildOutputDir); + if (!dxc.buildDllPath.empty()) + dxc.paths.build = system::path(dxc.buildDllPath).parent_path(); + } + + static inline system::path absoluteFromExe(const system::path& exeDirectory, std::string_view relativePath) + { + if (relativePath.empty() || exeDirectory.empty()) + return system::path(""); + + const auto relPath = system::path(relativePath); + if (relPath.is_absolute()) + return system::path(""); + + return std::filesystem::absolute(exeDirectory / relPath); + } + + inline bool hasUsableInstallPaths() const + { + if (!moduleExistsInDirectory(dxc.paths.install, dxc.name)) + return false; + return !sharedBuild || moduleExistsInDirectory(nabla.paths.install, nabla.name); + } + + inline bool tryResolveInstallPathsFromPrefix(const system::path& candidatePrefix) + { + if (candidatePrefix.empty()) + return false; + if (!hasRuntimeAbsKey(nabla) && !hasRuntimeAbsKey(dxc)) + return false; + + Module candidateNabla = nabla; + Module candidateDxc = dxc; + + if (hasRuntimeAbsKey(nabla)) + candidateNabla.paths.install = std::filesystem::absolute(candidatePrefix / system::path(nabla.runtimeAbsKey)); + if (hasRuntimeAbsKey(dxc)) + candidateDxc.paths.install = std::filesystem::absolute(candidatePrefix / system::path(dxc.runtimeAbsKey)); + + if (!moduleExistsInDirectory(candidateDxc.paths.install, candidateDxc.name)) + return false; + if (sharedBuild && !moduleExistsInDirectory(candidateNabla.paths.install, candidateNabla.name)) + return false; + + nabla.paths.install = candidateNabla.paths.install; + dxc.paths.install = candidateDxc.paths.install; + return true; + } + + inline bool tryResolveInstallPathsFromPackageLayout(const system::path& lookupStartDirectory) + { + if (lookupStartDirectory.empty()) + return false; + if (!hasRuntimeAbsKey(nabla) && !hasRuntimeAbsKey(dxc)) + return false; + + auto candidatePrefix = std::filesystem::absolute(lookupStartDirectory); + while (!candidatePrefix.empty()) + { + if (tryResolveInstallPathsFromPrefix(candidatePrefix)) + return true; + + const auto parent = candidatePrefix.parent_path(); + if (parent == candidatePrefix) + break; + candidatePrefix = parent; + } + return false; + } + + inline bool tryResolveInstallPathsFromBuildFallbackHints(const system::path& exeDirectory) + { + Module candidateNabla = nabla; + Module candidateDxc = dxc; + candidateNabla.paths.install = system::path(""); + candidateDxc.paths.install = system::path(""); + + if (!candidateNabla.installBuildFallbackRel.empty()) + candidateNabla.paths.install = absoluteFromExe(exeDirectory, candidateNabla.installBuildFallbackRel); + if (!candidateDxc.installBuildFallbackRel.empty()) + candidateDxc.paths.install = absoluteFromExe(exeDirectory, candidateDxc.installBuildFallbackRel); + + if (candidateDxc.paths.install.empty() && !candidateNabla.paths.install.empty() && hasRuntimeAbsKey(nabla) && hasRuntimeAbsKey(dxc)) + { + const auto dxcRelToNabla = system::path(dxc.runtimeAbsKey).lexically_relative(system::path(nabla.runtimeAbsKey)); + if (!dxcRelToNabla.empty() && dxcRelToNabla != system::path(".")) + candidateDxc.paths.install = std::filesystem::absolute(candidateNabla.paths.install / dxcRelToNabla); + } + + if (!moduleExistsInDirectory(candidateDxc.paths.install, candidateDxc.name)) + return false; + if (sharedBuild && !moduleExistsInDirectory(candidateNabla.paths.install, candidateNabla.name)) + return false; + + nabla.paths.install = candidateNabla.paths.install; + dxc.paths.install = candidateDxc.paths.install; + return true; + } + + #if defined(_NBL_PLATFORM_WINDOWS_) + inline void resolveDxcInstallPathFromLoadedNabla(bool useInstallLookups) + { + if (!useInstallLookups || !dxc.paths.install.empty()) + return; + if (!(sharedBuild && !nabla.runtimeAbsKey.empty() && !dxc.runtimeAbsKey.empty())) + return; + + const auto nablaRuntimeDir = !nabla.paths.install.empty() ? nabla.paths.install : loadedModuleDirectory(nabla.name); + if (nablaRuntimeDir.empty()) + return; + + const auto dxcRelToNabla = system::path(dxc.runtimeAbsKey).lexically_relative(system::path(nabla.runtimeAbsKey)); + if (!dxcRelToNabla.empty() && dxcRelToNabla != system::path(".")) + dxc.paths.install = std::filesystem::absolute(nablaRuntimeDir / dxcRelToNabla); + } + #else + inline void resolveDxcInstallPathFromLoadedNabla(bool) + { + } + #endif +}; +} + +#endif diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index c560e56a0f..99c76a302c 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -18,6 +18,7 @@ add_compile_options( set(CMAKE_SYSTEM_VERSION 10.0) project(NablaSmoke CXX) +include(${CMAKE_CURRENT_LIST_DIR}/NablaSmokeTests.cmake) # default hint for our CI, normally it needs to be path to package's directory where all autogen config .cmake scripts are set(PACKAGE_CONFIG_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/build-ct/install/cmake ${PACKAGE_CONFIG_SEARCH_PATH_HINTS}) @@ -31,32 +32,73 @@ target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) +set(NBL_SMOKE_FLOW "CONFIGURE_ONLY" CACHE STRING "Smoke runtime flow: MINIMALISTIC, CONFIGURE_ONLY or BUILD_ONLY") +set_property(CACHE NBL_SMOKE_FLOW PROPERTY STRINGS MINIMALISTIC CONFIGURE_ONLY BUILD_ONLY) +string(TOUPPER "${NBL_SMOKE_FLOW}" NBL_SMOKE_FLOW) +message(STATUS "Smoke runtime flow: ${NBL_SMOKE_FLOW}") +option(NBL_SMOKE_INSTALL_SELFTEST "Install smoke with CTest metadata and run tests from install tree" ON) + +if(NBL_SMOKE_FLOW STREQUAL "MINIMALISTIC") + message(STATUS "Smoke minimalistic flow uses only package default runtime lookup") +elseif(NBL_SMOKE_FLOW STREQUAL "CONFIGURE_ONLY") + nabla_setup_runtime_modules( + TARGETS smoke + RUNTIME_MODULES_SUBDIR "Libraries" + MODE CONFIGURE_TIME + INSTALL_RULES ON + ) +elseif(NBL_SMOKE_FLOW STREQUAL "BUILD_ONLY") + nabla_setup_runtime_modules( + TARGETS smoke + RUNTIME_MODULES_SUBDIR "Libraries" + MODE BUILD_TIME + INSTALL_RULES ON + ) +else() + message(FATAL_ERROR "Invalid NBL_SMOKE_FLOW='${NBL_SMOKE_FLOW}'") +endif() + set(CMAKE_CTEST_ARGUMENTS --verbose) enable_testing() -set(OPTS +set(NBL_SMOKE_TEST_ENVIRONMENT NBL_EXPLICIT_MODULE_LOAD_LOG=1 NBL_EXPLICIT_MODULE_REQUEST_LOG=1 - NBL_INSTALL_DIRECTORY=${Nabla_ROOT} ) option(ENABLE_CRASH_HANDLER "Enable crash handler" ON) -if(WIN32) - if(ENABLE_CRASH_HANDLER) - set(CMD - powershell -NoProfile -ExecutionPolicy Bypass - -File "$" - -Exe "$" - ) - endif() -endif() +nabla_smoke_add_install_load_api_test( + TEST_NAME NBL_INSTALL_LOAD_API + EXE_PATH "$" + CRASH_HANDLER_SCRIPT "$" + ENABLE_CRASH_HANDLER ${ENABLE_CRASH_HANDLER} + ENVIRONMENT "${NBL_SMOKE_TEST_ENVIRONMENT}" +) + +if(NBL_SMOKE_INSTALL_SELFTEST) + include(GNUInstallDirs) -if(NOT ENABLE_CRASH_HANDLER) - set(CMD - "$" + install(TARGETS smoke + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + install(FILES "${CMAKE_CURRENT_LIST_DIR}/cdb.ps1" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + + set(_nbl_smoke_install_cmake_dir "${CMAKE_INSTALL_DATADIR}/nabla-smoke") + install(FILES "${CMAKE_CURRENT_LIST_DIR}/NablaSmokeTests.cmake" + DESTINATION "${_nbl_smoke_install_cmake_dir}" ) -endif() -add_test(NAME NBL_INSTALL_LOAD_API COMMAND ${CMD}) -set_tests_properties(NBL_INSTALL_LOAD_API PROPERTIES ENVIRONMENT "${OPTS}") \ No newline at end of file + set(NBL_SMOKE_INSTALL_CMAKE_DIR "${_nbl_smoke_install_cmake_dir}") + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/CTestTestfile.install.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/CTestTestfile.install.cmake" + @ONLY + ) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/CTestTestfile.install.cmake" + DESTINATION "." + RENAME "CTestTestfile.cmake" + ) +endif() diff --git a/smoke/CTestTestfile.install.cmake.in b/smoke/CTestTestfile.install.cmake.in new file mode 100644 index 0000000000..c5059ce5d8 --- /dev/null +++ b/smoke/CTestTestfile.install.cmake.in @@ -0,0 +1,10 @@ +include("@NBL_SMOKE_INSTALL_CMAKE_DIR@/NablaSmokeTests.cmake") + +nabla_smoke_add_install_load_api_test( + TEST_NAME NBL_INSTALL_LOAD_API + EXE_PATH "@CMAKE_INSTALL_BINDIR@/smoke@CMAKE_EXECUTABLE_SUFFIX@" + CRASH_HANDLER_SCRIPT "@CMAKE_INSTALL_BINDIR@/cdb.ps1" + ENABLE_CRASH_HANDLER @ENABLE_CRASH_HANDLER@ + LEGACY_CTEST_MODE + ENVIRONMENT "@NBL_SMOKE_TEST_ENVIRONMENT@" +) diff --git a/smoke/NablaSmokeTests.cmake b/smoke/NablaSmokeTests.cmake new file mode 100644 index 0000000000..9e2b796ff7 --- /dev/null +++ b/smoke/NablaSmokeTests.cmake @@ -0,0 +1,39 @@ +function(nabla_smoke_add_install_load_api_test) + set(_nbl_smoke_options LEGACY_CTEST_MODE) + set(_nbl_smoke_one_value_args TEST_NAME EXE_PATH CRASH_HANDLER_SCRIPT ENABLE_CRASH_HANDLER) + set(_nbl_smoke_multi_value_args ENVIRONMENT) + cmake_parse_arguments(_NBL_SMOKE "${_nbl_smoke_options}" "${_nbl_smoke_one_value_args}" "${_nbl_smoke_multi_value_args}" ${ARGN}) + + if(NOT _NBL_SMOKE_TEST_NAME) + message(FATAL_ERROR "nabla_smoke_add_install_load_api_test requires TEST_NAME") + endif() + if(NOT _NBL_SMOKE_EXE_PATH) + message(FATAL_ERROR "nabla_smoke_add_install_load_api_test requires EXE_PATH") + endif() + + if(WIN32 AND _NBL_SMOKE_ENABLE_CRASH_HANDLER) + if(_NBL_SMOKE_LEGACY_CTEST_MODE) + add_test("${_NBL_SMOKE_TEST_NAME}" + powershell -NoProfile -ExecutionPolicy Bypass + -File "${_NBL_SMOKE_CRASH_HANDLER_SCRIPT}" + -Exe "${_NBL_SMOKE_EXE_PATH}" + ) + else() + add_test(NAME "${_NBL_SMOKE_TEST_NAME}" COMMAND + powershell -NoProfile -ExecutionPolicy Bypass + -File "${_NBL_SMOKE_CRASH_HANDLER_SCRIPT}" + -Exe "${_NBL_SMOKE_EXE_PATH}" + ) + endif() + else() + if(_NBL_SMOKE_LEGACY_CTEST_MODE) + add_test("${_NBL_SMOKE_TEST_NAME}" "${_NBL_SMOKE_EXE_PATH}") + else() + add_test(NAME "${_NBL_SMOKE_TEST_NAME}" COMMAND "${_NBL_SMOKE_EXE_PATH}") + endif() + endif() + + if(_NBL_SMOKE_ENVIRONMENT) + set_tests_properties("${_NBL_SMOKE_TEST_NAME}" PROPERTIES ENVIRONMENT "${_NBL_SMOKE_ENVIRONMENT}") + endif() +endfunction() diff --git a/smoke/RunSmokeFlow.cmake b/smoke/RunSmokeFlow.cmake new file mode 100644 index 0000000000..9350e6094e --- /dev/null +++ b/smoke/RunSmokeFlow.cmake @@ -0,0 +1,95 @@ +if(NOT DEFINED FLOW) + message(FATAL_ERROR "FLOW is required. Allowed values: MINIMALISTIC, CONFIGURE_ONLY, BUILD_ONLY") +endif() + +string(TOUPPER "${FLOW}" FLOW) +if(NOT FLOW MATCHES "^(MINIMALISTIC|CONFIGURE_ONLY|BUILD_ONLY)$") + message(FATAL_ERROR "Invalid FLOW='${FLOW}'. Allowed values: MINIMALISTIC, CONFIGURE_ONLY, BUILD_ONLY") +endif() + +if(NOT DEFINED CONFIG) + message(FATAL_ERROR "CONFIG is required (e.g. Debug, Release, RelWithDebInfo)") +endif() + +if(NOT DEFINED SMOKE_SOURCE_DIR) + set(SMOKE_SOURCE_DIR "smoke") +endif() + +if(NOT DEFINED BUILD_DIR) + set(BUILD_DIR "smoke/out") +endif() + +if(NOT DEFINED INSTALL_DIR) + set(INSTALL_DIR "${BUILD_DIR}/install") +endif() + +if(NOT DEFINED CTEST_BIN) + if(DEFINED CMAKE_CTEST_COMMAND) + set(CTEST_BIN "${CMAKE_CTEST_COMMAND}") + else() + find_program(CTEST_BIN ctest REQUIRED) + endif() +endif() + +function(run_cmd) + execute_process( + COMMAND ${ARGV} + COMMAND_ECHO STDOUT + RESULT_VARIABLE _rc + ) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "Command failed with exit code ${_rc}") + endif() +endfunction() + +file(REMOVE_RECURSE "${BUILD_DIR}") + +set(_run_install_selftest ON) +if(FLOW STREQUAL "MINIMALISTIC") + set(_run_install_selftest OFF) +endif() + +run_cmd( + "${CMAKE_COMMAND}" + -S "${SMOKE_SOURCE_DIR}" + -B "${BUILD_DIR}" + -D "NBL_SMOKE_FLOW=${FLOW}" + -D "NBL_SMOKE_INSTALL_SELFTEST=${_run_install_selftest}" +) + +run_cmd( + "${CMAKE_COMMAND}" + --build "${BUILD_DIR}" + --config "${CONFIG}" +) + +run_cmd( + "${CTEST_BIN}" + --verbose + --test-dir "${BUILD_DIR}" + --force-new-ctest-process + --output-on-failure + --no-tests=error + -C "${CONFIG}" +) + +if(_run_install_selftest) + file(REMOVE_RECURSE "${INSTALL_DIR}") + + run_cmd( + "${CMAKE_COMMAND}" + --install "${BUILD_DIR}" + --config "${CONFIG}" + --prefix "${INSTALL_DIR}" + ) + + run_cmd( + "${CTEST_BIN}" + --verbose + --test-dir "${INSTALL_DIR}" + --force-new-ctest-process + --output-on-failure + --no-tests=error + -C "${CONFIG}" + ) +endif() diff --git a/smoke/main.cpp b/smoke/main.cpp index 530b29adae..9510081f21 100644 --- a/smoke/main.cpp +++ b/smoke/main.cpp @@ -18,16 +18,6 @@ class Smoke final : public system::IApplicationFramework bool onAppInitialized(smart_refctd_ptr&& system) override { - const char* sdk = std::getenv("NBL_INSTALL_DIRECTORY"); - - if (sdk) - { - auto dir = std::filesystem::absolute(std::filesystem::path(sdk).make_preferred()).string(); - std::cout << "[INFO]: NBL_INSTALL_DIRECTORY = \"" << dir.c_str() << "\"\n"; - } - else - std::cerr << "[INFO]: NBL_INSTALL_DIRECTORY env was not defined!\n"; - if (isAPILoaded()) { std::cout << "[INFO]: Loaded Nabla API\n"; @@ -123,4 +113,4 @@ class Smoke final : public system::IApplicationFramework NBL_MAIN_FUNC(Smoke) #else int main() { return 0; } -#endif \ No newline at end of file +#endif diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 39d74994da..18a25c8619 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -866,10 +866,33 @@ nbl_install_dir_spec(../../include/nbl/application_templates nbl) # note: order important, keep after install rules due to NBL_3RDPARTY_DXC_NS_PACKAGE_RUNTIME_DLL_DIR_PATH property get_property(_NBL_DXC_PACKAGE_RUNTIME_DLL_DIR_PATH_ GLOBAL PROPERTY NBL_3RDPARTY_DXC_NS_PACKAGE_RUNTIME_DLL_DIR_PATH) get_target_property(_NBL_NABLA_PACKAGE_RUNTIME_DLL_DIR_PATH_ Nabla NBL_PACKAGE_RUNTIME_DLL_DIR_PATH) +set(_NBL_CONSUMER_BIN_DIR_GE_ + "$,$>>" +) +set(_NBL_NABLA_RUNTIME_DLL_DIR_GE_ + "$>" +) +set(_NBL_DXC_RUNTIME_DLL_DIR_GE_ + "$,3rdparty,dxc>>" +) +set(_NBL_NABLA_RUNTIME_SAME_ROOT_AS_CONSUMER_GE_ + "$,$>" +) +set(_NBL_DXC_RUNTIME_SAME_ROOT_AS_CONSUMER_GE_ + "$,$>" +) +set(_NBL_NABLA_RUNTIME_DLL_DIR_PATH_REL_TO_CONSUMER_EXE_GE_ + "$,>" +) +set(_NBL_DXC_RUNTIME_DLL_DIR_PATH_REL_TO_CONSUMER_EXE_GE_ + "$,>" +) target_compile_definitions(Nabla INTERFACE NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY="${_NBL_NABLA_PACKAGE_RUNTIME_DLL_DIR_PATH_}" INTERFACE NBL_CPACK_PACKAGE_DXC_DLL_DIR_ABS_KEY="${_NBL_DXC_PACKAGE_RUNTIME_DLL_DIR_PATH_}" + INTERFACE "$" + INTERFACE "$" ) NBL_ADJUST_FOLDERS(src) diff --git a/src/nbl/video/CVulkanPhysicalDevice.cpp b/src/nbl/video/CVulkanPhysicalDevice.cpp index da86d7c9d9..f3eeea9d41 100644 --- a/src/nbl/video/CVulkanPhysicalDevice.cpp +++ b/src/nbl/video/CVulkanPhysicalDevice.cpp @@ -462,8 +462,8 @@ std::unique_ptr CVulkanPhysicalDevice::create(core::smart properties.limits.supportedDepthResolveModes = static_cast(vulkan12Properties.supportedDepthResolveModes); properties.limits.supportedStencilResolveModes = static_cast(vulkan12Properties.supportedStencilResolveModes); - if (!vulkan12Properties.independentResolve || !vulkan12Properties.independentResolveNone) - RETURN_NULL_PHYSICAL_DEVICE; + //if (!vulkan12Properties.independentResolve || !vulkan12Properties.independentResolveNone) + // RETURN_NULL_PHYSICAL_DEVICE; // not dealing with vulkan12Properties.filterMinmaxSingleComponentFormats, TODO report in usage properties.limits.filterMinmaxImageComponentMapping = vulkan12Properties.filterMinmaxImageComponentMapping; @@ -882,8 +882,8 @@ std::unique_ptr CVulkanPhysicalDevice::create(core::smart // TODO sparse stuff properties.limits.variableMultisampleRate = deviceFeatures.features.variableMultisampleRate; - if (!deviceFeatures.features.inheritedQueries) - RETURN_NULL_PHYSICAL_DEVICE; + //if (!deviceFeatures.features.inheritedQueries) + // RETURN_NULL_PHYSICAL_DEVICE; /* Vulkan 1.1 Core */ @@ -1892,6 +1892,10 @@ core::smart_refctd_ptr CVulkanPhysicalDevice::createLogicalDevic extensionStrings[i++] = feature.c_str(); } + // remove inheritedQueries since llvmpipe doesn't support it + vk_deviceFeatures2.features.inheritedQueries = VK_FALSE; + //vk_deviceFeatures2.features = VkPhysicalDeviceFeatures{}; + // Create Device VkDeviceCreateInfo vk_createInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; vk_createInfo.pNext = &vk_deviceFeatures2; @@ -1913,7 +1917,32 @@ core::smart_refctd_ptr CVulkanPhysicalDevice::createLogicalDevic } vk_createInfo.queueCreateInfoCount = queueCreateInfos.size(); vk_createInfo.pQueueCreateInfos = queueCreateInfos.data(); - + + // erase extensions not supported by llvm + { + core::vector llvmUnsupportedExtensions = { + "VK_NV_shader_image_footprint", + "VK_KHR_fragment_shader_barycentric", + "VK_EXT_conservative_rasterization", + "VK_KHR_external_memory_win32", + "VK_KHR_shader_subgroup_uniform_control_flow", + "VK_EXT_pci_bus_info", + "VK_KHR_external_fence_win32", + "VK_KHR_external_semaphore_win32", + "VK_KHR_win32_keyed_mutex", + "VK_EXT_discard_rectangles" + "VK_NV_compute_shader_derivatives", + "VK_NV_shader_sm_builtins" + }; + + for (const char* extension : llvmUnsupportedExtensions) + { + auto it = std::find_if(extensionStrings.begin(), extensionStrings.end(), [extension](const char* s) { return std::strcmp(s, extension) == 0; }); + if(it != extensionStrings.end()) + extensionStrings.erase(it); + } + } + vk_createInfo.enabledExtensionCount = static_cast(extensionStrings.size()); vk_createInfo.ppEnabledExtensionNames = extensionStrings.data(); diff --git a/src/nbl/video/device_capabilities/device_limits.json b/src/nbl/video/device_capabilities/device_limits.json index e8bc3a3af4..a913f33d25 100644 --- a/src/nbl/video/device_capabilities/device_limits.json +++ b/src/nbl/video/device_capabilities/device_limits.json @@ -11,7 +11,7 @@ { "type": "uint32_t", "name": "MinMaxSSBOSize", - "value": "(0x1u << 30u) - 4" + "value": "134217728" }, { "type": "uint16_t", @@ -360,7 +360,7 @@ { "type": "uint32_t", "name": "maxFragmentCombinedOutputResources", - "value": 127 + "value": 104 } ] }, @@ -808,7 +808,7 @@ { "type": "core::bitflag", "name": "subgroupOpsShaderStages", - "value": "asset::IShader::E_SHADER_STAGE::ESS_COMPUTE | asset::IShader::E_SHADER_STAGE::ESS_ALL_GRAPHICS" + "value": "asset::IShader::E_SHADER_STAGE::ESS_UNKNOWN" }, { "type": "bool", @@ -1110,7 +1110,7 @@ { "type": "core::bitflag", "name": "supportedDepthResolveModes", - "value": "RESOLVE_MODE_FLAGS::SAMPLE_ZERO_BIT | RESOLVE_MODE_FLAGS::MIN_BIT | RESOLVE_MODE_FLAGS::MAX_BIT" + "value": "RESOLVE_MODE_FLAGS::SAMPLE_ZERO_BIT" }, { "type": "core::bitflag",