From 20e61bd8a68189f01b1e38367b37d21512e9c5ea Mon Sep 17 00:00:00 2001 From: Przemog1 Date: Wed, 26 Nov 2025 23:10:10 +0100 Subject: [PATCH 01/14] Lifted some device restrictions --- src/nbl/video/CVulkanPhysicalDevice.cpp | 8 ++++---- src/nbl/video/device_capabilities/device_limits.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nbl/video/CVulkanPhysicalDevice.cpp b/src/nbl/video/CVulkanPhysicalDevice.cpp index da86d7c9d9..d27cc07079 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 */ 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", From 5db92d301cd284069ca00b4b81462ed8767b82a8 Mon Sep 17 00:00:00 2001 From: Przemog1 Date: Thu, 27 Nov 2025 14:08:26 +0100 Subject: [PATCH 02/14] Disabled extensions not supported by llvmpipe --- src/nbl/video/CVulkanPhysicalDevice.cpp | 31 ++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/nbl/video/CVulkanPhysicalDevice.cpp b/src/nbl/video/CVulkanPhysicalDevice.cpp index d27cc07079..f3eeea9d41 100644 --- a/src/nbl/video/CVulkanPhysicalDevice.cpp +++ b/src/nbl/video/CVulkanPhysicalDevice.cpp @@ -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(); From 4466eddace8eccb51468742ced756f8e37b45519 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 04:56:20 +0100 Subject: [PATCH 03/14] Guard install lookup paths with package defines --- include/nbl/system/IApplicationFramework.h | 172 ++++++++++++++------- 1 file changed, 112 insertions(+), 60 deletions(-) diff --git a/include/nbl/system/IApplicationFramework.h b/include/nbl/system/IApplicationFramework.h index f17859ebb2..bb4c99e4ed 100644 --- a/include/nbl/system/IApplicationFramework.h +++ b/include/nbl/system/IApplicationFramework.h @@ -25,87 +25,139 @@ 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) + struct Interface { - if (!value || (value[0] == '\0')) - return system::path(""); - - const auto candidate = system::path(value); - if (std::filesystem::exists(candidate)) - return candidate; - - return system::path(""); + system::path install; + system::path build; }; - auto readEnvFlag = [](const char* key) + struct Module { - 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"); + Interface paths; + std::string_view name; }; - 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 = !readEnvFlag("NBL_RUN_FROM_BUILD_INTERFACE"); - #endif // NBL_RELOCATABLE_PACKAGE - - constexpr struct - { - std::string_view nabla, dxc; - } module = - { + Module nabla{ + {}, #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 + Module dxc{ + {}, + "dxcompiler" + }; #ifdef _NBL_SHARED_BUILD_ #if defined(_NABLA_OUTPUT_DIR_) - build.nabla = _NABLA_OUTPUT_DIR_; + nabla.paths.build = _NABLA_OUTPUT_DIR_; #endif #endif #if defined(_DXC_DLL_) - build.dxc = path(_DXC_DLL_).parent_path(); + dxc.paths.build = 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; + // There must be no mix between interfaces' lookup, we detect our packate layout + // to determine whether its install prefix or host build tree execution + + #ifdef NBL_RELOCATABLE_PACKAGE + const bool useInstallLookups = true; + #else + auto getExecutableDirectory = []() -> system::path + { + #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 + }; + const auto executableDirectory = getExecutableDirectory(); + #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR) + const auto nablaRelDir = system::path(NBL_CPACK_PACKAGE_NABLA_DLL_DIR); + nabla.paths.install = std::filesystem::absolute(executableDirectory / nablaRelDir); #endif + #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR) + const auto dxcRelDir = system::path(NBL_CPACK_PACKAGE_DXC_DLL_DIR); + dxc.paths.install = std::filesystem::absolute(executableDirectory / dxcRelDir); + #endif + + const auto detectPackageLayout = [&nabla, &dxc]() + { + auto moduleExistsInDir = [](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); + 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; + }; + + const bool hasPackageDxc = moduleExistsInDir(dxc.paths.install, dxc.name); + #ifdef _NBL_SHARED_BUILD_ + const bool hasPackageNabla = moduleExistsInDir(nabla.paths.install, nabla.name); + return hasPackageDxc && hasPackageNabla; + #else + return hasPackageDxc; + #endif + }; - #ifdef NBL_CPACK_PACKAGE_DXC_DLL_DIR - rel.dxc = NBL_CPACK_PACKAGE_DXC_DLL_DIR; + const bool useInstallLookups = detectPackageLayout(); #endif using RV = const std::vector; @@ -132,11 +184,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(dxc.name, useInstallLookups ? RV{ dxc.paths.install } : RV{ 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(nabla.name, useInstallLookups ? RV{ nabla.paths.install } : RV{ nabla.paths.build })) return false; #endif From 1c456efcb44b22bc75d3017cb0c302c298ca6c73 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 05:39:41 +0100 Subject: [PATCH 04/14] Remove NSC build interface env wrapper --- cmake/common.cmake | 1 - 1 file changed, 1 deletion(-) 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} From b64184a14d18cff4bc4a8d9f4297affb2251ca30 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 13:44:10 +0100 Subject: [PATCH 05/14] Refactor runtime module lookup and smoke override tests --- include/nbl/system/CSystemWin32.h | 15 +- include/nbl/system/IApplicationFramework.h | 169 +++-------------- include/nbl/system/ModuleLookupUtils.h | 114 ++++++++++++ include/nbl/system/RuntimeModuleLookup.h | 201 +++++++++++++++++++++ smoke/CMakeLists.txt | 33 +++- smoke/main.cpp | 12 +- smoke/run_override_test.cmake | 108 +++++++++++ 7 files changed, 491 insertions(+), 161 deletions(-) create mode 100644 include/nbl/system/ModuleLookupUtils.h create mode 100644 include/nbl/system/RuntimeModuleLookup.h create mode 100644 smoke/run_override_test.cmake 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 bb4c99e4ed..82da25cfb5 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,143 +26,33 @@ class IApplicationFramework : public core::IReferenceCounted // this is safe to call multiple times static bool GlobalsInit() { - struct Interface - { - system::path install; - system::path build; - }; - - struct Module - { - Interface paths; - std::string_view name; - }; - - Module nabla{ - {}, - #ifdef _NBL_SHARED_BUILD_ - _NABLA_DLL_NAME_ - #else - "" - #endif - }; - - Module dxc{ - {}, - "dxcompiler" - }; - - #ifdef _NBL_SHARED_BUILD_ - #if defined(_NABLA_OUTPUT_DIR_) - nabla.paths.build = _NABLA_OUTPUT_DIR_; - #endif - #endif - #if defined(_DXC_DLL_) - dxc.paths.build = path(_DXC_DLL_).parent_path(); - #endif - - // There must be no mix between interfaces' lookup, we detect our packate layout - // to determine whether its install prefix or host build tree execution - - #ifdef NBL_RELOCATABLE_PACKAGE - const bool useInstallLookups = true; - #else - auto getExecutableDirectory = []() -> system::path - { - #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 - }; - const auto executableDirectory = getExecutableDirectory(); - #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR) - const auto nablaRelDir = system::path(NBL_CPACK_PACKAGE_NABLA_DLL_DIR); - nabla.paths.install = std::filesystem::absolute(executableDirectory / nablaRelDir); - #endif - #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR) - const auto dxcRelDir = system::path(NBL_CPACK_PACKAGE_DXC_DLL_DIR); - dxc.paths.install = std::filesystem::absolute(executableDirectory / dxcRelDir); - #endif - - const auto detectPackageLayout = [&nabla, &dxc]() - { - auto moduleExistsInDir = [](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); - 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; - }; - - const bool hasPackageDxc = moduleExistsInDir(dxc.paths.install, dxc.name); - #ifdef _NBL_SHARED_BUILD_ - const bool hasPackageNabla = moduleExistsInDir(nabla.paths.install, nabla.name); - return hasPackageDxc && hasPackageNabla; - #else - return hasPackageDxc; - #endif - }; - - const bool useInstallLookups = detectPackageLayout(); - #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 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()); @@ -184,11 +75,11 @@ class IApplicationFramework : public core::IReferenceCounted return true; }; - if (not load(dxc.name, useInstallLookups ? RV{ dxc.paths.install } : RV{ dxc.paths.build })) + 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(nabla.name, useInstallLookups ? RV{ nabla.paths.install } : RV{ nabla.paths.build })) + 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..8268cd7e55 --- /dev/null +++ b/include/nbl/system/RuntimeModuleLookup.h @@ -0,0 +1,201 @@ +#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 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_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 (!hasCompleteInstallOverride()) + tryResolveInstallPathsFromPackageLayout(exeDirectory); + return true; + } + return hasUsableInstallPaths() || tryResolveInstallPathsFromPackageLayout(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(""); + return std::filesystem::absolute(exeDirectory / system::path(relativePath)); + } + + 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 hasCompleteInstallOverride() const + { + return sharedBuild ? (hasInstallOverride(nabla) && hasInstallOverride(dxc)) : hasInstallOverride(dxc); + } + + #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..34ed41965f 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -31,13 +31,26 @@ target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) +add_executable(smoke_override main.cpp pch.hpp cdb.ps1) +target_link_libraries(smoke_override PRIVATE Nabla::Nabla) +target_compile_definitions(smoke_override PRIVATE + _AFXDLL + "NBL_CPACK_PACKAGE_NABLA_DLL_DIR=\"./Libraries\"" + "NBL_CPACK_PACKAGE_DXC_DLL_DIR=\"./Libraries\"" +) +target_precompile_headers(smoke_override PRIVATE pch.hpp) +set_target_properties(smoke smoke_override PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${Nabla_ROOT}/debug/bin" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${Nabla_ROOT}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${Nabla_ROOT}/relwithdebinfo/bin" +) + set(CMAKE_CTEST_ARGUMENTS --verbose) enable_testing() set(OPTS 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) @@ -59,4 +72,20 @@ if(NOT ENABLE_CRASH_HANDLER) 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_tests_properties(NBL_INSTALL_LOAD_API PROPERTIES ENVIRONMENT "${OPTS}") + +set(_SMOKE_CPACK_REL_ENTRY "./$<$,Release>>:$>>") +set(_SMOKE_NABLA_RUNTIME_DIR "${Nabla_ROOT}/${_SMOKE_CPACK_REL_ENTRY}/runtime/nbl") +set(_SMOKE_DXC_RUNTIME_DIR "${_SMOKE_NABLA_RUNTIME_DIR}/3rdparty/dxc") +set(_SMOKE_OVERRIDE_LIB_DIR "$/Libraries") + +add_test(NAME NBL_INSTALL_LOAD_API_OVERRIDE + COMMAND "${CMAKE_COMMAND}" + "-DSMOKE_EXE=$" + "-DNABLA_RUNTIME_DIR=${_SMOKE_NABLA_RUNTIME_DIR}" + "-DDXC_RUNTIME_DIR=${_SMOKE_DXC_RUNTIME_DIR}" + "-DDXC_MODULE_BASENAME=dxcompiler" + "-DOVERRIDE_DIR=${_SMOKE_OVERRIDE_LIB_DIR}" + "-P" "$" +) +set_tests_properties(NBL_INSTALL_LOAD_API_OVERRIDE PROPERTIES ENVIRONMENT "${OPTS}") 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/smoke/run_override_test.cmake b/smoke/run_override_test.cmake new file mode 100644 index 0000000000..afd87de085 --- /dev/null +++ b/smoke/run_override_test.cmake @@ -0,0 +1,108 @@ +cmake_minimum_required(VERSION 3.20) + +if(NOT DEFINED SMOKE_EXE) + message(FATAL_ERROR "SMOKE_EXE is required") +endif() +if(NOT DEFINED OVERRIDE_DIR) + message(FATAL_ERROR "OVERRIDE_DIR is required") +endif() +if(NOT EXISTS "${SMOKE_EXE}") + message(FATAL_ERROR "SMOKE_EXE not found: ${SMOKE_EXE}") +endif() + +function(find_module_file OUT_VAR DIR_PATH MODULE_BASENAME REQUIRED_FLAG) + if(NOT EXISTS "${DIR_PATH}") + if(REQUIRED_FLAG) + message(FATAL_ERROR "Runtime directory not found: ${DIR_PATH}") + endif() + set(${OUT_VAR} "" PARENT_SCOPE) + return() + endif() + + set(_candidates "${MODULE_BASENAME}") + if(WIN32) + list(APPEND _candidates "${MODULE_BASENAME}.dll") + elseif(APPLE) + list(APPEND _candidates "${MODULE_BASENAME}.dylib" "lib${MODULE_BASENAME}.dylib") + else() + list(APPEND _candidates "${MODULE_BASENAME}.so" "lib${MODULE_BASENAME}.so") + endif() + + foreach(_name IN LISTS _candidates) + set(_candidate_path "${DIR_PATH}/${_name}") + if(EXISTS "${_candidate_path}" AND NOT IS_DIRECTORY "${_candidate_path}") + set(${OUT_VAR} "${_candidate_path}" PARENT_SCOPE) + return() + endif() + endforeach() + + if(NOT WIN32 AND NOT APPLE) + file(GLOB _versioned_candidates + "${DIR_PATH}/${MODULE_BASENAME}.so.*" + "${DIR_PATH}/lib${MODULE_BASENAME}.so.*" + ) + list(LENGTH _versioned_candidates _versioned_count) + if(_versioned_count GREATER 0) + list(GET _versioned_candidates 0 _versioned_first) + set(${OUT_VAR} "${_versioned_first}" PARENT_SCOPE) + return() + endif() + endif() + + if(REQUIRED_FLAG) + message(FATAL_ERROR "Could not find module ${MODULE_BASENAME} in ${DIR_PATH}") + endif() + + set(${OUT_VAR} "" PARENT_SCOPE) +endfunction() + +file(MAKE_DIRECTORY "${OVERRIDE_DIR}") + +if(DEFINED DXC_RUNTIME_DIR AND DEFINED DXC_MODULE_BASENAME AND NOT "${DXC_MODULE_BASENAME}" STREQUAL "") + find_module_file(_dxc_module_file "${DXC_RUNTIME_DIR}" "${DXC_MODULE_BASENAME}" TRUE) +else() + message(FATAL_ERROR "DXC_RUNTIME_DIR and DXC_MODULE_BASENAME are required") +endif() + +if(DEFINED NABLA_RUNTIME_DIR AND EXISTS "${NABLA_RUNTIME_DIR}") + if(DEFINED NABLA_MODULE_BASENAME AND NOT "${NABLA_MODULE_BASENAME}" STREQUAL "") + find_module_file(_nabla_module_file "${NABLA_RUNTIME_DIR}" "${NABLA_MODULE_BASENAME}" FALSE) + set(_nabla_modules "${_nabla_module_file}") + else() + if(WIN32) + file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.dll") + elseif(APPLE) + file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.dylib" "${NABLA_RUNTIME_DIR}/libNabla*.dylib") + else() + file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.so" "${NABLA_RUNTIME_DIR}/Nabla*.so.*" "${NABLA_RUNTIME_DIR}/libNabla*.so" "${NABLA_RUNTIME_DIR}/libNabla*.so.*") + endif() + endif() + + foreach(_nabla_module IN LISTS _nabla_modules) + if(NOT "${_nabla_module}" STREQUAL "") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_nabla_module}" "${OVERRIDE_DIR}" + RESULT_VARIABLE _copy_nabla_rv + ) + if(NOT _copy_nabla_rv EQUAL 0) + message(FATAL_ERROR "Failed to copy Nabla module from ${_nabla_module} to ${OVERRIDE_DIR}") + endif() + endif() + endforeach() +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_dxc_module_file}" "${OVERRIDE_DIR}" + RESULT_VARIABLE _copy_dxc_rv +) +if(NOT _copy_dxc_rv EQUAL 0) + message(FATAL_ERROR "Failed to copy DXC module from ${_dxc_module_file} to ${OVERRIDE_DIR}") +endif() + +execute_process( + COMMAND "${SMOKE_EXE}" + RESULT_VARIABLE _smoke_rv +) +if(NOT _smoke_rv EQUAL 0) + message(FATAL_ERROR "smoke_override failed with exit code ${_smoke_rv}") +endif() From 0f8b6c1d4294c9cf293105f8091eb0e85654d433 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 14:05:22 +0100 Subject: [PATCH 06/14] Remove smoke override test flow --- smoke/CMakeLists.txt | 27 +-------- smoke/run_override_test.cmake | 108 ---------------------------------- 2 files changed, 1 insertion(+), 134 deletions(-) delete mode 100644 smoke/run_override_test.cmake diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 34ed41965f..2cd3c28d00 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -30,16 +30,7 @@ add_executable(smoke main.cpp pch.hpp cdb.ps1) target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) - -add_executable(smoke_override main.cpp pch.hpp cdb.ps1) -target_link_libraries(smoke_override PRIVATE Nabla::Nabla) -target_compile_definitions(smoke_override PRIVATE - _AFXDLL - "NBL_CPACK_PACKAGE_NABLA_DLL_DIR=\"./Libraries\"" - "NBL_CPACK_PACKAGE_DXC_DLL_DIR=\"./Libraries\"" -) -target_precompile_headers(smoke_override PRIVATE pch.hpp) -set_target_properties(smoke smoke_override PROPERTIES +set_target_properties(smoke PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${Nabla_ROOT}/debug/bin" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${Nabla_ROOT}/bin" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${Nabla_ROOT}/relwithdebinfo/bin" @@ -73,19 +64,3 @@ endif() add_test(NAME NBL_INSTALL_LOAD_API COMMAND ${CMD}) set_tests_properties(NBL_INSTALL_LOAD_API PROPERTIES ENVIRONMENT "${OPTS}") - -set(_SMOKE_CPACK_REL_ENTRY "./$<$,Release>>:$>>") -set(_SMOKE_NABLA_RUNTIME_DIR "${Nabla_ROOT}/${_SMOKE_CPACK_REL_ENTRY}/runtime/nbl") -set(_SMOKE_DXC_RUNTIME_DIR "${_SMOKE_NABLA_RUNTIME_DIR}/3rdparty/dxc") -set(_SMOKE_OVERRIDE_LIB_DIR "$/Libraries") - -add_test(NAME NBL_INSTALL_LOAD_API_OVERRIDE - COMMAND "${CMAKE_COMMAND}" - "-DSMOKE_EXE=$" - "-DNABLA_RUNTIME_DIR=${_SMOKE_NABLA_RUNTIME_DIR}" - "-DDXC_RUNTIME_DIR=${_SMOKE_DXC_RUNTIME_DIR}" - "-DDXC_MODULE_BASENAME=dxcompiler" - "-DOVERRIDE_DIR=${_SMOKE_OVERRIDE_LIB_DIR}" - "-P" "$" -) -set_tests_properties(NBL_INSTALL_LOAD_API_OVERRIDE PROPERTIES ENVIRONMENT "${OPTS}") diff --git a/smoke/run_override_test.cmake b/smoke/run_override_test.cmake deleted file mode 100644 index afd87de085..0000000000 --- a/smoke/run_override_test.cmake +++ /dev/null @@ -1,108 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -if(NOT DEFINED SMOKE_EXE) - message(FATAL_ERROR "SMOKE_EXE is required") -endif() -if(NOT DEFINED OVERRIDE_DIR) - message(FATAL_ERROR "OVERRIDE_DIR is required") -endif() -if(NOT EXISTS "${SMOKE_EXE}") - message(FATAL_ERROR "SMOKE_EXE not found: ${SMOKE_EXE}") -endif() - -function(find_module_file OUT_VAR DIR_PATH MODULE_BASENAME REQUIRED_FLAG) - if(NOT EXISTS "${DIR_PATH}") - if(REQUIRED_FLAG) - message(FATAL_ERROR "Runtime directory not found: ${DIR_PATH}") - endif() - set(${OUT_VAR} "" PARENT_SCOPE) - return() - endif() - - set(_candidates "${MODULE_BASENAME}") - if(WIN32) - list(APPEND _candidates "${MODULE_BASENAME}.dll") - elseif(APPLE) - list(APPEND _candidates "${MODULE_BASENAME}.dylib" "lib${MODULE_BASENAME}.dylib") - else() - list(APPEND _candidates "${MODULE_BASENAME}.so" "lib${MODULE_BASENAME}.so") - endif() - - foreach(_name IN LISTS _candidates) - set(_candidate_path "${DIR_PATH}/${_name}") - if(EXISTS "${_candidate_path}" AND NOT IS_DIRECTORY "${_candidate_path}") - set(${OUT_VAR} "${_candidate_path}" PARENT_SCOPE) - return() - endif() - endforeach() - - if(NOT WIN32 AND NOT APPLE) - file(GLOB _versioned_candidates - "${DIR_PATH}/${MODULE_BASENAME}.so.*" - "${DIR_PATH}/lib${MODULE_BASENAME}.so.*" - ) - list(LENGTH _versioned_candidates _versioned_count) - if(_versioned_count GREATER 0) - list(GET _versioned_candidates 0 _versioned_first) - set(${OUT_VAR} "${_versioned_first}" PARENT_SCOPE) - return() - endif() - endif() - - if(REQUIRED_FLAG) - message(FATAL_ERROR "Could not find module ${MODULE_BASENAME} in ${DIR_PATH}") - endif() - - set(${OUT_VAR} "" PARENT_SCOPE) -endfunction() - -file(MAKE_DIRECTORY "${OVERRIDE_DIR}") - -if(DEFINED DXC_RUNTIME_DIR AND DEFINED DXC_MODULE_BASENAME AND NOT "${DXC_MODULE_BASENAME}" STREQUAL "") - find_module_file(_dxc_module_file "${DXC_RUNTIME_DIR}" "${DXC_MODULE_BASENAME}" TRUE) -else() - message(FATAL_ERROR "DXC_RUNTIME_DIR and DXC_MODULE_BASENAME are required") -endif() - -if(DEFINED NABLA_RUNTIME_DIR AND EXISTS "${NABLA_RUNTIME_DIR}") - if(DEFINED NABLA_MODULE_BASENAME AND NOT "${NABLA_MODULE_BASENAME}" STREQUAL "") - find_module_file(_nabla_module_file "${NABLA_RUNTIME_DIR}" "${NABLA_MODULE_BASENAME}" FALSE) - set(_nabla_modules "${_nabla_module_file}") - else() - if(WIN32) - file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.dll") - elseif(APPLE) - file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.dylib" "${NABLA_RUNTIME_DIR}/libNabla*.dylib") - else() - file(GLOB _nabla_modules "${NABLA_RUNTIME_DIR}/Nabla*.so" "${NABLA_RUNTIME_DIR}/Nabla*.so.*" "${NABLA_RUNTIME_DIR}/libNabla*.so" "${NABLA_RUNTIME_DIR}/libNabla*.so.*") - endif() - endif() - - foreach(_nabla_module IN LISTS _nabla_modules) - if(NOT "${_nabla_module}" STREQUAL "") - execute_process( - COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_nabla_module}" "${OVERRIDE_DIR}" - RESULT_VARIABLE _copy_nabla_rv - ) - if(NOT _copy_nabla_rv EQUAL 0) - message(FATAL_ERROR "Failed to copy Nabla module from ${_nabla_module} to ${OVERRIDE_DIR}") - endif() - endif() - endforeach() -endif() - -execute_process( - COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_dxc_module_file}" "${OVERRIDE_DIR}" - RESULT_VARIABLE _copy_dxc_rv -) -if(NOT _copy_dxc_rv EQUAL 0) - message(FATAL_ERROR "Failed to copy DXC module from ${_dxc_module_file} to ${OVERRIDE_DIR}") -endif() - -execute_process( - COMMAND "${SMOKE_EXE}" - RESULT_VARIABLE _smoke_rv -) -if(NOT _smoke_rv EQUAL 0) - message(FATAL_ERROR "smoke_override failed with exit code ${_smoke_rv}") -endif() From 35f348bb7cb7b75cb0abc221769f71b8f79b107e Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 16:15:54 +0100 Subject: [PATCH 07/14] Add one call runtime lookup flow for package consumers --- .github/workflows/build-nabla.yml | 9 +++++ cmake/NablaConfig.cmake.in | 49 +++++++++++++++++++++++- include/nbl/system/RuntimeModuleLookup.h | 4 ++ smoke/CMakeLists.txt | 17 +++++--- src/nbl/CMakeLists.txt | 7 ++++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index 704040514e..1e2262a889 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -393,3 +393,12 @@ jobs: - name: CTest Smoke run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} + + - name: Configure Smoke Custom Lookup + run: cmake -S smoke -B smoke/out -D NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON + + - name: Build Smoke Custom Lookup + run: cmake --build smoke/out --config ${{ matrix.config }} + + - name: CTest Smoke Custom Lookup + run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index 44b2a1abcb..6302c1fa16 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -15,6 +15,53 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/NablaExportTargets.cmake") check_required_components(Nabla) +# Config mapping note: +# Runtime copy sources are resolved from $. +# CMake applies imported-config mapping (MAP_IMPORTED_CONFIG_*) to this expression. +# If a consumer overrides mapping on Nabla::Nabla, do it before this call. +function(nabla_enable_custom_install_lookup _TARGET) + if(NOT TARGET "${_TARGET}") + message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") + endif() + + set(_nbl_runtime_subdir "your_custom_package") + set(_nbl_libraries_subdir "Libraries") + + set(_nbl_options "") + set(_nbl_one_value_args RUNTIME_SUBDIR LIBRARIES_SUBDIR) + set(_nbl_multi_value_args "") + cmake_parse_arguments(_NBL_CUSTOM "${_nbl_options}" "${_nbl_one_value_args}" "${_nbl_multi_value_args}" ${ARGN}) + + if(_NBL_CUSTOM_RUNTIME_SUBDIR) + set(_nbl_runtime_subdir "${_NBL_CUSTOM_RUNTIME_SUBDIR}") + endif() + if(_NBL_CUSTOM_LIBRARIES_SUBDIR) + set(_nbl_libraries_subdir "${_NBL_CUSTOM_LIBRARIES_SUBDIR}") + endif() + + get_property(_nbl_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(_nbl_is_multi_config) + foreach(_nbl_cfg IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER "${_nbl_cfg}" _nbl_cfg_upper) + set_property(TARGET "${_TARGET}" PROPERTY "RUNTIME_OUTPUT_DIRECTORY_${_nbl_cfg_upper}" "${CMAKE_CURRENT_BINARY_DIR}/${_nbl_cfg}/${_nbl_runtime_subdir}") + endforeach() + else() + set_target_properties("${_TARGET}" PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${_nbl_runtime_subdir}") + endif() + + target_compile_definitions("${_TARGET}" PRIVATE + NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./${_nbl_libraries_subdir}" + NBL_CPACK_PACKAGE_DXC_DLL_DIR="./${_nbl_libraries_subdir}" + ) + + add_custom_command(TARGET "${_TARGET}" POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/${_nbl_libraries_subdir}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$/${_nbl_libraries_subdir}/" + COMMAND ${CMAKE_COMMAND} -E copy_directory "$,3rdparty,dxc>" "$/${_nbl_libraries_subdir}" + VERBATIM + ) +endfunction() + if(NABLA_FIND_PACKAGE_VERBOSE) message(STATUS "\n-- Nabla_ROOT = ${Nabla_ROOT}" @@ -29,4 +76,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/include/nbl/system/RuntimeModuleLookup.h b/include/nbl/system/RuntimeModuleLookup.h index 8268cd7e55..616242524c 100644 --- a/include/nbl/system/RuntimeModuleLookup.h +++ b/include/nbl/system/RuntimeModuleLookup.h @@ -46,9 +46,13 @@ struct RuntimeModuleLookup final #endif #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR) nabla.installOverrideRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR; + #elif defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_DEFAULT) + nabla.installOverrideRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR_DEFAULT; #endif #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR) dxc.installOverrideRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR; + #elif defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR_DEFAULT) + dxc.installOverrideRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR_DEFAULT; #endif #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY) nabla.runtimeAbsKey = NBL_CPACK_PACKAGE_NABLA_DLL_DIR_ABS_KEY; diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 2cd3c28d00..42b5e8f262 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -30,11 +30,18 @@ add_executable(smoke main.cpp pch.hpp cdb.ps1) target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) -set_target_properties(smoke PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_DEBUG "${Nabla_ROOT}/debug/bin" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${Nabla_ROOT}/bin" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${Nabla_ROOT}/relwithdebinfo/bin" -) + +option(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP "Use custom install lookup layout for smoke runtime modules" OFF) +if(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP) + if(NOT COMMAND nabla_enable_custom_install_lookup) + message(FATAL_ERROR "Nabla package does not expose nabla_enable_custom_install_lookup") + endif() + + nabla_enable_custom_install_lookup(smoke + RUNTIME_SUBDIR "your_custom_package" + LIBRARIES_SUBDIR "Libraries" + ) +endif() set(CMAKE_CTEST_ARGUMENTS --verbose) enable_testing() diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 39d74994da..e816e87e43 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -866,10 +866,17 @@ 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_NABLA_RUNTIME_DLL_DIR_PATH_REL_TO_CONSUMER_EXE_GE_ + "$>,$,$>>>" +) +set(_NBL_NABLA_RUNTIME_DLL_DIR_PATH_FROM_CONSUMER_EXE_GE_ + "$,${_NBL_NABLA_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 "$" ) NBL_ADJUST_FOLDERS(src) From b5bc065ecff074d02a681483724d1a1f9c7dd262 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 17:58:04 +0100 Subject: [PATCH 08/14] Add smoke install selftest and runtime module install rules --- .github/workflows/build-nabla.yml | 12 +++ cmake/NablaConfig.cmake.in | 110 ++++++++++++++++++++------- smoke/CMakeLists.txt | 65 +++++++++++----- smoke/CTestTestfile.install.cmake.in | 10 +++ smoke/NablaSmokeTests.cmake | 39 ++++++++++ 5 files changed, 188 insertions(+), 48 deletions(-) create mode 100644 smoke/CTestTestfile.install.cmake.in create mode 100644 smoke/NablaSmokeTests.cmake diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index 1e2262a889..e0b5a89fdc 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -402,3 +402,15 @@ jobs: - name: CTest Smoke Custom Lookup run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} + + - name: Configure Smoke Installed Runtime Test + run: cmake -S smoke -B smoke/out -D NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON -D NBL_SMOKE_INSTALL_SELFTEST=ON + + - name: Build Smoke Installed Runtime Test + run: cmake --build smoke/out --config ${{ matrix.config }} + + - name: Install Smoke Installed Runtime Test + run: cmake --install smoke/out --config ${{ matrix.config }} --prefix smoke/out/install + + - name: CTest Smoke Installed Runtime Test + run: ctest --verbose --test-dir smoke/out/install --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index 6302c1fa16..adf1ab9799 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -15,51 +15,107 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/NablaExportTargets.cmake") check_required_components(Nabla) -# Config mapping note: -# Runtime copy sources are resolved from $. -# CMake applies imported-config mapping (MAP_IMPORTED_CONFIG_*) to this expression. -# If a consumer overrides mapping on Nabla::Nabla, do it before this call. -function(nabla_enable_custom_install_lookup _TARGET) +# +# nabla_setup_runtime_modules( [RUNTIME_MODULES_SUBDIR ] [INSTALL_RULES ]) +# +# One-call runtime setup for Nabla and DXC modules. +# This function does not modify output directories of . +# It resolves destinations from the executable location at build time: +# $> +# +# Behavior: +# - Adds runtime lookup defines on : +# NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./" +# NBL_CPACK_PACKAGE_DXC_DLL_DIR="./" +# - Copies runtime modules into: +# $>/ +# - Optionally emits install rules (INSTALL_RULES ON): +# ${CMAKE_INSTALL_BINDIR}/ +# +# Typical usage: +# 1) Consumer follows Nabla package layout and keeps runtime next to the app: +# target_link_libraries(my_app PRIVATE Nabla::Nabla) +# # no extra call required +# +# 2) Consumer uses custom runtime folder, for example: +# /Libraries +# nabla_setup_runtime_modules(my_app RUNTIME_MODULES_SUBDIR "Libraries") +# +# 3) Consumer also installs runtime modules together with app install tree: +# install(TARGETS my_app RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +# nabla_setup_runtime_modules( +# my_app +# RUNTIME_MODULES_SUBDIR "Libraries" +# INSTALL_RULES ON +# ) +# +# Config mapping: +# - Source module path is resolved from $. +# - Imported-config mapping (MAP_IMPORTED_CONFIG_*) applies automatically. +# - Mapping can be overridden before or after this call in one configure run. +# - If using CMAKE_MAP_IMPORTED_CONFIG_, set it before find_package(Nabla). +# - Install rules place runtime modules under CMAKE_INSTALL_BINDIR and assume app runtime is +# installed there (or with equivalent relative layout). +# +function(nabla_setup_runtime_modules _TARGET) if(NOT TARGET "${_TARGET}") message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") endif() - set(_nbl_runtime_subdir "your_custom_package") - set(_nbl_libraries_subdir "Libraries") + set(_nbl_runtime_modules_subdir "Libraries") + set(_nbl_install_rules OFF) set(_nbl_options "") - set(_nbl_one_value_args RUNTIME_SUBDIR LIBRARIES_SUBDIR) + set(_nbl_one_value_args RUNTIME_MODULES_SUBDIR INSTALL_RULES) set(_nbl_multi_value_args "") cmake_parse_arguments(_NBL_CUSTOM "${_nbl_options}" "${_nbl_one_value_args}" "${_nbl_multi_value_args}" ${ARGN}) - if(_NBL_CUSTOM_RUNTIME_SUBDIR) - set(_nbl_runtime_subdir "${_NBL_CUSTOM_RUNTIME_SUBDIR}") - endif() - if(_NBL_CUSTOM_LIBRARIES_SUBDIR) - set(_nbl_libraries_subdir "${_NBL_CUSTOM_LIBRARIES_SUBDIR}") + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") endif() - - get_property(_nbl_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(_nbl_is_multi_config) - foreach(_nbl_cfg IN LISTS CMAKE_CONFIGURATION_TYPES) - string(TOUPPER "${_nbl_cfg}" _nbl_cfg_upper) - set_property(TARGET "${_TARGET}" PROPERTY "RUNTIME_OUTPUT_DIRECTORY_${_nbl_cfg_upper}" "${CMAKE_CURRENT_BINARY_DIR}/${_nbl_cfg}/${_nbl_runtime_subdir}") - endforeach() - else() - set_target_properties("${_TARGET}" PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${_nbl_runtime_subdir}") + if(DEFINED _NBL_CUSTOM_INSTALL_RULES) + set(_nbl_install_rules "${_NBL_CUSTOM_INSTALL_RULES}") endif() target_compile_definitions("${_TARGET}" PRIVATE - NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./${_nbl_libraries_subdir}" - NBL_CPACK_PACKAGE_DXC_DLL_DIR="./${_nbl_libraries_subdir}" + NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./${_nbl_runtime_modules_subdir}" + NBL_CPACK_PACKAGE_DXC_DLL_DIR="./${_nbl_runtime_modules_subdir}" ) add_custom_command(TARGET "${_TARGET}" POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/${_nbl_libraries_subdir}" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$/${_nbl_libraries_subdir}/" - COMMAND ${CMAKE_COMMAND} -E copy_directory "$,3rdparty,dxc>" "$/${_nbl_libraries_subdir}" + COMMAND ${CMAKE_COMMAND} -E make_directory "$/${_nbl_runtime_modules_subdir}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$/${_nbl_runtime_modules_subdir}/" + COMMAND ${CMAKE_COMMAND} -E copy_directory "$,3rdparty,dxc>" "$/${_nbl_runtime_modules_subdir}" VERBATIM ) + + if(_nbl_install_rules) + if(NOT DEFINED CMAKE_INSTALL_BINDIR) + include(GNUInstallDirs) + endif() + + set(_nbl_install_modules_dest "${CMAKE_INSTALL_BINDIR}/${_nbl_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() + endif() +endfunction() + +# Backward-compatible alias. Keep temporarily for external consumers. +function(nabla_enable_custom_install_lookup _TARGET) + message(DEPRECATION + "nabla_enable_custom_install_lookup is deprecated. " + "Use nabla_setup_runtime_modules instead." + ) + nabla_setup_runtime_modules("${_TARGET}" ${ARGN}) endfunction() if(NABLA_FIND_PACKAGE_VERBOSE) diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 42b5e8f262..a6f05f9795 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}) @@ -32,42 +33,64 @@ target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) option(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP "Use custom install lookup layout for smoke runtime modules" OFF) +option(NBL_SMOKE_INSTALL_SELFTEST "Install smoke with CTest metadata and run tests from install tree" OFF) +if(NBL_SMOKE_INSTALL_SELFTEST AND NOT NBL_SMOKE_CUSTOM_INSTALL_LOOKUP) + message(FATAL_ERROR "NBL_SMOKE_INSTALL_SELFTEST requires NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON") +endif() if(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP) - if(NOT COMMAND nabla_enable_custom_install_lookup) - message(FATAL_ERROR "Nabla package does not expose nabla_enable_custom_install_lookup") + set(_nbl_smoke_runtime_modules_setup_args + RUNTIME_MODULES_SUBDIR "Libraries" + ) + if(NBL_SMOKE_INSTALL_SELFTEST) + list(APPEND _nbl_smoke_runtime_modules_setup_args INSTALL_RULES ON) endif() - nabla_enable_custom_install_lookup(smoke - RUNTIME_SUBDIR "your_custom_package" - LIBRARIES_SUBDIR "Libraries" + nabla_setup_runtime_modules(smoke + ${_nbl_smoke_runtime_modules_setup_args} ) 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 ) 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}") + 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() From 61e250bbce290fc0e53bd64bbe11e517506e132a Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 21 Feb 2026 20:36:42 +0100 Subject: [PATCH 09/14] Consolidate smoke runtime flows and CI execution --- .github/workflows/build-nabla.yml | 32 +---- cmake/NablaConfig.cmake.in | 114 ++++++++--------- docs/consume/README.md | 142 +++++++++++++++++++++ include/nbl/system/IApplicationFramework.h | 2 +- include/nbl/system/RuntimeModuleLookup.h | 59 +++++++-- smoke/CMakeLists.txt | 27 ++-- smoke/RunSmokeFlow.cmake | 88 +++++++++++++ src/nbl/CMakeLists.txt | 24 +++- 8 files changed, 376 insertions(+), 112 deletions(-) create mode 100644 docs/consume/README.md create mode 100644 smoke/RunSmokeFlow.cmake diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index e0b5a89fdc..47a0ac8fc0 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -385,32 +385,8 @@ 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 NO_BUILD_COPY + run: cmake -D FLOW=NO_BUILD_COPY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake - - name: Build Smoke - run: cmake --build smoke/out --config ${{ matrix.config }} - - - name: CTest Smoke - run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} - - - name: Configure Smoke Custom Lookup - run: cmake -S smoke -B smoke/out -D NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON - - - name: Build Smoke Custom Lookup - run: cmake --build smoke/out --config ${{ matrix.config }} - - - name: CTest Smoke Custom Lookup - run: ctest --verbose --test-dir smoke/out --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} - - - name: Configure Smoke Installed Runtime Test - run: cmake -S smoke -B smoke/out -D NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON -D NBL_SMOKE_INSTALL_SELFTEST=ON - - - name: Build Smoke Installed Runtime Test - run: cmake --build smoke/out --config ${{ matrix.config }} - - - name: Install Smoke Installed Runtime Test - run: cmake --install smoke/out --config ${{ matrix.config }} --prefix smoke/out/install - - - name: CTest Smoke Installed Runtime Test - run: ctest --verbose --test-dir smoke/out/install --force-new-ctest-process --output-on-failure --no-tests=error -C ${{ matrix.config }} + - name: Smoke Flow WITH_BUILD_COPY + run: cmake -D FLOW=WITH_BUILD_COPY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index adf1ab9799..d1b6d836e0 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -17,46 +17,74 @@ check_required_components(Nabla) # # nabla_setup_runtime_modules( [RUNTIME_MODULES_SUBDIR ] [INSTALL_RULES ]) +# nabla_setup_runtime_install_modules( [RUNTIME_MODULES_SUBDIR ]) # -# One-call runtime setup for Nabla and DXC modules. -# This function does not modify output directories of . -# It resolves destinations from the executable location at build time: -# $> +# Runtime setup helpers for Nabla and DXC modules. # -# Behavior: +# Common behavior: # - Adds runtime lookup defines on : # NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./" # NBL_CPACK_PACKAGE_DXC_DLL_DIR="./" -# - Copies runtime modules into: -# $>/ -# - Optionally emits install rules (INSTALL_RULES ON): -# ${CMAKE_INSTALL_BINDIR}/ -# -# Typical usage: -# 1) Consumer follows Nabla package layout and keeps runtime next to the app: -# target_link_libraries(my_app PRIVATE Nabla::Nabla) -# # no extra call required # -# 2) Consumer uses custom runtime folder, for example: -# /Libraries -# nabla_setup_runtime_modules(my_app RUNTIME_MODULES_SUBDIR "Libraries") +# nabla_setup_runtime_modules: +# - Adds build-time copy into: +# $>/ +# - Optionally installs runtime modules when INSTALL_RULES is ON. # -# 3) Consumer also installs runtime modules together with app install tree: -# install(TARGETS my_app RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") -# nabla_setup_runtime_modules( -# my_app -# RUNTIME_MODULES_SUBDIR "Libraries" -# INSTALL_RULES ON -# ) +# nabla_setup_runtime_install_modules: +# - Adds install rules only (no build-time copy): +# ${CMAKE_INSTALL_BINDIR}/ # # Config mapping: # - Source module path is resolved from $. # - Imported-config mapping (MAP_IMPORTED_CONFIG_*) applies automatically. -# - Mapping can be overridden before or after this call in one configure run. # - If using CMAKE_MAP_IMPORTED_CONFIG_, set it before find_package(Nabla). -# - Install rules place runtime modules under CMAKE_INSTALL_BINDIR and assume app runtime is -# installed there (or with equivalent relative layout). # +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_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(nabla_setup_runtime_install_modules _TARGET) + if(NOT TARGET "${_TARGET}") + message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") + endif() + + set(_nbl_runtime_modules_subdir "Libraries") + set(_nbl_options "") + set(_nbl_one_value_args RUNTIME_MODULES_SUBDIR) + set(_nbl_multi_value_args "") + cmake_parse_arguments(_NBL_CUSTOM "${_nbl_options}" "${_nbl_one_value_args}" "${_nbl_multi_value_args}" ${ARGN}) + + if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) + set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") + endif() + + _nbl_runtime_modules_apply_lookup_definitions("${_TARGET}" "${_nbl_runtime_modules_subdir}") + _nbl_runtime_modules_add_install_rules("${_nbl_runtime_modules_subdir}") +endfunction() + function(nabla_setup_runtime_modules _TARGET) if(NOT TARGET "${_TARGET}") message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") @@ -64,7 +92,6 @@ function(nabla_setup_runtime_modules _TARGET) set(_nbl_runtime_modules_subdir "Libraries") set(_nbl_install_rules OFF) - set(_nbl_options "") set(_nbl_one_value_args RUNTIME_MODULES_SUBDIR INSTALL_RULES) set(_nbl_multi_value_args "") @@ -77,10 +104,7 @@ function(nabla_setup_runtime_modules _TARGET) set(_nbl_install_rules "${_NBL_CUSTOM_INSTALL_RULES}") endif() - target_compile_definitions("${_TARGET}" PRIVATE - NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./${_nbl_runtime_modules_subdir}" - NBL_CPACK_PACKAGE_DXC_DLL_DIR="./${_nbl_runtime_modules_subdir}" - ) + _nbl_runtime_modules_apply_lookup_definitions("${_TARGET}" "${_nbl_runtime_modules_subdir}") add_custom_command(TARGET "${_TARGET}" POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "$/${_nbl_runtime_modules_subdir}" @@ -90,34 +114,10 @@ function(nabla_setup_runtime_modules _TARGET) ) if(_nbl_install_rules) - if(NOT DEFINED CMAKE_INSTALL_BINDIR) - include(GNUInstallDirs) - endif() - - set(_nbl_install_modules_dest "${CMAKE_INSTALL_BINDIR}/${_nbl_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() + _nbl_runtime_modules_add_install_rules("${_nbl_runtime_modules_subdir}") endif() endfunction() -# Backward-compatible alias. Keep temporarily for external consumers. -function(nabla_enable_custom_install_lookup _TARGET) - message(DEPRECATION - "nabla_enable_custom_install_lookup is deprecated. " - "Use nabla_setup_runtime_modules instead." - ) - nabla_setup_runtime_modules("${_TARGET}" ${ARGN}) -endfunction() - if(NABLA_FIND_PACKAGE_VERBOSE) message(STATUS "\n-- Nabla_ROOT = ${Nabla_ROOT}" diff --git a/docs/consume/README.md b/docs/consume/README.md new file mode 100644 index 0000000000..4b746c6618 --- /dev/null +++ b/docs/consume/README.md @@ -0,0 +1,142 @@ +# 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_setup_runtime_modules(...)` +- helper `nabla_setup_runtime_install_modules(...)` + +On shared builds, runtime modules include Nabla and DXC. + +## 2. Locate the package + +You can point CMake to the package with: + +- `-D Nabla_DIR=/cmake` +- `CMAKE_PREFIX_PATH=` + +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) +``` + +## 3. Flow NO_BUILD_COPY install to e.g. `./Libraries` + +Use this flow when: + +- build-time should load directly from package +- install tree should load from e.g. `./Libraries` + +Call install-only helper: + +```cmake +include(GNUInstallDirs) + +add_executable(my_app main.cpp) +target_link_libraries(my_app PRIVATE Nabla::Nabla) + +nabla_setup_runtime_install_modules(my_app + RUNTIME_MODULES_SUBDIR "Libraries" +) + +install(TARGETS my_app + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +``` + +What it does: + +- adds runtime lookup defines `./Libraries` +- adds install rules for Nabla/DXC runtime modules to `${CMAKE_INSTALL_BINDIR}/Libraries` +- does not add post-build copy + +Runtime behavior: + +- build tree falls back to package runtime if `./Libraries` does not exist and relative package lookup can be resolved +- install tree uses `./Libraries` once modules are installed there + +## 4. Flow WITH_BUILD_COPY install to e.g. `./Libraries` + +Use one call when you want both: + +- build-time copy to runtime subdir +- install-time copy to runtime subdir + +```cmake +include(GNUInstallDirs) + +add_executable(my_app main.cpp) +target_link_libraries(my_app PRIVATE Nabla::Nabla) + +nabla_setup_runtime_modules(my_app + RUNTIME_MODULES_SUBDIR "Libraries" + INSTALL_RULES ON +) + +install(TARGETS my_app + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +``` + +## 5. Config mapping + +Runtime source paths are resolved from `$`. + +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)` + +## 6. Troubleshooting + +### `Could not load dxcompiler module` or `Could not load Nabla API` + +Check: + +- helper usage matches your intended flow mode +- `RUNTIME_MODULES_SUBDIR` matches actual runtime folder layout +- install tree actually contains runtime modules under expected subdir + +### Build works but installed app fails + +Most often install rules are missing. + +Use either: + +- `nabla_setup_runtime_install_modules(...)` for `NO_BUILD_COPY` +- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` for `WITH_BUILD_COPY` + +### Build tree cannot resolve package runtime in install-only mode + +This usually means your build tree and package runtime are on different roots or drives so a relative fallback cannot be formed. + +Use one of: + +- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` to copy runtime modules into build tree + +### Why modules are copied in build tree + +Only `nabla_setup_runtime_modules(... INSTALL_RULES ON)` performs build-time copy. + +If you want no build copy, use `nabla_setup_runtime_install_modules(...)` instead. + +## 7. Design guidance + +For relocatable consumers: + +- keep lookup relative to executable +- never expose absolute paths in public compile definitions +- use one of the helper flows consistently per target diff --git a/include/nbl/system/IApplicationFramework.h b/include/nbl/system/IApplicationFramework.h index 82da25cfb5..53af9c9b94 100644 --- a/include/nbl/system/IApplicationFramework.h +++ b/include/nbl/system/IApplicationFramework.h @@ -42,7 +42,7 @@ class IApplicationFramework : public core::IReferenceCounted 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 expose any define that contains an absolute path. + 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. diff --git a/include/nbl/system/RuntimeModuleLookup.h b/include/nbl/system/RuntimeModuleLookup.h index 616242524c..dd6ce35e67 100644 --- a/include/nbl/system/RuntimeModuleLookup.h +++ b/include/nbl/system/RuntimeModuleLookup.h @@ -20,6 +20,7 @@ struct RuntimeModuleLookup final std::string_view buildOutputDir = ""; std::string_view buildDllPath = ""; std::string_view installOverrideRel = ""; + std::string_view installBuildFallbackRel = ""; std::string_view runtimeAbsKey = ""; }; @@ -46,13 +47,15 @@ struct RuntimeModuleLookup final #endif #if defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR) nabla.installOverrideRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR; - #elif defined(NBL_CPACK_PACKAGE_NABLA_DLL_DIR_DEFAULT) - nabla.installOverrideRel = NBL_CPACK_PACKAGE_NABLA_DLL_DIR_DEFAULT; #endif #if defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR) dxc.installOverrideRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR; - #elif defined(NBL_CPACK_PACKAGE_DXC_DLL_DIR_DEFAULT) - dxc.installOverrideRel = NBL_CPACK_PACKAGE_DXC_DLL_DIR_DEFAULT; + #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; @@ -76,11 +79,18 @@ struct RuntimeModuleLookup final { if (relocatablePackage) { - if (!hasCompleteInstallOverride()) - tryResolveInstallPathsFromPackageLayout(exeDirectory); + if (!hasUsableInstallPaths()) + { + if (!tryResolveInstallPathsFromPackageLayout(exeDirectory)) + tryResolveInstallPathsFromBuildFallbackHints(exeDirectory); + } return true; } - return hasUsableInstallPaths() || tryResolveInstallPathsFromPackageLayout(exeDirectory); + if (hasUsableInstallPaths()) + return true; + if (tryResolveInstallPathsFromPackageLayout(exeDirectory)) + return true; + return tryResolveInstallPathsFromBuildFallbackHints(exeDirectory); } inline void finalizeInstallLookups(bool useInstallLookups) @@ -117,7 +127,12 @@ struct RuntimeModuleLookup final { if (relativePath.empty() || exeDirectory.empty()) return system::path(""); - return std::filesystem::absolute(exeDirectory / system::path(relativePath)); + + const auto relPath = system::path(relativePath); + if (relPath.is_absolute()) + return system::path(""); + + return std::filesystem::absolute(exeDirectory / relPath); } inline bool hasUsableInstallPaths() const @@ -173,9 +188,33 @@ struct RuntimeModuleLookup final return false; } - inline bool hasCompleteInstallOverride() const + inline bool tryResolveInstallPathsFromBuildFallbackHints(const system::path& exeDirectory) { - return sharedBuild ? (hasInstallOverride(nabla) && hasInstallOverride(dxc)) : hasInstallOverride(dxc); + 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_) diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index a6f05f9795..8de97448d1 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -32,22 +32,25 @@ target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) -option(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP "Use custom install lookup layout for smoke runtime modules" OFF) -option(NBL_SMOKE_INSTALL_SELFTEST "Install smoke with CTest metadata and run tests from install tree" OFF) -if(NBL_SMOKE_INSTALL_SELFTEST AND NOT NBL_SMOKE_CUSTOM_INSTALL_LOOKUP) - message(FATAL_ERROR "NBL_SMOKE_INSTALL_SELFTEST requires NBL_SMOKE_CUSTOM_INSTALL_LOOKUP=ON") -endif() -if(NBL_SMOKE_CUSTOM_INSTALL_LOOKUP) - set(_nbl_smoke_runtime_modules_setup_args +set(NBL_SMOKE_FLOW "NO_BUILD_COPY" CACHE STRING "Smoke runtime flow: NO_BUILD_COPY or WITH_BUILD_COPY") +set_property(CACHE NBL_SMOKE_FLOW PROPERTY STRINGS NO_BUILD_COPY WITH_BUILD_COPY) +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 "NO_BUILD_COPY") + # No build-time copy, install-time runtime modules in ./Libraries. + nabla_setup_runtime_install_modules(smoke RUNTIME_MODULES_SUBDIR "Libraries" ) - if(NBL_SMOKE_INSTALL_SELFTEST) - list(APPEND _nbl_smoke_runtime_modules_setup_args INSTALL_RULES ON) - endif() - +elseif(NBL_SMOKE_FLOW STREQUAL "WITH_BUILD_COPY") + # Build-time copy + install-time runtime modules in ./Libraries. nabla_setup_runtime_modules(smoke - ${_nbl_smoke_runtime_modules_setup_args} + RUNTIME_MODULES_SUBDIR "Libraries" + INSTALL_RULES ON ) +else() + message(FATAL_ERROR "Invalid NBL_SMOKE_FLOW='${NBL_SMOKE_FLOW}'") endif() set(CMAKE_CTEST_ARGUMENTS --verbose) diff --git a/smoke/RunSmokeFlow.cmake b/smoke/RunSmokeFlow.cmake new file mode 100644 index 0000000000..64d8ea0e7d --- /dev/null +++ b/smoke/RunSmokeFlow.cmake @@ -0,0 +1,88 @@ +if(NOT DEFINED FLOW) + message(FATAL_ERROR "FLOW is required. Allowed values: NO_BUILD_COPY, WITH_BUILD_COPY") +endif() + +string(TOUPPER "${FLOW}" FLOW) +if(NOT FLOW MATCHES "^(NO_BUILD_COPY|WITH_BUILD_COPY)$") + message(FATAL_ERROR "Invalid FLOW='${FLOW}'. Allowed values: NO_BUILD_COPY, WITH_BUILD_COPY") +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}") + +run_cmd( + "${CMAKE_COMMAND}" + -S "${SMOKE_SOURCE_DIR}" + -B "${BUILD_DIR}" + -D "NBL_SMOKE_FLOW=${FLOW}" + -D "NBL_SMOKE_INSTALL_SELFTEST=ON" +) + +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}" +) + +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}" +) diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index e816e87e43..18a25c8619 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -866,17 +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_NABLA_RUNTIME_DLL_DIR_PATH_FROM_CONSUMER_EXE_GE_ - "$,${_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 "$" + INTERFACE "$" ) NBL_ADJUST_FOLDERS(src) From edd1bec532cc68e832431dc2ed9b589907401666 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Feb 2026 00:23:39 +0100 Subject: [PATCH 10/14] Refine runtime module setup API and smoke flows --- .github/workflows/build-nabla.yml | 8 +- cmake/NablaConfig.cmake.in | 679 ++++++++++++++++++++++++++++-- docs/consume/README.md | 164 +++++--- smoke/CMakeLists.txt | 19 +- smoke/RunSmokeFlow.cmake | 6 +- 5 files changed, 763 insertions(+), 113 deletions(-) diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index 47a0ac8fc0..fea595428f 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -385,8 +385,8 @@ jobs: if (-not (Test-Path "smoke/build-ct/install")) { throw "smoke/build-ct/install not found" } tree.com smoke /F - - name: Smoke Flow NO_BUILD_COPY - run: cmake -D FLOW=NO_BUILD_COPY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake + - name: Smoke Flow CONFIGURE_ONLY + run: cmake -D FLOW=CONFIGURE_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake - - name: Smoke Flow WITH_BUILD_COPY - run: cmake -D FLOW=WITH_BUILD_COPY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake + - name: Smoke Flow BUILD_ONLY + run: cmake -D FLOW=BUILD_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index d1b6d836e0..24d46834e5 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -16,30 +16,45 @@ include("${CMAKE_CURRENT_LIST_DIR}/NablaExportTargets.cmake") check_required_components(Nabla) # -# nabla_setup_runtime_modules( [RUNTIME_MODULES_SUBDIR ] [INSTALL_RULES ]) -# nabla_setup_runtime_install_modules( [RUNTIME_MODULES_SUBDIR ]) +# nabla_sync_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_ ]... +# [MODE ] +# [RUNTIME_MODULES_SUBDIR ] +# [BUILD_TRIGGER_TARGETS ] +# ) # -# Runtime setup helpers for Nabla and DXC modules. +# nabla_apply_runtime_lookup( +# TARGETS +# [RUNTIME_MODULES_SUBDIR ] +# ) # -# Common behavior: -# - Adds runtime lookup defines on : -# NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./" -# NBL_CPACK_PACKAGE_DXC_DLL_DIR="./" +# nabla_setup_runtime_install_modules( +# [RUNTIME_MODULES_SUBDIR ] +# ) # -# nabla_setup_runtime_modules: -# - Adds build-time copy into: -# $>/ -# - Optionally installs runtime modules when INSTALL_RULES is ON. +# nabla_setup_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_ ]... +# [APPLY_LOOKUP_TO_TARGETS ] +# [RUNTIME_MODULES_SUBDIR ] +# [MODE ] +# [INSTALL_RULES ] +# [BUILD_TRIGGER_TARGETS ] +# ) # -# nabla_setup_runtime_install_modules: -# - Adds install rules only (no build-time copy): -# ${CMAKE_INSTALL_BINDIR}/ +# Wrapper around sync + lookup + install helpers. # # Config mapping: -# - Source module path is resolved from $. -# - Imported-config mapping (MAP_IMPORTED_CONFIG_*) applies automatically. +# - 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}" @@ -47,6 +62,18 @@ function(_nbl_runtime_modules_apply_lookup_definitions _TARGET _RUNTIME_MODULES_ ) 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) @@ -66,36 +93,581 @@ function(_nbl_runtime_modules_add_install_rules _RUNTIME_MODULES_SUBDIR) endif() endfunction() -function(nabla_setup_runtime_install_modules _TARGET) +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_extract_destination_overrides _OUT_OVERRIDES _OUT_UNKNOWN) + set(_tokens ${ARGN}) + set(_overrides "") + set(_unknown "") + + list(LENGTH _tokens _tokens_len) + math(EXPR _tokens_mod2 "${_tokens_len} % 2") + if(_tokens_mod2) + set(${_OUT_OVERRIDES} "" PARENT_SCOPE) + set(${_OUT_UNKNOWN} "${_tokens}" PARENT_SCOPE) + return() + endif() + + while(TRUE) + list(LENGTH _tokens _tokens_len) + if(_tokens_len EQUAL 0) + break() + endif() + + list(POP_FRONT _tokens _key) + list(POP_FRONT _tokens _value) + string(TOUPPER "${_key}" _key_upper) + + if(_key_upper MATCHES "^DESTINATION_[A-Z0-9_]+$") + string(REGEX REPLACE "^DESTINATION_" "" _cfg_upper "${_key_upper}") + list(APPEND _overrides "${_cfg_upper}::${_value}") + else() + list(APPEND _unknown "${_key}" "${_value}") + endif() + endwhile() + + set(${_OUT_OVERRIDES} ${_overrides} PARENT_SCOPE) + set(${_OUT_UNKNOWN} ${_unknown} PARENT_SCOPE) +endfunction() + +function(_nbl_runtime_modules_expand_destination_pairs _DESTINATION_DEFAULT _DESTINATION_OVERRIDES _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) + set(_resolved_destination "") + + foreach(_override IN LISTS _DESTINATION_OVERRIDES) + string(REPLACE "::" ";" _override_parts "${_override}") + list(GET _override_parts 0 _override_cfg_upper) + if(_override_cfg_upper STREQUAL _cfg_upper) + list(GET _override_parts 1 _resolved_destination) + break() + endif() + endforeach() + + if(_resolved_destination STREQUAL "") + set(_resolved_destination "${_DESTINATION_DEFAULT}") + endif() + + if(_resolved_destination STREQUAL "") + message(FATAL_ERROR "Nabla: missing destination for consumer config \"${_consumer_config}\". Provide DESTINATION or DESTINATION_${_cfg_upper}.") + 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) + file(COPY_FILE "${_nabla_runtime_file}" "${_runtime_modules_dst}/${_nabla_runtime_name}" ONLY_IF_DIFFERENT INPUT_MAY_BE_RECENT) + file(COPY_FILE "${_dxc_runtime_file}" "${_runtime_modules_dst}/${_dxc_runtime_name}" 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}" + ) + 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") - set(_nbl_options "") - set(_nbl_one_value_args RUNTIME_MODULES_SUBDIR) - set(_nbl_multi_value_args "") - cmake_parse_arguments(_NBL_CUSTOM "${_nbl_options}" "${_nbl_one_value_args}" "${_nbl_multi_value_args}" ${ARGN}) + 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_apply_lookup_definitions("${_TARGET}" "${_nbl_runtime_modules_subdir}") _nbl_runtime_modules_add_install_rules("${_nbl_runtime_modules_subdir}") endfunction() -function(nabla_setup_runtime_modules _TARGET) - if(NOT TARGET "${_TARGET}") - message(FATAL_ERROR "Nabla: target \"${_TARGET}\" does not exist") +# +# nabla_sync_runtime_modules( +# [TARGETS ] +# [DESTINATION ] +# [DESTINATION_ ...] +# [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_ 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;RUNTIME_MODULES_SUBDIR" "TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) + _nbl_runtime_modules_extract_destination_overrides(_destination_overrides _unknown_tokens ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}) + + if(_unknown_tokens) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_sync_runtime_modules: ${_unknown_tokens}") 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(_destination_overrides) + 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_") + 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}" "${_destination_overrides}" _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_ ...] +# [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_options "") - set(_nbl_one_value_args RUNTIME_MODULES_SUBDIR INSTALL_RULES) - set(_nbl_multi_value_args "") - cmake_parse_arguments(_NBL_CUSTOM "${_nbl_options}" "${_nbl_one_value_args}" "${_nbl_multi_value_args}" ${ARGN}) + set(_nbl_mode BUILD_TIME) + + cmake_parse_arguments(_NBL_CUSTOM "" "RUNTIME_MODULES_SUBDIR;INSTALL_RULES;MODE;DESTINATION" "TARGETS;APPLY_LOOKUP_TO_TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) + _nbl_runtime_modules_extract_destination_overrides(_destination_overrides _unknown_tokens ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}) + + if(_unknown_tokens) + message(FATAL_ERROR "Nabla: unexpected arguments for nabla_setup_runtime_modules: ${_unknown_tokens}") + endif() if(_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR) set(_nbl_runtime_modules_subdir "${_NBL_CUSTOM_RUNTIME_MODULES_SUBDIR}") @@ -103,18 +675,51 @@ function(nabla_setup_runtime_modules _TARGET) 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() - _nbl_runtime_modules_apply_lookup_definitions("${_TARGET}" "${_nbl_runtime_modules_subdir}") - - add_custom_command(TARGET "${_TARGET}" POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/${_nbl_runtime_modules_subdir}" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$/${_nbl_runtime_modules_subdir}/" - COMMAND ${CMAKE_COMMAND} -E copy_directory "$,3rdparty,dxc>" "$/${_nbl_runtime_modules_subdir}" - VERBATIM + 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() + foreach(_override IN LISTS _destination_overrides) + string(REPLACE "::" ";" _override_parts "${_override}") + list(GET _override_parts 0 _cfg_upper) + list(GET _override_parts 1 _cfg_destination) + list(APPEND _sync_args "DESTINATION_${_cfg_upper}" "${_cfg_destination}") + endforeach() + 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) - _nbl_runtime_modules_add_install_rules("${_nbl_runtime_modules_subdir}") + nabla_setup_runtime_install_modules( + RUNTIME_MODULES_SUBDIR "${_nbl_runtime_modules_subdir}" + ) endif() endfunction() diff --git a/docs/consume/README.md b/docs/consume/README.md index 4b746c6618..53353fd6d6 100644 --- a/docs/consume/README.md +++ b/docs/consume/README.md @@ -7,19 +7,20 @@ This document describes how to consume an installed Nabla package from another C After `find_package(Nabla CONFIG REQUIRED)`, the package provides: - imported target `Nabla::Nabla` -- helper `nabla_setup_runtime_modules(...)` +- 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. -## 2. Locate the package +Implementation and argument docs: -You can point CMake to the package with: +- 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 -- `-D Nabla_DIR=/cmake` -- `CMAKE_PREFIX_PATH=` - -Minimal baseline: +## 2. Minimal baseline ```cmake cmake_minimum_required(VERSION 3.30) @@ -31,57 +32,65 @@ add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE Nabla::Nabla) ``` -## 3. Flow NO_BUILD_COPY install to e.g. `./Libraries` - -Use this flow when: +Behavior in this minimal setup: -- build-time should load directly from package -- install tree should load from e.g. `./Libraries` +- 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 -Call install-only helper: +If you also need your own install layout, add install rules and relative lookup defines. +Helpers from sections below can do this for you. -```cmake -include(GNUInstallDirs) +## 3. Runtime setup primitives -add_executable(my_app main.cpp) -target_link_libraries(my_app PRIVATE Nabla::Nabla) +### 3.1 Copy runtime modules -nabla_setup_runtime_install_modules(my_app +```cmake +nabla_sync_runtime_modules( + TARGETS my_app + MODE BUILD_TIME RUNTIME_MODULES_SUBDIR "Libraries" ) +``` -install(TARGETS my_app - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +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 ) ``` -What it does: +Rules: -- adds runtime lookup defines `./Libraries` -- adds install rules for Nabla/DXC runtime modules to `${CMAKE_INSTALL_BINDIR}/Libraries` -- does not add post-build copy +- use either `TARGETS` mode or `DESTINATION` / `DESTINATION_` mode +- `MODE CONFIGURE_TIME` does copy during configure/generate +- `MODE BUILD_TIME` and `MODE BOTH` in destination mode require `BUILD_TRIGGER_TARGETS` -Runtime behavior: +### 3.2 Apply runtime lookup defines -- build tree falls back to package runtime if `./Libraries` does not exist and relative package lookup can be resolved -- install tree uses `./Libraries` once modules are installed there +```cmake +nabla_apply_runtime_lookup( + TARGETS my_app + RUNTIME_MODULES_SUBDIR "Libraries" +) +``` -## 4. Flow WITH_BUILD_COPY install to e.g. `./Libraries` +This sets: -Use one call when you want both: +- `NBL_CPACK_PACKAGE_NABLA_DLL_DIR="./Libraries"` +- `NBL_CPACK_PACKAGE_DXC_DLL_DIR="./Libraries"` -- build-time copy to runtime subdir -- install-time copy to runtime subdir +### 3.3 Install runtime modules ```cmake include(GNUInstallDirs) -add_executable(my_app main.cpp) -target_link_libraries(my_app PRIVATE Nabla::Nabla) - -nabla_setup_runtime_modules(my_app +nabla_setup_runtime_install_modules( RUNTIME_MODULES_SUBDIR "Libraries" - INSTALL_RULES ON ) install(TARGETS my_app @@ -89,9 +98,48 @@ install(TARGETS my_app ) ``` -## 5. Config mapping +## 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 `$`. +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. @@ -99,44 +147,38 @@ 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 -## 6. Troubleshooting +## 7. Troubleshooting ### `Could not load dxcompiler module` or `Could not load Nabla API` Check: -- helper usage matches your intended flow mode -- `RUNTIME_MODULES_SUBDIR` matches actual runtime folder layout -- install tree actually contains runtime modules under expected subdir +- 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 -Most often install rules are missing. +Install rules are usually missing. Use either: -- `nabla_setup_runtime_install_modules(...)` for `NO_BUILD_COPY` -- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` for `WITH_BUILD_COPY` - -### Build tree cannot resolve package runtime in install-only mode - -This usually means your build tree and package runtime are on different roots or drives so a relative fallback cannot be formed. - -Use one of: +- `nabla_setup_runtime_install_modules(...)` +- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` -- `nabla_setup_runtime_modules(... INSTALL_RULES ON)` to copy runtime modules into build tree - -### Why modules are copied in build tree - -Only `nabla_setup_runtime_modules(... INSTALL_RULES ON)` performs build-time copy. - -If you want no build copy, use `nabla_setup_runtime_install_modules(...)` instead. - -## 7. Design guidance +## 8. Design guidance For relocatable consumers: - keep lookup relative to executable - never expose absolute paths in public compile definitions -- use one of the helper flows consistently per target +- 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. diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 8de97448d1..7369388483 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -32,21 +32,24 @@ target_link_libraries(smoke PRIVATE Nabla::Nabla) target_compile_definitions(smoke PRIVATE _AFXDLL) target_precompile_headers(smoke PRIVATE pch.hpp) -set(NBL_SMOKE_FLOW "NO_BUILD_COPY" CACHE STRING "Smoke runtime flow: NO_BUILD_COPY or WITH_BUILD_COPY") -set_property(CACHE NBL_SMOKE_FLOW PROPERTY STRINGS NO_BUILD_COPY WITH_BUILD_COPY) +set(NBL_SMOKE_FLOW "CONFIGURE_ONLY" CACHE STRING "Smoke runtime flow: CONFIGURE_ONLY or BUILD_ONLY") +set_property(CACHE NBL_SMOKE_FLOW PROPERTY STRINGS 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 "NO_BUILD_COPY") - # No build-time copy, install-time runtime modules in ./Libraries. - nabla_setup_runtime_install_modules(smoke +if(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 "WITH_BUILD_COPY") - # Build-time copy + install-time runtime modules in ./Libraries. - nabla_setup_runtime_modules(smoke +elseif(NBL_SMOKE_FLOW STREQUAL "BUILD_ONLY") + nabla_setup_runtime_modules( + TARGETS smoke RUNTIME_MODULES_SUBDIR "Libraries" + MODE BUILD_TIME INSTALL_RULES ON ) else() diff --git a/smoke/RunSmokeFlow.cmake b/smoke/RunSmokeFlow.cmake index 64d8ea0e7d..f192e2a838 100644 --- a/smoke/RunSmokeFlow.cmake +++ b/smoke/RunSmokeFlow.cmake @@ -1,10 +1,10 @@ if(NOT DEFINED FLOW) - message(FATAL_ERROR "FLOW is required. Allowed values: NO_BUILD_COPY, WITH_BUILD_COPY") + message(FATAL_ERROR "FLOW is required. Allowed values: CONFIGURE_ONLY, BUILD_ONLY") endif() string(TOUPPER "${FLOW}" FLOW) -if(NOT FLOW MATCHES "^(NO_BUILD_COPY|WITH_BUILD_COPY)$") - message(FATAL_ERROR "Invalid FLOW='${FLOW}'. Allowed values: NO_BUILD_COPY, WITH_BUILD_COPY") +if(NOT FLOW MATCHES "^(CONFIGURE_ONLY|BUILD_ONLY)$") + message(FATAL_ERROR "Invalid FLOW='${FLOW}'. Allowed values: CONFIGURE_ONLY, BUILD_ONLY") endif() if(NOT DEFINED CONFIG) From 6c23014559f23c7e287f466af8eaa5259020f78b Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Feb 2026 01:02:01 +0100 Subject: [PATCH 11/14] Use cmake parse arguments for runtime module destinations --- cmake/NablaConfig.cmake.in | 123 ++++++++++++++++--------------------- docs/consume/README.md | 10 +-- 2 files changed, 57 insertions(+), 76 deletions(-) diff --git a/cmake/NablaConfig.cmake.in b/cmake/NablaConfig.cmake.in index 24d46834e5..b67293d5ab 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -19,7 +19,9 @@ check_required_components(Nabla) # nabla_sync_runtime_modules( # [TARGETS ] # [DESTINATION ] -# [DESTINATION_ ]... +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] # [MODE ] # [RUNTIME_MODULES_SUBDIR ] # [BUILD_TRIGGER_TARGETS ] @@ -37,7 +39,9 @@ check_required_components(Nabla) # nabla_setup_runtime_modules( # [TARGETS ] # [DESTINATION ] -# [DESTINATION_ ]... +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] # [APPLY_LOOKUP_TO_TARGETS ] # [RUNTIME_MODULES_SUBDIR ] # [MODE ] @@ -106,64 +110,24 @@ function(_nbl_runtime_modules_collect_consumer_configs _OUT_CONFIGS) set(${_OUT_CONFIGS} ${_consumer_configs} PARENT_SCOPE) endfunction() -function(_nbl_runtime_modules_extract_destination_overrides _OUT_OVERRIDES _OUT_UNKNOWN) - set(_tokens ${ARGN}) - set(_overrides "") - set(_unknown "") - - list(LENGTH _tokens _tokens_len) - math(EXPR _tokens_mod2 "${_tokens_len} % 2") - if(_tokens_mod2) - set(${_OUT_OVERRIDES} "" PARENT_SCOPE) - set(${_OUT_UNKNOWN} "${_tokens}" PARENT_SCOPE) - return() - endif() - - while(TRUE) - list(LENGTH _tokens _tokens_len) - if(_tokens_len EQUAL 0) - break() - endif() - - list(POP_FRONT _tokens _key) - list(POP_FRONT _tokens _value) - string(TOUPPER "${_key}" _key_upper) - - if(_key_upper MATCHES "^DESTINATION_[A-Z0-9_]+$") - string(REGEX REPLACE "^DESTINATION_" "" _cfg_upper "${_key_upper}") - list(APPEND _overrides "${_cfg_upper}::${_value}") - else() - list(APPEND _unknown "${_key}" "${_value}") - endif() - endwhile() - - set(${_OUT_OVERRIDES} ${_overrides} PARENT_SCOPE) - set(${_OUT_UNKNOWN} ${_unknown} PARENT_SCOPE) -endfunction() - -function(_nbl_runtime_modules_expand_destination_pairs _DESTINATION_DEFAULT _DESTINATION_OVERRIDES _OUT_CFG_DST_PAIRS) +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) - set(_resolved_destination "") - - foreach(_override IN LISTS _DESTINATION_OVERRIDES) - string(REPLACE "::" ";" _override_parts "${_override}") - list(GET _override_parts 0 _override_cfg_upper) - if(_override_cfg_upper STREQUAL _cfg_upper) - list(GET _override_parts 1 _resolved_destination) - break() - endif() - endforeach() - - if(_resolved_destination STREQUAL "") + 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 DESTINATION_${_cfg_upper}.") + 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 "\\$<") @@ -525,7 +489,9 @@ endfunction() # nabla_sync_runtime_modules( # [TARGETS ] # [DESTINATION ] -# [DESTINATION_ ...] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] # [MODE BUILD_TIME|CONFIGURE_TIME|BOTH] # [RUNTIME_MODULES_SUBDIR ] # [BUILD_TRIGGER_TARGETS ] @@ -537,7 +503,7 @@ endfunction() # - TARGETS mode # Copies beside each target runtime dir under RUNTIME_MODULES_SUBDIR. # - DESTINATION mode -# Copies to explicit DESTINATION or DESTINATION_ paths. +# Copies to explicit DESTINATION or DESTINATION_DEBUG/RELEASE/RELWITHDEBINFO paths. # # MODE: # - BUILD_TIME @@ -555,11 +521,10 @@ function(nabla_sync_runtime_modules) set(_nbl_runtime_modules_subdir "Libraries") set(_nbl_mode BUILD_TIME) - cmake_parse_arguments(_NBL_CUSTOM "" "MODE;DESTINATION;RUNTIME_MODULES_SUBDIR" "TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) - _nbl_runtime_modules_extract_destination_overrides(_destination_overrides _unknown_tokens ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}) + cmake_parse_arguments(_NBL_CUSTOM "" "MODE;DESTINATION;DESTINATION_DEBUG;DESTINATION_RELEASE;DESTINATION_RELWITHDEBINFO;RUNTIME_MODULES_SUBDIR" "TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) - if(_unknown_tokens) - message(FATAL_ERROR "Nabla: unexpected arguments for nabla_sync_runtime_modules: ${_unknown_tokens}") + 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) @@ -583,7 +548,13 @@ function(nabla_sync_runtime_modules) if(DEFINED _NBL_CUSTOM_DESTINATION AND NOT _NBL_CUSTOM_DESTINATION STREQUAL "") set(_has_destination ON) endif() - if(_destination_overrides) + 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() @@ -592,7 +563,7 @@ function(nabla_sync_runtime_modules) endif() if(NOT _has_targets AND NOT _has_destination) - message(FATAL_ERROR "Nabla: nabla_sync_runtime_modules requires TARGETS or DESTINATION/DESTINATION_") + message(FATAL_ERROR "Nabla: nabla_sync_runtime_modules requires TARGETS or DESTINATION/DESTINATION_DEBUG/DESTINATION_RELEASE/DESTINATION_RELWITHDEBINFO") endif() if(_has_targets) @@ -615,7 +586,13 @@ function(nabla_sync_runtime_modules) return() endif() - _nbl_runtime_modules_expand_destination_pairs("${_NBL_CUSTOM_DESTINATION}" "${_destination_overrides}" _cfg_dst_pairs) + _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) @@ -639,7 +616,9 @@ endfunction() # nabla_setup_runtime_modules( # [TARGETS ] # [DESTINATION ] -# [DESTINATION_ ...] +# [DESTINATION_DEBUG ] +# [DESTINATION_RELEASE ] +# [DESTINATION_RELWITHDEBINFO ] # [MODE BUILD_TIME|CONFIGURE_TIME|BOTH] # [RUNTIME_MODULES_SUBDIR ] # [INSTALL_RULES ON|OFF] @@ -662,11 +641,10 @@ function(nabla_setup_runtime_modules) set(_nbl_install_rules OFF) set(_nbl_mode BUILD_TIME) - cmake_parse_arguments(_NBL_CUSTOM "" "RUNTIME_MODULES_SUBDIR;INSTALL_RULES;MODE;DESTINATION" "TARGETS;APPLY_LOOKUP_TO_TARGETS;BUILD_TRIGGER_TARGETS" ${ARGV}) - _nbl_runtime_modules_extract_destination_overrides(_destination_overrides _unknown_tokens ${_NBL_CUSTOM_UNPARSED_ARGUMENTS}) + 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(_unknown_tokens) - message(FATAL_ERROR "Nabla: unexpected arguments for nabla_setup_runtime_modules: ${_unknown_tokens}") + 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) @@ -690,12 +668,15 @@ function(nabla_setup_runtime_modules) if(DEFINED _NBL_CUSTOM_DESTINATION AND NOT _NBL_CUSTOM_DESTINATION STREQUAL "") list(APPEND _sync_args DESTINATION "${_NBL_CUSTOM_DESTINATION}") endif() - foreach(_override IN LISTS _destination_overrides) - string(REPLACE "::" ";" _override_parts "${_override}") - list(GET _override_parts 0 _cfg_upper) - list(GET _override_parts 1 _cfg_destination) - list(APPEND _sync_args "DESTINATION_${_cfg_upper}" "${_cfg_destination}") - endforeach() + 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() diff --git a/docs/consume/README.md b/docs/consume/README.md index 53353fd6d6..f11aa10bbe 100644 --- a/docs/consume/README.md +++ b/docs/consume/README.md @@ -66,7 +66,7 @@ nabla_sync_runtime_modules( Rules: -- use either `TARGETS` mode or `DESTINATION` / `DESTINATION_` mode +- 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` @@ -178,7 +178,7 @@ For relocatable consumers: 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. +- 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 From 833009ba7dceb86f4c6723cdaa43734fba24fe8e Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Feb 2026 01:43:06 +0100 Subject: [PATCH 12/14] Add minimalistic smoke flow and consumption reference --- docs/consume/README.md | 15 +++++++++++++ smoke/CMakeLists.txt | 8 ++++--- smoke/RunSmokeFlow.cmake | 47 +++++++++++++++++++++++----------------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/docs/consume/README.md b/docs/consume/README.md index f11aa10bbe..e73a69c310 100644 --- a/docs/consume/README.md +++ b/docs/consume/README.md @@ -182,3 +182,18 @@ Note: - 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/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 7369388483..99c76a302c 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -32,13 +32,15 @@ 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: CONFIGURE_ONLY or BUILD_ONLY") -set_property(CACHE NBL_SMOKE_FLOW PROPERTY STRINGS CONFIGURE_ONLY BUILD_ONLY) +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 "CONFIGURE_ONLY") +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" diff --git a/smoke/RunSmokeFlow.cmake b/smoke/RunSmokeFlow.cmake index f192e2a838..9350e6094e 100644 --- a/smoke/RunSmokeFlow.cmake +++ b/smoke/RunSmokeFlow.cmake @@ -1,10 +1,10 @@ if(NOT DEFINED FLOW) - message(FATAL_ERROR "FLOW is required. Allowed values: CONFIGURE_ONLY, BUILD_ONLY") + message(FATAL_ERROR "FLOW is required. Allowed values: MINIMALISTIC, CONFIGURE_ONLY, BUILD_ONLY") endif() string(TOUPPER "${FLOW}" FLOW) -if(NOT FLOW MATCHES "^(CONFIGURE_ONLY|BUILD_ONLY)$") - message(FATAL_ERROR "Invalid FLOW='${FLOW}'. Allowed values: CONFIGURE_ONLY, BUILD_ONLY") +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) @@ -44,12 +44,17 @@ 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=ON" + -D "NBL_SMOKE_INSTALL_SELFTEST=${_run_install_selftest}" ) run_cmd( @@ -68,21 +73,23 @@ run_cmd( -C "${CONFIG}" ) -file(REMOVE_RECURSE "${INSTALL_DIR}") +if(_run_install_selftest) + file(REMOVE_RECURSE "${INSTALL_DIR}") -run_cmd( - "${CMAKE_COMMAND}" - --install "${BUILD_DIR}" - --config "${CONFIG}" - --prefix "${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}" -) + run_cmd( + "${CTEST_BIN}" + --verbose + --test-dir "${INSTALL_DIR}" + --force-new-ctest-process + --output-on-failure + --no-tests=error + -C "${CONFIG}" + ) +endif() From 4742eabf207a5fb2ff26d0317acd793e638491bd Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Feb 2026 02:19:03 +0100 Subject: [PATCH 13/14] Add minimalistic smoke flow to CI --- .github/workflows/build-nabla.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index fea595428f..e14586510b 100644 --- a/.github/workflows/build-nabla.yml +++ b/.github/workflows/build-nabla.yml @@ -385,6 +385,9 @@ jobs: if (-not (Test-Path "smoke/build-ct/install")) { throw "smoke/build-ct/install not found" } tree.com smoke /F + - name: Smoke Flow MINIMALISTIC + run: cmake -D FLOW=MINIMALISTIC -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake + - name: Smoke Flow CONFIGURE_ONLY run: cmake -D FLOW=CONFIGURE_ONLY -D CONFIG=${{ matrix.config }} -P smoke/RunSmokeFlow.cmake From 1b6611b4cb29d83a9e1e5eea73201060098ed7fc Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Feb 2026 11:44:08 +0100 Subject: [PATCH 14/14] Fix runtime sync trigger and Docker setup retries --- .github/workflows/build-nabla.yml | 32 ++++++++++++++++++++++++++++--- .github/workflows/run-nsc.yml | 32 ++++++++++++++++++++++++++++--- cmake/NablaConfig.cmake.in | 9 +++++++-- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-nabla.yml b/.github/workflows/build-nabla.yml index e14586510b..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 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 b67293d5ab..c91f6dfaec 100644 --- a/cmake/NablaConfig.cmake.in +++ b/cmake/NablaConfig.cmake.in @@ -286,13 +286,18 @@ function(_nbl_runtime_modules_add_configure_sync_rule_for_pairs _CFG_DST_PAIRS _ cmake_path(GET _nabla_runtime_file FILENAME _nabla_runtime_name) cmake_path(GET _dxc_runtime_file FILENAME _dxc_runtime_name) - file(COPY_FILE "${_nabla_runtime_file}" "${_runtime_modules_dst}/${_nabla_runtime_name}" ONLY_IF_DIFFERENT INPUT_MAY_BE_RECENT) - file(COPY_FILE "${_dxc_runtime_file}" "${_runtime_modules_dst}/${_dxc_runtime_name}" ONLY_IF_DIFFERENT INPUT_MAY_BE_RECENT) + 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()