diff --git a/CHANGELOG.md b/CHANGELOG.md index 4609b4da2b..59667d0ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ♻️🏁 Remove Windows-specific restrictions for dynamic QDMI device library handling ([#1406]) ([**@burgholzer**]) - ♻️ Migrate Python bindings from `pybind11` to `nanobind` ([#1383]) ([**@denialhaag**], [**@burgholzer**]) - 📦️ Provide Stable ABI wheels for Python 3.12+ ([#1383]) ([**@burgholzer**], [**@denialhaag**]) - 🚚 Create dedicated `mqt.core.na` submodule to closely follow the structure of other submodules ([#1383]) ([**@burgholzer**]) @@ -286,6 +287,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1406]: https://github.com/munich-quantum-toolkit/core/pull/1406 [#1402]: https://github.com/munich-quantum-toolkit/core/pull/1402 [#1385]: https://github.com/munich-quantum-toolkit/core/pull/1385 [#1384]: https://github.com/munich-quantum-toolkit/core/pull/1384 diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 3f1b00d1cc..668f0a2569 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -420,9 +420,7 @@ All authentication parameters are optional and can be provided as keyword argume operation.def(nb::self != nb::self, nb::sig("def __ne__(self, arg: object, /) -> bool")); -#ifndef _WIN32 - // Module-level function to add dynamic device libraries on non-Windows - // systems + // Module-level function to add dynamic device libraries m.def( "add_dynamic_device_library", [](const std::string& libraryPath, const std::string& prefix, @@ -463,9 +461,6 @@ All authentication parameters are optional and can be provided as keyword argume This function loads a shared library (.so, .dll, or .dylib) that implements a QDMI device interface and makes it available for use in sessions. -Note: - This function is only available on non-Windows platforms. - Args: library_path: Path to the shared library file to load. prefix: Function prefix used by the library (e.g., "MY_DEVICE"). @@ -499,7 +494,6 @@ This function loads a shared library (.so, .dll, or .dylib) that implements a QD >>> from mqt.core.plugins.qiskit import QDMIBackend >>> backend = QDMIBackend(device=device))pb"); -#endif // _WIN32 } } // namespace mqt diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 7c477f0642..1ee96eac1c 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -116,16 +116,14 @@ struct DeviceLibrary { virtual ~DeviceLibrary() = default; }; -#ifndef _WIN32 /** * @brief Definition of the dynamic device library. * @details This class is used to load the QDMI device interface functions * from a dynamic library at runtime. It inherits from DeviceLibrary and * overrides the constructor and destructor to open and close the library. - * @note This class is only available on non-Windows platforms. */ class DynamicDeviceLibrary final : public DeviceLibrary { - /// @brief Handle to the dynamic library returned by `dlopen`. + /// @brief Handle to the dynamic library void* libHandle_; public: @@ -145,7 +143,6 @@ class DynamicDeviceLibrary final : public DeviceLibrary { */ ~DynamicDeviceLibrary() override; }; -#endif // _WIN32 // Macro to define a static library class that inherits from DeviceLibrary. // It binds all device library functions to the functions of the static library. @@ -428,7 +425,6 @@ class Driver final { /// @brief Destructor for the Driver class. ~Driver(); -#ifndef _WIN32 /** * @brief Loads a dynamic device library and adds it to the driver. * @@ -439,8 +435,6 @@ class Driver final { * * @return A pointer to the newly created device. * - * @note This function is only available on non-Windows platforms. - * * @throws std::runtime_error If the device cannot be initialized. * @throws std::bad_alloc If memory allocation fails during the process. */ @@ -448,7 +442,7 @@ class Driver final { const std::string& prefix, const DeviceSessionConfig& config = {}) -> QDMI_Device; -#endif // _WIN32 + /** * @brief Allocates a new session. * @see QDMI_session_alloc diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 60d00b5514..6592f50470 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -364,9 +364,6 @@ def add_dynamic_device_library( This function loads a shared library (.so, .dll, or .dylib) that implements a QDMI device interface and makes it available for use in sessions. - Note: - This function is only available on non-Windows platforms. - Args: library_path: Path to the shared library file to load. prefix: Function prefix used by the library (e.g., "MY_DEVICE"). diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index f6d674337d..a6912fd346 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -31,7 +31,9 @@ #include #include -#ifndef _WIN32 +#ifdef _WIN32 +#include +#else #include #endif // _WIN32 @@ -83,10 +85,19 @@ DEFINE_STATIC_LIBRARY(MQT_NA) DEFINE_STATIC_LIBRARY(MQT_DDSIM) DEFINE_STATIC_LIBRARY(MQT_SC) -#ifndef _WIN32 +#ifdef _WIN32 +#define DL_OPEN(lib) LoadLibraryA((lib)) +#define DL_SYM(lib, sym) GetProcAddress(static_cast((lib)), (sym)) +#define DL_CLOSE(lib) FreeLibrary(static_cast((lib))) +#else +#define DL_OPEN(lib) dlopen((lib), RTLD_NOW | RTLD_LOCAL) +#define DL_SYM(lib, sym) dlsym((lib), (sym)) +#define DL_CLOSE(lib) dlclose((lib)) +#endif + DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, const std::string& prefix) - : libHandle_(dlopen(libName.c_str(), RTLD_NOW | RTLD_LOCAL)) { + : libHandle_(DL_OPEN(libName.c_str())) { if (libHandle_ == nullptr) { throw std::runtime_error("Couldn't open the device library: " + libName); } @@ -98,7 +109,7 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, { \ const std::string symbolName = std::string(prefix) + "_QDMI_" + #symbol; \ (symbol) = reinterpret_cast( \ - dlsym(libHandle_, symbolName.c_str())); \ + DL_SYM(libHandle_, symbolName.c_str())); \ if ((symbol) == nullptr) { \ throw std::runtime_error("Failed to load symbol: " + symbolName); \ } \ @@ -131,7 +142,7 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, LOAD_DYNAMIC_SYMBOL(device_session_query_operation_property) // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } catch (const std::exception&) { - dlclose(libHandle_); + DL_CLOSE(libHandle_); throw; } // initialize the device @@ -145,10 +156,13 @@ DynamicDeviceLibrary::~DynamicDeviceLibrary() { } // close the dynamic library if (libHandle_ != nullptr) { - dlclose(libHandle_); + DL_CLOSE(libHandle_); } } -#endif // _WIN32 + +#undef DL_OPEN +#undef DL_SYM +#undef DL_CLOSE } // namespace qdmi QDMI_Device_impl_d::QDMI_Device_impl_d( @@ -384,7 +398,6 @@ Driver::~Driver() { devices_.clear(); } -#ifndef _WIN32 auto Driver::addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix, const DeviceSessionConfig& config) @@ -393,7 +406,6 @@ auto Driver::addDynamicDeviceLibrary(const std::string& libName, std::make_unique(libName, prefix), config)); return devices_.back().get(); } -#endif auto Driver::sessionAlloc(QDMI_Session* session) -> int { if (session == nullptr) { diff --git a/src/qdmi/na/CMakeLists.txt b/src/qdmi/na/CMakeLists.txt index e59de2206c..5faf76cea3 100644 --- a/src/qdmi/na/CMakeLists.txt +++ b/src/qdmi/na/CMakeLists.txt @@ -151,47 +151,44 @@ if(NOT TARGET ${TARGET_NAME}) # in the tests add_library(qdmi::mqt_na_device ALIAS ${TARGET_NAME}) - # Do not build dynamic NA device on Windows because it cannot be used anyways in the current setup - if(NOT WIN32) - set(DYN_TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-na-device-dyn) - if(NOT TARGET ${DYN_TARGET_NAME}) - # Set prefix for QDMI - set(QDMI_PREFIX "MQT_NA_DYN") - # Generate prefixed QDMI headers - generate_prefixed_qdmi_headers(${QDMI_PREFIX}) - file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_na_dyn_qdmi/**.hpp) - # Add dynamic library target - add_library(${DYN_TARGET_NAME} SHARED) - add_dependencies(${DYN_TARGET_NAME} ${TARGET_NAME}) - # add sources to target - target_sources(${DYN_TARGET_NAME} PRIVATE DynDevice.cpp) - # add headers using file sets - target_sources( - ${DYN_TARGET_NAME} - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_CORE_INCLUDE_BUILD_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/include - FILES - ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp - ${QDMI_HDRS}) - # add link libraries - target_link_libraries( - ${DYN_TARGET_NAME} - PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings - spdlog::spdlog) - # set c++ standard - target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) - # set versioning information - set_target_properties( - ${DYN_TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMINaDeviceDyn) - add_library(MQT::CoreQDMINaDeviceDyn ALIAS ${DYN_TARGET_NAME}) - endif() + set(DYN_TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-na-device-dyn) + if(NOT TARGET ${DYN_TARGET_NAME}) + # Set prefix for QDMI + set(QDMI_PREFIX "MQT_NA_DYN") + # Generate prefixed QDMI headers + generate_prefixed_qdmi_headers(${QDMI_PREFIX}) + file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_na_dyn_qdmi/**.hpp) + # Add dynamic library target + add_library(${DYN_TARGET_NAME} SHARED) + add_dependencies(${DYN_TARGET_NAME} ${TARGET_NAME}) + # add sources to target + target_sources(${DYN_TARGET_NAME} PRIVATE DynDevice.cpp) + # add headers using file sets + target_sources( + ${DYN_TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/include + FILES + ${DEVICE_HDR} + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp + ${QDMI_HDRS}) + # add link libraries + target_link_libraries( + ${DYN_TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) + # set c++ standard + target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) + # set versioning information + set_target_properties( + ${DYN_TARGET_NAME} + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + EXPORT_NAME CoreQDMINaDeviceDyn) + add_library(MQT::CoreQDMINaDeviceDyn ALIAS ${DYN_TARGET_NAME}) endif() endif() diff --git a/src/qdmi/sc/CMakeLists.txt b/src/qdmi/sc/CMakeLists.txt index e477eedd23..e0b2a94096 100644 --- a/src/qdmi/sc/CMakeLists.txt +++ b/src/qdmi/sc/CMakeLists.txt @@ -151,47 +151,44 @@ if(NOT TARGET ${TARGET_NAME}) # in the tests add_library(qdmi::mqt_sc_device ALIAS ${TARGET_NAME}) - # Do not build dynamic SC device on Windows because it cannot be used anyways in the current setup - if(NOT WIN32) - set(DYN_TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-dyn) - if(NOT TARGET ${DYN_TARGET_NAME}) - # Set prefix for QDMI - set(QDMI_PREFIX "MQT_SC_DYN") - # Generate prefixed QDMI headers - generate_prefixed_qdmi_headers(${QDMI_PREFIX}) - file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_sc_dyn_qdmi/**.hpp) - # Add dynamic library target - add_library(${DYN_TARGET_NAME} SHARED) - add_dependencies(${DYN_TARGET_NAME} ${TARGET_NAME}) - # add sources to target - target_sources(${DYN_TARGET_NAME} PRIVATE DynDevice.cpp) - # add headers using file sets - target_sources( - ${DYN_TARGET_NAME} - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_CORE_INCLUDE_BUILD_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/include - FILES - ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/sc/Device.hpp - ${QDMI_HDRS}) - # add link libraries - target_link_libraries( - ${DYN_TARGET_NAME} - PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings - spdlog::spdlog) - # set c++ standard - target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) - # set versioning information - set_target_properties( - ${DYN_TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMIScDeviceDyn) - add_library(MQT::CoreQDMIScDeviceDyn ALIAS ${DYN_TARGET_NAME}) - endif() + set(DYN_TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-dyn) + if(NOT TARGET ${DYN_TARGET_NAME}) + # Set prefix for QDMI + set(QDMI_PREFIX "MQT_SC_DYN") + # Generate prefixed QDMI headers + generate_prefixed_qdmi_headers(${QDMI_PREFIX}) + file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_sc_dyn_qdmi/**.hpp) + # Add dynamic library target + add_library(${DYN_TARGET_NAME} SHARED) + add_dependencies(${DYN_TARGET_NAME} ${TARGET_NAME}) + # add sources to target + target_sources(${DYN_TARGET_NAME} PRIVATE DynDevice.cpp) + # add headers using file sets + target_sources( + ${DYN_TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/include + FILES + ${DEVICE_HDR} + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/sc/Device.hpp + ${QDMI_HDRS}) + # add link libraries + target_link_libraries( + ${DYN_TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) + # set c++ standard + target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) + # set versioning information + set_target_properties( + ${DYN_TARGET_NAME} + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + EXPORT_NAME CoreQDMIScDeviceDyn) + add_library(MQT::CoreQDMIScDeviceDyn ALIAS ${DYN_TARGET_NAME}) endif() endif() diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 5d92ac84a7..6dabf4618a 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -10,14 +10,13 @@ from __future__ import annotations -import sys import tempfile from pathlib import Path from typing import cast import pytest -from mqt.core.fomac import Device, Job, ProgramFormat, Session +from mqt.core.fomac import Device, Job, ProgramFormat, Session, add_dynamic_device_library def _get_devices() -> list[Device]: @@ -803,15 +802,7 @@ def test_session_multiple_instances() -> None: assert len(devices1) == len(devices2) -if sys.platform != "win32": - from mqt.core import fomac - - def test_add_dynamic_device_library_exists() -> None: - """Test that add_dynamic_device_library function exists on non-Windows platforms.""" - assert hasattr(fomac, "add_dynamic_device_library") - assert callable(fomac.add_dynamic_device_library) - - def test_add_dynamic_device_library_nonexistent_library() -> None: - """Test that loading a non-existent library raises an error.""" - with pytest.raises(RuntimeError): - fomac.add_dynamic_device_library("/nonexistent/lib.so", "PREFIX") +def test_add_dynamic_device_library_nonexistent_library() -> None: + """Test that loading a non-existent library raises an error.""" + with pytest.raises(RuntimeError): + add_dynamic_device_library("/nonexistent/lib.so", "PREFIX") diff --git a/test/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt index 6934a23d54..007b15563d 100644 --- a/test/qdmi/CMakeLists.txt +++ b/test/qdmi/CMakeLists.txt @@ -13,12 +13,10 @@ set(TARGET_NAME mqt-core-qdmi-driver-test) if(TARGET MQT::CoreQDMIDriver) package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_driver.cpp) - if(NOT WIN32) - add_dependencies(${TARGET_NAME} MQT::CoreQDMINaDeviceDyn MQT::CoreQDMIScDeviceDyn) - target_compile_definitions( - ${TARGET_NAME} - PRIVATE - "DYN_DEV_LIBS=std::array{ std::pair{\"$\", \"MQT_NA_DYN\"}, std::pair{\"$\", \"MQT_SC_DYN\"} }" - ) - endif() + add_dependencies(${TARGET_NAME} MQT::CoreQDMINaDeviceDyn MQT::CoreQDMIScDeviceDyn) + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + "DYN_DEV_LIBS=std::array{ std::pair{\"$\", \"MQT_NA_DYN\"}, std::pair{\"$\", \"MQT_SC_DYN\"} }" + ) endif() diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index 61c64b4f9c..5aab77c8ef 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -48,7 +48,6 @@ class DriverTest : public testing::TestWithParam { protected: QDMI_Session session = nullptr; QDMI_Device device = nullptr; -#ifndef _WIN32 static void SetUpTestSuite() { // Load dynamic libraries with default device session configuration const qdmi::DeviceSessionConfig config; @@ -58,7 +57,6 @@ class DriverTest : public testing::TestWithParam { } }); } -#endif // _WIN32 void SetUp() override { const auto& deviceName = GetParam(); @@ -502,16 +500,10 @@ TEST_P(DriverTest, QueryNeedsCalibration) { EXPECT_EQ(ret, QDMI_SUCCESS); EXPECT_THAT(needsCalibration, testing::AnyOf(0, 1)); } -#ifdef _WIN32 -constexpr std::array DEVICES{"MQT NA Default QDMI Device", - "MQT Core DDSIM QDMI Device", - "MQT SC Default QDMI Device"}; -#else constexpr std::array DEVICES{ "MQT NA Default QDMI Device", "MQT NA Dynamic QDMI Device", "MQT Core DDSIM QDMI Device", "MQT SC Default QDMI Device", "MQT SC Dynamic QDMI Device"}; -#endif // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P( // Custom instantiation name @@ -530,7 +522,6 @@ INSTANTIATE_TEST_SUITE_P( return name; }); -#ifndef _WIN32 TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithBaseUrl) { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:8080"; @@ -728,7 +719,6 @@ TEST(DynamicDeviceLibraryTest, addDynamicDeviceLibraryReturnsDevice) { EXPECT_GT(size, 0) << "Device should have a non-empty name"; } } -#endif // _WIN32 INSTANTIATE_TEST_SUITE_P( // Custom instantiation name