From c40c82a639722015411725965e2ee576b44856ab Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 21:13:29 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Add=20binary-safe=20FoMaC=20job?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preserve exact byte payloads for binary QDMI programs while retaining null-terminated text submission. Expose byte submission and retrieval in Python and exercise a real QIR module through DDSIM. Assisted-by: GPT-5.6 via Codex --- bindings/fomac/fomac.cpp | 52 ++++++++++++++++++++++++++++---- include/mqt-core/fomac/FoMaC.hpp | 32 ++++++++++++++++++-- python/mqt/core/fomac.pyi | 22 +++++++++++++- src/fomac/FoMaC.cpp | 46 ++++++++++++++++++++++------ test/python/fomac/test_fomac.py | 25 +++++++++++++++ 5 files changed, 158 insertions(+), 19 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index fcdfd0ddf8..372b255ce3 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -244,6 +245,14 @@ when the custom slot is unsupported.)pb"); job.def_prop_ro("program", &fomac::Job::getProgram, "The submitted program."); + job.def_prop_ro( + "program_bytes", + [](const fomac::Job& self) { + const auto program = self.getProgramBytes(); + return nb::bytes(program.data(), program.size()); + }, + "The exact bytes of the submitted program."); + job.def_prop_ro("num_shots", &fomac::Job::getNumShots, "The number of shots."); @@ -383,12 +392,43 @@ The caller must provide the type documented by the device implementation. Use ``bytes`` to retrieve the value without interpretation. Returns ``None`` when the custom slot is unsupported.)pb"); - device.def("submit_job", &fomac::Device::submitJob, "program"_a, - "program_format"_a, "num_shots"_a, nb::kw_only(), - "custom1"_a = nb::none(), "custom2"_a = nb::none(), - "custom3"_a = nb::none(), "custom4"_a = nb::none(), - "custom5"_a = nb::none(), nb::rv_policy::reference_internal, - "Submits a job to the device."); + device.def( + "submit_job", + [](const fomac::Device& self, const std::string& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) { + return self.submitJob(program, format, numShots, custom1, custom2, + custom3, custom4, custom5); + }, + "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + "Submits a text job to the device."); + + device.def( + "submit_job", + [](const fomac::Device& self, const nb::bytes& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) { + const auto bytes = std::span{ + static_cast(program.data()), program.size()}; + return self.submitJob(bytes, format, numShots, custom1, custom2, + custom3, custom4, custom5); + }, + "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + "Submits an exact byte payload to the device."); device.def("__repr__", [](const fomac::Device& dev) { return ""; diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 25f07ce68f..d8a4571c8d 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -505,7 +506,12 @@ class Device { std::to_string(static_cast(property))); } - /// @see QDMI_job_submit + /** + * @brief Submits a textual program. + * @details The terminating null byte required by QDMI text formats is + * included in the submitted payload. + * @see QDMI_job_submit + */ [[nodiscard]] Job submitJob( const std::string& program, QDMI_Program_Format format, size_t numShots, const std::optional& custom1 = std::nullopt, @@ -514,6 +520,21 @@ class Device { const std::optional& custom4 = std::nullopt, const std::optional& custom5 = std::nullopt) const; + /** + * @brief Submits a binary program. + * @details The bytes are submitted exactly as provided without appending a + * null byte. + * @see QDMI_job_submit + */ + [[nodiscard]] Job submitJob( + std::span program, QDMI_Program_Format format, + size_t numShots, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; + auto operator<=>(const Device&) const noexcept = default; private: @@ -625,9 +646,16 @@ class Job { /// Get the program format [[nodiscard]] QDMI_Program_Format getProgramFormat() const; - /// Get the program to be executed + /** + * @brief Gets a textual program without its terminating null byte. + * @throws std::invalid_argument If the device does not return a + * null-terminated payload. + */ [[nodiscard]] std::string getProgram() const; + /// Gets the submitted program bytes exactly as returned by the device. + [[nodiscard]] std::vector getProgramBytes() const; + /// Get the number of shots [[nodiscard]] size_t getNumShots() const; diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 3e8bd79642..cb3415b39e 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -170,6 +170,10 @@ class Job: def program(self) -> str: """The submitted program.""" + @property + def program_bytes(self) -> bytes: + """The exact bytes of the submitted program.""" + @property def num_shots(self) -> int: """The number of shots.""" @@ -331,6 +335,7 @@ class Device: when the custom slot is unsupported. """ + @overload def submit_job( self, program: str, @@ -343,7 +348,22 @@ class Device: custom4: str | bool | float | None = None, custom5: str | bool | float | None = None, ) -> Job: - """Submits a job to the device.""" + """Submits a text job to the device.""" + + @overload + def submit_job( + self, + program: bytes, + program_format: ProgramFormat, + num_shots: int, + *, + custom1: str | bool | float | None = None, + custom2: str | bool | float | None = None, + custom3: str | bool | float | None = None, + custom4: str | bool | float | None = None, + custom5: str | bool | float | None = None, + ) -> Job: + """Submits an exact byte payload to the device.""" def __eq__(self, arg: object, /) -> bool: ... def __ne__(self, arg: object, /) -> bool: ... diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 4b42160f9b..d311e5a42b 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -321,6 +322,19 @@ Job Device::submitJob(const std::string& program, const std::optional& custom3, const std::optional& custom4, const std::optional& custom5) const { + const auto bytes = std::as_bytes( + std::span(program.c_str(), static_cast(program.size() + 1))); + return submitJob(bytes, format, numShots, custom1, custom2, custom3, custom4, + custom5); +} + +Job Device::submitJob(const std::span program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { QDMI_Job job = nullptr; qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; @@ -329,10 +343,10 @@ Job Device::submitJob(const std::string& program, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(format), &format), "Setting program format"); - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, - program.size() + 1, program.c_str()), - "Setting program"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_PROGRAM, + program.size(), program.data()), + "Setting program"); qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(numShots), &numShots), @@ -423,21 +437,33 @@ QDMI_Program_Format Job::getProgramFormat() const { return format; } -std::string Job::getProgram() const { +std::vector Job::getProgramBytes() const { size_t size = 0; qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_PROGRAM, 0, nullptr, &size), "Querying program size"); - std::string program(size - 1, '\0'); - qdmi::throwIfError(QDMI_job_query_property(job_.get(), - QDMI_JOB_PROPERTY_PROGRAM, size, - program.data(), nullptr), - "Querying program"); + std::vector program(size); + if (size != 0) { + qdmi::throwIfError(QDMI_job_query_property(job_.get(), + QDMI_JOB_PROPERTY_PROGRAM, size, + program.data(), nullptr), + "Querying program"); + } return program; } +std::string Job::getProgram() const { + const auto program = getProgramBytes(); + if (program.empty() || program.back() != std::byte{0}) { + throw std::invalid_argument( + "Cannot decode program as a null-terminated string; use " + "getProgramBytes() for binary payloads"); + } + return {reinterpret_cast(program.data()), program.size() - 1}; +} + size_t Job::getNumShots() const { size_t numShots = 0; qdmi::throwIfError( diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index dffc65bf3a..73efc54037 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -489,6 +489,31 @@ def test_device_executes_qir_program(ddsim_device: Device) -> None: assert sum(job.get_counts().values()) == 10 +def test_device_executes_binary_qir_program(ddsim_device: Device) -> None: + """Submit and retrieve an exact QIR module byte payload.""" + from mqt.core.mlir import OutputFormat, compile_program # ruff:ignore[import-outside-top-level] + + qasm3_program = """ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c; +h q[0]; +cx q[0], q[1]; +c = measure q; +""" + program = compile_program(qasm3_program, output=OutputFormat.QIR_BASE) + program_bytes = program.to_bitcode() + assert ProgramFormat.QIR_BASE_MODULE in ddsim_device.supported_program_formats() + + job = ddsim_device.submit_job(program_bytes, ProgramFormat.QIR_BASE_MODULE, num_shots=10) + assert job.program_bytes == program_bytes + job.wait() + + assert job.check() == Job.Status.DONE + assert sum(job.get_counts().values()) == 10 + + def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: """Test that submit_job forwards custom job parameters to DDSIM.""" with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): From 6320365cd419d1dc3117cbdca7696bbc50cad54c Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 21:13:39 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=94=A7=20Support=20embedded=20QDMI=20?= =?UTF-8?q?QIR=20consumers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow DDSIM's QIR JIT to reuse a parent LLVM/MLIR installation without building MQT Core's compiler dialects. Reuse parent-provided spdlog targets, export capability properties, and verify the configuration with a nested consumer build. Assisted-by: GPT-5.6 via Codex --- CMakeLists.txt | 16 +++--- UPGRADING.md | 8 ++- cmake/ExternalDependencies.cmake | 57 +++++++++++++------- cmake/mqt-core-config.cmake.in | 2 +- docs/qir/index.md | 31 +++++++++-- src/fomac/CMakeLists.txt | 8 +++ src/qdmi/devices/dd/CMakeLists.txt | 6 +++ test/CMakeLists.txt | 37 +++++++++++++ test/cmake/qdmi-consumer/CMakeLists.txt | 70 +++++++++++++++++++++++++ test/cmake/qdmi-consumer/main.cpp | 26 +++++++++ 10 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 test/cmake/qdmi-consumer/CMakeLists.txt create mode 100644 test/cmake/qdmi-consumer/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bc895e4c7..1c1bae855c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,14 @@ project( set(MQT_CORE_TARGET_NAME "mqt-core") -if(BUILD_MQT_CORE_MLIR) +option(BUILD_MQT_CORE_QIR_RUNNER "Build the QIR runner of the MQT Core project" + ${BUILD_MQT_CORE_MLIR}) +option(BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + "Enable QIR program format support for the DDSIM QDMI Device" ${BUILD_MQT_CORE_MLIR}) + +if(BUILD_MQT_CORE_MLIR + OR BUILD_MQT_CORE_QIR_RUNNER + OR BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) include(SetupMLIR) endif() @@ -120,13 +127,6 @@ if(MQT_CORE_INSTALL) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) endif() -cmake_dependent_option(BUILD_MQT_CORE_QIR_RUNNER "Build the QIR runner of the MQT Core project" ON - "BUILD_MQT_CORE_MLIR" OFF) - -cmake_dependent_option( - BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR "Enable QIR program format support for the DDSIM QDMI Device" - ON "BUILD_MQT_CORE_MLIR" OFF) - # add main library code add_subdirectory(src) diff --git a/UPGRADING.md b/UPGRADING.md index 043fd920e2..2fd6689a74 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -28,8 +28,12 @@ For local development, you can configure `MLIR_DIR` once in a repository-local Core's CMake setup will pick this up automatically when `MLIR_DIR` is not otherwise provided. -The MLIR components can still be manually disabled by passing -`-DBUILD_MQT_CORE_MLIR=OFF` to CMake. +The MQT Core compiler dialects can still be manually disabled by passing +`-DBUILD_MQT_CORE_MLIR=OFF` to CMake. The QIR runner and DDSIM QIR support +follow that option by default, but can now be selected independently through +`BUILD_MQT_CORE_QIR_RUNNER` and `BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR`. To remove +the LLVM/MLIR dependency from an existing build directory, explicitly disable +all three options. Known limitations: diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index b361a7e9ff..ad0c13da3e 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -101,24 +101,40 @@ FetchContent_Declare( FIND_PACKAGE_ARGS ${QDMI_MINIMUM_VERSION}) list(APPEND FETCH_PACKAGES qdmi) -set(SPDLOG_VERSION - 1.17.0 - CACHE STRING "spdlog version") -set(SPDLOG_URL https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz) -# Add position independent code for spdlog, this is required for python bindings on linux -set(SPDLOG_BUILD_PIC ON) -set(SPDLOG_SYSTEM_INCLUDES - ON - CACHE INTERNAL "Treat the library headers like system headers") -cmake_dependent_option(MQT_CORE_SPDLOG_INSTALL "Install spdlog library" ON "MQT_CORE_INSTALL" OFF) -# Disable upstream spdlog install rules and install with explicit MQT components below. -set(SPDLOG_INSTALL - OFF - CACHE BOOL "Disable upstream spdlog install rules; handled by mqt-core" FORCE) -cmake_dependent_option(SPDLOG_BUILD_SHARED "Build spdlog as shared library" ON - "BUILD_MQT_CORE_SHARED_LIBS" OFF) -FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_VERSION}) -list(APPEND FETCH_PACKAGES spdlog) +set(SPDLOG_MINIMUM_VERSION + 1.15.3 + CACHE STRING "Minimum spdlog version") +set(MQT_CORE_MANAGES_SPDLOG OFF) +if(TARGET spdlog::spdlog) + get_target_property(MQT_CORE_SPDLOG_TARGET_VERSION spdlog::spdlog VERSION) + if(MQT_CORE_SPDLOG_TARGET_VERSION AND MQT_CORE_SPDLOG_TARGET_VERSION VERSION_LESS + SPDLOG_MINIMUM_VERSION) + message( + FATAL_ERROR + "The existing spdlog::spdlog target provides version ${MQT_CORE_SPDLOG_TARGET_VERSION}, " + "but MQT Core requires at least ${SPDLOG_MINIMUM_VERSION}.") + endif() +else() + set(SPDLOG_VERSION + 1.17.0 + CACHE STRING "spdlog version") + set(SPDLOG_URL https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz) + # Add position independent code for spdlog, this is required for Python bindings on Linux. + set(SPDLOG_BUILD_PIC ON) + set(SPDLOG_SYSTEM_INCLUDES + ON + CACHE INTERNAL "Treat the library headers like system headers") + cmake_dependent_option(MQT_CORE_SPDLOG_INSTALL "Install spdlog library" ON "MQT_CORE_INSTALL" OFF) + # Disable upstream spdlog install rules and install with explicit MQT components below. + set(SPDLOG_INSTALL + OFF + CACHE BOOL "Disable upstream spdlog install rules; handled by mqt-core" FORCE) + cmake_dependent_option(SPDLOG_BUILD_SHARED "Build spdlog as shared library" ON + "BUILD_MQT_CORE_SHARED_LIBS" OFF) + FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_MINIMUM_VERSION}) + list(APPEND FETCH_PACKAGES spdlog) + set(MQT_CORE_MANAGES_SPDLOG ON) +endif() # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) @@ -182,7 +198,7 @@ if(MQT_CORE_JSON_INSTALL AND TARGET nlohmann_json) endif() # Ensure external shared libraries end up in a common lib layout used by our RUNPATH -if(TARGET spdlog) +if(MQT_CORE_MANAGES_SPDLOG AND TARGET spdlog) set_target_properties( spdlog PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}" @@ -191,7 +207,8 @@ if(TARGET spdlog) endif() # Install spdlog with explicit MQT components. -if(MQT_CORE_SPDLOG_INSTALL +if(MQT_CORE_MANAGES_SPDLOG + AND MQT_CORE_SPDLOG_INSTALL AND TARGET spdlog AND TARGET spdlog_header_only) include(CMakePackageConfigHelpers) diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index f8d1982f46..ddb3b48449 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -14,7 +14,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") include(CMakeFindDependencyMacro) find_dependency(nlohmann_json) -find_dependency(spdlog) +find_dependency(spdlog @SPDLOG_MINIMUM_VERSION@) find_dependency(qdmi) option(MQT_CORE_WITH_GMP "Library is configured to use GMP" @MQT_CORE_WITH_GMP@) diff --git a/docs/qir/index.md b/docs/qir/index.md index 8ddac20831..bfeaa65ffd 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -22,8 +22,10 @@ See {cite:p}`stadeTowardsSupportingQIR2025` for more details. ### Building the Runner To build this tool, the CMake option `BUILD_MQT_CORE_QIR_RUNNER` has to be -enabled. It is enabled by default, but depends on `BUILD_MQT_CORE_MLIR` being -set. From the root of the repository, you can build the runner as follows: +enabled. It follows `BUILD_MQT_CORE_MLIR` by default, but can be enabled +independently when the project already provides a compatible LLVM/MLIR +installation. From the root of the repository, you can build the runner as +follows: ```bash cmake -S . -B build -DBUILD_MQT_CORE_QIR_RUNNER=ON -DBUILD_MQT_CORE_MLIR=ON @@ -59,4 +61,27 @@ The QDMI Device accepts jobs in the following program formats: QASM2, QASM3, QIR Base/Adaptive Profile Module (LLVM bitcode), and QIR Base/Adaptive Profile String (LLVM assembly). These QIR formats are only supported when the `BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR` CMake option is enabled. It is enabled by -default, but depends on `BUILD_MQT_CORE_MLIR` being set. +default when `BUILD_MQT_CORE_MLIR` is enabled, but can be selected +independently. This lets an embedding project reuse its existing LLVM/MLIR +installation for the QIR JIT without building MQT Core's compiler dialects. + +FoMaC C++ applications submit textual programs through the +`Device::submitJob(const std::string&, ...)` overload, which includes the +terminating null byte required by QDMI. Binary module payloads use the +`Device::submitJob(std::span, ...)` overload instead. It +preserves embedded null bytes and submits exactly the span's size without +appending a terminator. `Job::getProgramBytes()` retrieves such a payload +without interpreting its format or removing terminal null bytes; the existing +`Job::getProgram()` remains the textual, null-terminated accessor. + +The `MQT::CoreFoMaC` CMake target advertises this API through the exported +`MQT_CORE_FOMAC_BINARY_PROGRAM_API` target property. The +`MQT::CoreQDMI_DDSIM_Device` target similarly reports whether it was built with +QIR support through `MQT_CORE_QDMI_DDSIM_WITH_QIR`. Embedding projects can use +these properties to reject an incompatible pre-existing MQT Core target during +configuration. + +The Python API follows the same distinction: pass `str` to `Device.submit_job` +for a textual program and `bytes` for an exact binary payload. +`Job.program_bytes` always returns the unmodified payload, while `Job.program` +expects a null-terminated UTF-8 text payload. diff --git a/src/fomac/CMakeLists.txt b/src/fomac/CMakeLists.txt index 631141c998..29d68bedce 100644 --- a/src/fomac/CMakeLists.txt +++ b/src/fomac/CMakeLists.txt @@ -25,6 +25,14 @@ if(NOT TARGET ${TARGET_NAME}) PUBLIC qdmi::qdmi MQT::CoreQDMICommon MQT::CoreQDMIDriver PRIVATE spdlog::spdlog) + # Allow embedding projects to reject a pre-existing FoMaC target that predates exact-byte program + # submission. + set_property(TARGET ${TARGET_NAME} PROPERTY MQT_CORE_FOMAC_BINARY_PROGRAM_API ON) + set_property( + TARGET ${TARGET_NAME} + APPEND + PROPERTY EXPORT_PROPERTIES MQT_CORE_FOMAC_BINARY_PROGRAM_API) + # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} diff --git a/src/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index ee4a2a3458..daf45fe16b 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -44,6 +44,12 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT MQT::CoreQIRRuntime) target_compile_definitions(${TARGET_NAME} PRIVATE BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR) endif() + set_property(TARGET ${TARGET_NAME} PROPERTY MQT_CORE_QDMI_DDSIM_WITH_QIR + ${BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR}) + set_property( + TARGET ${TARGET_NAME} + APPEND + PROPERTY EXPORT_PROPERTIES MQT_CORE_QDMI_DDSIM_WITH_QIR) # Make QDMI version available and ensure symbols are exported when building the library target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_VERSION="${QDMI_VERSION}" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6203a14e01..8f4927b65f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,5 +17,42 @@ add_subdirectory(qir) add_subdirectory(qdmi) add_subdirectory(fomac) +if(MLIR_FOUND) + set(MQT_CORE_QDMI_CONSUMER_SOURCE_DIR "${PROJECT_SOURCE_DIR}/test/cmake/qdmi-consumer") + set(MQT_CORE_QDMI_CONSUMER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-consumer") + set(MQT_CORE_QDMI_CONSUMER_ARGS + -G + "${CMAKE_GENERATOR}" + -S + "${MQT_CORE_QDMI_CONSUMER_SOURCE_DIR}" + -B + "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" + "-DMQT_CORE_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DPARENT_SPDLOG_SOURCE_DIR=${spdlog_SOURCE_DIR}" + "-DPARENT_SPDLOG_DIR=${spdlog_DIR}" + "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DMLIR_DIR=${MLIR_DIR}") + foreach(dependency IN ITEMS nlohmann_json boost_mp qdmi) + set(dependency_source_dir_variable "${dependency}_SOURCE_DIR") + if(DEFINED ${dependency_source_dir_variable}) + string(TOUPPER "${dependency}" dependency_upper) + list(APPEND MQT_CORE_QDMI_CONSUMER_ARGS + "-DFETCHCONTENT_SOURCE_DIR_${dependency_upper}=${${dependency_source_dir_variable}}") + endif() + endforeach() + + add_test(NAME mqt-core-cmake-qdmi-consumer-configure COMMAND ${CMAKE_COMMAND} --fresh + ${MQT_CORE_QDMI_CONSUMER_ARGS}) + set_tests_properties(mqt-core-cmake-qdmi-consumer-configure + PROPERTIES FIXTURES_SETUP mqt-core-cmake-qdmi-consumer) + + add_test(NAME mqt-core-cmake-qdmi-consumer-build + COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" --target + mqt-core-qdmi-consumer) + set_tests_properties(mqt-core-cmake-qdmi-consumer-build PROPERTIES FIXTURES_REQUIRED + mqt-core-cmake-qdmi-consumer) +endif() + # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/cmake/qdmi-consumer/CMakeLists.txt b/test/cmake/qdmi-consumer/CMakeLists.txt new file mode 100644 index 0000000000..e56fa578a5 --- /dev/null +++ b/test/cmake/qdmi-consumer/CMakeLists.txt @@ -0,0 +1,70 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +cmake_minimum_required(VERSION 3.24...4.2) + +project(mqt-core-qdmi-consumer LANGUAGES C CXX) + +if(NOT MQT_CORE_SOURCE_DIR) + message(FATAL_ERROR "MQT_CORE_SOURCE_DIR must point to the MQT Core source tree") +endif() + +if(EXISTS "${PARENT_SPDLOG_SOURCE_DIR}/CMakeLists.txt") + set(SPDLOG_BUILD_PIC ON) + set(SPDLOG_INSTALL OFF) + add_subdirectory("${PARENT_SPDLOG_SOURCE_DIR}" spdlog EXCLUDE_FROM_ALL) +elseif(PARENT_SPDLOG_DIR) + set(spdlog_DIR "${PARENT_SPDLOG_DIR}") + find_package(spdlog CONFIG REQUIRED) +else() + message(FATAL_ERROR "A parent spdlog source or package directory is required") +endif() + +set(MQT_CORE_INSTALL + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_BINDINGS + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_TESTS + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_BENCHMARKS + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_DOCUMENTATION + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_MLIR + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_QIR_RUNNER + OFF + CACHE BOOL "") +set(BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR + ON + CACHE BOOL "") + +add_subdirectory("${MQT_CORE_SOURCE_DIR}" mqt-core EXCLUDE_FROM_ALL) + +if(TARGET MLIRSupportMQT) + message(FATAL_ERROR "DDSIM QIR support unexpectedly added the MQT Core MLIR targets") +endif() + +get_target_property(fomac_binary_program_api MQT::CoreFoMaC MQT_CORE_FOMAC_BINARY_PROGRAM_API) +if(NOT fomac_binary_program_api) + message(FATAL_ERROR "FoMaC does not advertise exact-byte program submission") +endif() + +get_target_property(ddsim_with_qir MQT::CoreQDMI_DDSIM_Device MQT_CORE_QDMI_DDSIM_WITH_QIR) +if(NOT ddsim_with_qir) + message(FATAL_ERROR "DDSIM does not advertise QIR support") +endif() + +add_executable(mqt-core-qdmi-consumer main.cpp) +target_link_libraries(mqt-core-qdmi-consumer PRIVATE MQT::CoreFoMaC MQT::CoreQDMI_DDSIM_Device) diff --git a/test/cmake/qdmi-consumer/main.cpp b/test/cmake/qdmi-consumer/main.cpp new file mode 100644 index 0000000000..7cc71a01f9 --- /dev/null +++ b/test/cmake/qdmi-consumer/main.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "fomac/FoMaC.hpp" + +#include +#include + +int main() { + const auto submitBinaryProgram = [](const fomac::Device& device) { + constexpr std::array program{}; + return device.submitJob(program, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0); + }; + static_cast(submitBinaryProgram); + + fomac::Session session; + static_cast(session.getDevices()); + return 0; +} From 5e8e6dbd296dcd5873ddcae99ed53928602eeeb0 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 21:35:03 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20Enforce=20QDMI=20program=20r?= =?UTF-8?q?epresentation=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-5.6 via Codex --- bindings/fomac/fomac.cpp | 1 + docs/qir/index.md | 7 +++++-- python/mqt/core/fomac.pyi | 2 ++ src/fomac/FoMaC.cpp | 13 +++++++++++++ src/qdmi/devices/dd/Device.cpp | 3 +++ test/python/fomac/test_fomac.py | 14 ++++++++++++++ test/qdmi/devices/dd/job_parameters_test.cpp | 17 +++++++++++++++++ 7 files changed, 55 insertions(+), 2 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 372b255ce3..8d8abf7354 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -283,6 +283,7 @@ when the custom slot is unsupported.)pb"); .value("CALIBRATION", QDMI_PROGRAM_FORMAT_CALIBRATION) .value("QPY", QDMI_PROGRAM_FORMAT_QPY) .value("IQM_JSON", QDMI_PROGRAM_FORMAT_IQMJSON) + .value("BATCH_JOB", QDMI_PROGRAM_FORMAT_BATCHJOB) .value("CUSTOM1", QDMI_PROGRAM_FORMAT_CUSTOM1) .value("CUSTOM2", QDMI_PROGRAM_FORMAT_CUSTOM2) .value("CUSTOM3", QDMI_PROGRAM_FORMAT_CUSTOM3) diff --git a/docs/qir/index.md b/docs/qir/index.md index bfeaa65ffd..3cd350294e 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -72,7 +72,9 @@ terminating null byte required by QDMI. Binary module payloads use the preserves embedded null bytes and submits exactly the span's size without appending a terminator. `Job::getProgramBytes()` retrieves such a payload without interpreting its format or removing terminal null bytes; the existing -`Job::getProgram()` remains the textual, null-terminated accessor. +`Job::getProgram()` remains the textual, null-terminated accessor. It rejects +known binary and non-text formats based on their QDMI format identifier, even if +their payload happens to end in a null byte. The `MQT::CoreFoMaC` CMake target advertises this API through the exported `MQT_CORE_FOMAC_BINARY_PROGRAM_API` target property. The @@ -84,4 +86,5 @@ configuration. The Python API follows the same distinction: pass `str` to `Device.submit_job` for a textual program and `bytes` for an exact binary payload. `Job.program_bytes` always returns the unmodified payload, while `Job.program` -expects a null-terminated UTF-8 text payload. +expects a null-terminated UTF-8 text payload and rejects known binary or +non-text formats. diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index cb3415b39e..e0250bf3e0 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -219,6 +219,8 @@ class ProgramFormat(enum.Enum): IQM_JSON = 8 + BATCH_JOB = 9 + CUSTOM1 = 999999995 CUSTOM2 = 999999996 diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index d311e5a42b..20a65ee696 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -455,6 +455,19 @@ std::vector Job::getProgramBytes() const { } std::string Job::getProgram() const { + switch (getProgramFormat()) { + case QDMI_PROGRAM_FORMAT_QIRBASEMODULE: + case QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE: + case QDMI_PROGRAM_FORMAT_CALIBRATION: + case QDMI_PROGRAM_FORMAT_QPY: + case QDMI_PROGRAM_FORMAT_BATCHJOB: + throw std::invalid_argument( + "Cannot decode a binary or non-text program as a string; use " + "getProgramBytes()"); + default: + break; + } + const auto program = getProgramBytes(); if (program.empty() || program.back() != std::byte{0}) { throw std::invalid_argument( diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 8cdcfddc54..4e303dde4a 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -393,6 +393,9 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( // Text payloads include the trailing '\0' in `size`. // Strip it so it is not counted in the stored string's size. const auto* text = static_cast(value); + if (size == 0 || text[size - 1] != '\0') { + return QDMI_ERROR_INVALIDARGUMENT; + } program_ = std::string(text, size - 1); } else { // Binary payloads are stored exactly as received. diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 73efc54037..409ce57523 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -461,10 +461,22 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: assert job.program_format == ProgramFormat.QASM3 # The program should be preserved assert job.program == qasm3_program + assert job.program_bytes == qasm3_program.encode() + b"\0" # Num shots should match request assert job.num_shots == 100 +def test_program_format_includes_batch_job() -> None: + """Expose every standard QDMI program format.""" + assert ProgramFormat.BATCH_JOB.value == 9 + + +def test_device_rejects_unterminated_text_bytes(ddsim_device: Device) -> None: + """Reject byte payloads that do not satisfy QDMI's text contract.""" + with pytest.raises(ValueError, match=r"Setting program: Invalid argument\."): + ddsim_device.submit_job(b"OPENQASM 3.0;", ProgramFormat.QASM3, num_shots=1) + + def test_device_executes_qir_program(ddsim_device: Device) -> None: """Compile and execute a QIR program with the DDSIM device.""" # Keep this lazy to cover loading MLIR after the QIR-enabled device. @@ -508,6 +520,8 @@ def test_device_executes_binary_qir_program(ddsim_device: Device) -> None: job = ddsim_device.submit_job(program_bytes, ProgramFormat.QIR_BASE_MODULE, num_shots=10) assert job.program_bytes == program_bytes + with pytest.raises(ValueError, match="binary or non-text"): + _ = job.program job.wait() assert job.check() == Job.Status.DONE diff --git a/test/qdmi/devices/dd/job_parameters_test.cpp b/test/qdmi/devices/dd/job_parameters_test.cpp index 044b13ec1e..bdeb770390 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -85,6 +85,23 @@ TEST(JobParameters, SetAndQueryBasics) { EXPECT_EQ(program, qdmi_test::QASM3_BELL_SAMPLING); } +TEST(JobParameters, RejectsUnterminatedTextProgram) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + + constexpr QDMI_Program_Format fmt = QDMI_PROGRAM_FORMAT_QASM3; + ASSERT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), &fmt), + QDMI_SUCCESS); + + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, + strlen(qdmi_test::QASM3_BELL_SAMPLING), + qdmi_test::QASM3_BELL_SAMPLING), + QDMI_ERROR_INVALIDARGUMENT); +} + TEST(JobParameters, ProgramFormatSupport) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; From 775f2faf04052ebac17fcf63b6759d9d4817dbfb Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 21:47:36 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=90=9B=20Tighten=20QDMI=20integration?= =?UTF-8?q?=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-5.6 via Codex --- cmake/ExternalDependencies.cmake | 25 ++++--- docs/qir/index.md | 6 ++ include/mqt-core/fomac/FoMaC.hpp | 14 +++- src/fomac/FoMaC.cpp | 51 +++++++++++--- src/qdmi/devices/dd/Device.cpp | 3 +- test/CMakeLists.txt | 70 ++++++++++++++++++-- test/cmake/qdmi-consumer/main.cpp | 15 ++--- test/python/fomac/test_fomac.py | 22 ++++-- test/qdmi/devices/dd/job_parameters_test.cpp | 17 +++++ 9 files changed, 180 insertions(+), 43 deletions(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index ad0c13da3e..35e679b7a5 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -105,16 +105,7 @@ set(SPDLOG_MINIMUM_VERSION 1.15.3 CACHE STRING "Minimum spdlog version") set(MQT_CORE_MANAGES_SPDLOG OFF) -if(TARGET spdlog::spdlog) - get_target_property(MQT_CORE_SPDLOG_TARGET_VERSION spdlog::spdlog VERSION) - if(MQT_CORE_SPDLOG_TARGET_VERSION AND MQT_CORE_SPDLOG_TARGET_VERSION VERSION_LESS - SPDLOG_MINIMUM_VERSION) - message( - FATAL_ERROR - "The existing spdlog::spdlog target provides version ${MQT_CORE_SPDLOG_TARGET_VERSION}, " - "but MQT Core requires at least ${SPDLOG_MINIMUM_VERSION}.") - endif() -else() +if(NOT TARGET spdlog::spdlog) set(SPDLOG_VERSION 1.17.0 CACHE STRING "spdlog version") @@ -139,6 +130,20 @@ endif() # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) +get_target_property(MQT_CORE_SPDLOG_TARGET_VERSION spdlog::spdlog VERSION) +if(NOT MQT_CORE_SPDLOG_TARGET_VERSION OR MQT_CORE_SPDLOG_TARGET_VERSION MATCHES "-NOTFOUND$") + set(MQT_CORE_SPDLOG_TARGET_VERSION "${spdlog_VERSION}") +endif() +if(NOT MQT_CORE_SPDLOG_TARGET_VERSION) + message(FATAL_ERROR "Cannot determine the version of the existing spdlog::spdlog target. " + "Set spdlog_VERSION to a version compatible with MQT Core.") +endif() +if(MQT_CORE_SPDLOG_TARGET_VERSION VERSION_LESS SPDLOG_MINIMUM_VERSION) + message( + FATAL_ERROR "The spdlog::spdlog target provides version ${MQT_CORE_SPDLOG_TARGET_VERSION}, " + "but MQT Core requires at least ${SPDLOG_MINIMUM_VERSION}.") +endif() + # Install nlohmann_json with explicit MQT components. if(MQT_CORE_JSON_INSTALL AND TARGET nlohmann_json) set(MQT_CORE_JSON_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/cmake/nlohmann_json") diff --git a/docs/qir/index.md b/docs/qir/index.md index 3cd350294e..243212b708 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -88,3 +88,9 @@ for a textual program and `bytes` for an exact binary payload. `Job.program_bytes` always returns the unmodified payload, while `Job.program` expects a null-terminated UTF-8 text payload and rejects known binary or non-text formats. + +The generic submission and program-access APIs intentionally reject QDMI +calibration and batch-job formats. Calibration jobs do not carry a program, +while batch jobs contain job handles rather than serialized program bytes. Their +format identifiers remain available for capability discovery; they require +dedicated typed APIs. diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index d8a4571c8d..3dfde956de 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -510,6 +510,8 @@ class Device { * @brief Submits a textual program. * @details The terminating null byte required by QDMI text formats is * included in the submitted payload. + * @throws std::invalid_argument If the format requires binary submission or + * does not carry a generic program payload. * @see QDMI_job_submit */ [[nodiscard]] Job submitJob( @@ -524,6 +526,8 @@ class Device { * @brief Submits a binary program. * @details The bytes are submitted exactly as provided without appending a * null byte. + * @throws std::invalid_argument If the format does not carry a generic + * program payload. * @see QDMI_job_submit */ [[nodiscard]] Job submitJob( @@ -648,12 +652,16 @@ class Job { /** * @brief Gets a textual program without its terminating null byte. - * @throws std::invalid_argument If the device does not return a - * null-terminated payload. + * @throws std::invalid_argument If the format is not textual or the device + * does not return a null-terminated payload. */ [[nodiscard]] std::string getProgram() const; - /// Gets the submitted program bytes exactly as returned by the device. + /** + * @brief Gets the submitted program bytes exactly as returned by the device. + * @throws std::invalid_argument If the format does not carry a generic + * program payload. + */ [[nodiscard]] std::vector getProgramBytes() const; /// Get the number of shots diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 20a65ee696..c7c280da6e 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -35,6 +35,21 @@ #include namespace fomac { +namespace { +[[nodiscard]] constexpr bool +isBinaryProgramFormat(const QDMI_Program_Format format) noexcept { + return format == QDMI_PROGRAM_FORMAT_QIRBASEMODULE || + format == QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE || + format == QDMI_PROGRAM_FORMAT_QPY; +} + +[[nodiscard]] constexpr bool +hasNoGenericProgramPayload(const QDMI_Program_Format format) noexcept { + return format == QDMI_PROGRAM_FORMAT_CALIBRATION || + format == QDMI_PROGRAM_FORMAT_BATCHJOB; +} +} // namespace + size_t Site::getIndex() const { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } @@ -322,6 +337,15 @@ Job Device::submitJob(const std::string& program, const std::optional& custom3, const std::optional& custom4, const std::optional& custom5) const { + if (isBinaryProgramFormat(format)) { + throw std::invalid_argument( + "Binary program formats require exact-byte submission"); + } + if (hasNoGenericProgramPayload(format)) { + throw std::invalid_argument( + "Calibration and batch jobs do not use a generic program payload"); + } + const auto bytes = std::as_bytes( std::span(program.c_str(), static_cast(program.size() + 1))); return submitJob(bytes, format, numShots, custom1, custom2, custom3, custom4, @@ -335,6 +359,11 @@ Job Device::submitJob(const std::span program, const std::optional& custom3, const std::optional& custom4, const std::optional& custom5) const { + if (hasNoGenericProgramPayload(format)) { + throw std::invalid_argument( + "Calibration and batch jobs do not use a generic program payload"); + } + QDMI_Job job = nullptr; qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; @@ -438,6 +467,11 @@ QDMI_Program_Format Job::getProgramFormat() const { } std::vector Job::getProgramBytes() const { + if (hasNoGenericProgramPayload(getProgramFormat())) { + throw std::invalid_argument( + "Calibration and batch jobs do not expose a generic program payload"); + } + size_t size = 0; qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_PROGRAM, 0, @@ -455,17 +489,14 @@ std::vector Job::getProgramBytes() const { } std::string Job::getProgram() const { - switch (getProgramFormat()) { - case QDMI_PROGRAM_FORMAT_QIRBASEMODULE: - case QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE: - case QDMI_PROGRAM_FORMAT_CALIBRATION: - case QDMI_PROGRAM_FORMAT_QPY: - case QDMI_PROGRAM_FORMAT_BATCHJOB: + const auto format = getProgramFormat(); + if (isBinaryProgramFormat(format)) { + throw std::invalid_argument( + "Cannot decode a binary program as a string; use getProgramBytes()"); + } + if (hasNoGenericProgramPayload(format)) { throw std::invalid_argument( - "Cannot decode a binary or non-text program as a string; use " - "getProgramBytes()"); - default: - break; + "Calibration and batch jobs do not expose a generic program payload"); } const auto program = getProgramBytes(); diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 4e303dde4a..c810256135 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -393,7 +393,8 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( // Text payloads include the trailing '\0' in `size`. // Strip it so it is not counted in the stored string's size. const auto* text = static_cast(value); - if (size == 0 || text[size - 1] != '\0') { + if (size == 0 || text[size - 1] != '\0' || + std::find(text, text + size - 1, '\0') != text + size - 1) { return QDMI_ERROR_INVALIDARGUMENT; } program_ = std::string(text, size - 1); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8f4927b65f..9c9142db20 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,17 +19,12 @@ add_subdirectory(fomac) if(MLIR_FOUND) set(MQT_CORE_QDMI_CONSUMER_SOURCE_DIR "${PROJECT_SOURCE_DIR}/test/cmake/qdmi-consumer") - set(MQT_CORE_QDMI_CONSUMER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-consumer") - set(MQT_CORE_QDMI_CONSUMER_ARGS + set(MQT_CORE_QDMI_CONSUMER_COMMON_ARGS -G "${CMAKE_GENERATOR}" -S "${MQT_CORE_QDMI_CONSUMER_SOURCE_DIR}" - -B - "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" "-DMQT_CORE_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DPARENT_SPDLOG_SOURCE_DIR=${spdlog_SOURCE_DIR}" - "-DPARENT_SPDLOG_DIR=${spdlog_DIR}" "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DMLIR_DIR=${MLIR_DIR}") @@ -37,11 +32,15 @@ if(MLIR_FOUND) set(dependency_source_dir_variable "${dependency}_SOURCE_DIR") if(DEFINED ${dependency_source_dir_variable}) string(TOUPPER "${dependency}" dependency_upper) - list(APPEND MQT_CORE_QDMI_CONSUMER_ARGS + list(APPEND MQT_CORE_QDMI_CONSUMER_COMMON_ARGS "-DFETCHCONTENT_SOURCE_DIR_${dependency_upper}=${${dependency_source_dir_variable}}") endif() endforeach() + set(MQT_CORE_QDMI_CONSUMER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-consumer") + set(MQT_CORE_QDMI_CONSUMER_ARGS + ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} -B "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" + "-DPARENT_SPDLOG_SOURCE_DIR=${spdlog_SOURCE_DIR}") add_test(NAME mqt-core-cmake-qdmi-consumer-configure COMMAND ${CMAKE_COMMAND} --fresh ${MQT_CORE_QDMI_CONSUMER_ARGS}) set_tests_properties(mqt-core-cmake-qdmi-consumer-configure @@ -52,6 +51,63 @@ if(MLIR_FOUND) mqt-core-qdmi-consumer) set_tests_properties(mqt-core-cmake-qdmi-consumer-build PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-qdmi-consumer) + + set(MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/spdlog-package") + set(MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR + "${CMAKE_CURRENT_BINARY_DIR}/cmake/spdlog-package-install") + add_test( + NAME mqt-core-cmake-spdlog-package-configure + COMMAND + ${CMAKE_COMMAND} --fresh -G "${CMAKE_GENERATOR}" -S "${spdlog_SOURCE_DIR}" -B + "${MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_INSTALL_PREFIX=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}" + "-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}" -DSPDLOG_BUILD_SHARED=OFF + -DSPDLOG_BUILD_PIC=ON -DSPDLOG_INSTALL=ON) + set_tests_properties(mqt-core-cmake-spdlog-package-configure + PROPERTIES FIXTURES_SETUP mqt-core-cmake-spdlog-package-configure) + + add_test(NAME mqt-core-cmake-spdlog-package-install + COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR}" --target + install) + set_tests_properties( + mqt-core-cmake-spdlog-package-install + PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package-configure FIXTURES_SETUP + mqt-core-cmake-spdlog-package) + + set(MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR + "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-package-consumer") + set(MQT_CORE_QDMI_PACKAGE_CONSUMER_ARGS + ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} + -B + "${MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR}" + "-DPARENT_SPDLOG_DIR=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/spdlog" + ) + add_test(NAME mqt-core-cmake-qdmi-package-consumer-configure + COMMAND ${CMAKE_COMMAND} --fresh ${MQT_CORE_QDMI_PACKAGE_CONSUMER_ARGS}) + set_tests_properties( + mqt-core-cmake-qdmi-package-consumer-configure + PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package FIXTURES_SETUP + mqt-core-cmake-qdmi-package-consumer) + + add_test(NAME mqt-core-cmake-qdmi-package-consumer-build + COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR}" --target + mqt-core-qdmi-consumer) + set_tests_properties(mqt-core-cmake-qdmi-package-consumer-build + PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-qdmi-package-consumer) + + set(MQT_CORE_QDMI_INCOMPATIBLE_SPDLOG_BINARY_DIR + "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-incompatible-spdlog") + add_test( + NAME mqt-core-cmake-qdmi-incompatible-spdlog + COMMAND + ${CMAKE_COMMAND} --fresh ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} -B + "${MQT_CORE_QDMI_INCOMPATIBLE_SPDLOG_BINARY_DIR}" + "-DPARENT_SPDLOG_DIR=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/spdlog" + -DSPDLOG_MINIMUM_VERSION=2.0.0) + set_tests_properties( + mqt-core-cmake-qdmi-incompatible-spdlog + PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package WILL_FAIL TRUE + PASS_REGULAR_EXPRESSION "requires at least 2.0.0") endif() # copy test circuits to build directory diff --git a/test/cmake/qdmi-consumer/main.cpp b/test/cmake/qdmi-consumer/main.cpp index 7cc71a01f9..f04380fa06 100644 --- a/test/cmake/qdmi-consumer/main.cpp +++ b/test/cmake/qdmi-consumer/main.cpp @@ -13,14 +13,13 @@ #include #include -int main() { - const auto submitBinaryProgram = [](const fomac::Device& device) { - constexpr std::array program{}; - return device.submitJob(program, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0); - }; - static_cast(submitBinaryProgram); - +int main(const int argc, char**) { fomac::Session session; - static_cast(session.getDevices()); + const auto devices = session.getDevices(); + if (argc > 1 && !devices.empty()) { + constexpr std::array program{std::byte{0}}; + static_cast(devices.front().submitJob( + program, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0)); + } return 0; } diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 409ce57523..edef508f14 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -471,10 +471,24 @@ def test_program_format_includes_batch_job() -> None: assert ProgramFormat.BATCH_JOB.value == 9 -def test_device_rejects_unterminated_text_bytes(ddsim_device: Device) -> None: - """Reject byte payloads that do not satisfy QDMI's text contract.""" +@pytest.mark.parametrize("program", [b"OPENQASM 3.0;", b"OPENQASM 3.0;\0garbage\0", "OPENQASM 3.0;\0garbage"]) +def test_device_rejects_invalid_text_payloads(ddsim_device: Device, program: str | bytes) -> None: + """Reject payloads that do not satisfy QDMI's text contract.""" with pytest.raises(ValueError, match=r"Setting program: Invalid argument\."): - ddsim_device.submit_job(b"OPENQASM 3.0;", ProgramFormat.QASM3, num_shots=1) + ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=1) + + +def test_device_rejects_text_for_binary_format(ddsim_device: Device) -> None: + """Require exact byte submission for known binary formats.""" + with pytest.raises(ValueError, match="require exact-byte submission"): + ddsim_device.submit_job("not bitcode", ProgramFormat.QIR_BASE_MODULE, num_shots=1) + + +@pytest.mark.parametrize("program_format", [ProgramFormat.CALIBRATION, ProgramFormat.BATCH_JOB]) +def test_device_rejects_formats_without_generic_payload(ddsim_device: Device, program_format: ProgramFormat) -> None: + """Keep specialized QDMI formats out of the generic program API.""" + with pytest.raises(ValueError, match="do not use a generic program payload"): + ddsim_device.submit_job(b"", program_format, num_shots=1) def test_device_executes_qir_program(ddsim_device: Device) -> None: @@ -520,7 +534,7 @@ def test_device_executes_binary_qir_program(ddsim_device: Device) -> None: job = ddsim_device.submit_job(program_bytes, ProgramFormat.QIR_BASE_MODULE, num_shots=10) assert job.program_bytes == program_bytes - with pytest.raises(ValueError, match="binary or non-text"): + with pytest.raises(ValueError, match="binary program"): _ = job.program job.wait() diff --git a/test/qdmi/devices/dd/job_parameters_test.cpp b/test/qdmi/devices/dd/job_parameters_test.cpp index bdeb770390..8ada4d4157 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -102,6 +102,23 @@ TEST(JobParameters, RejectsUnterminatedTextProgram) { QDMI_ERROR_INVALIDARGUMENT); } +TEST(JobParameters, RejectsInteriorNullInTextProgram) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + + constexpr QDMI_Program_Format fmt = QDMI_PROGRAM_FORMAT_QASM3; + ASSERT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), &fmt), + QDMI_SUCCESS); + + constexpr char program[] = "OPENQASM 3.0;\0garbage"; + EXPECT_EQ( + MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, sizeof(program), program), + QDMI_ERROR_INVALIDARGUMENT); +} + TEST(JobParameters, ProgramFormatSupport) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; From d3df7f10be9bfd7611cd6fd51862f5b4fa8dfd5b Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 27 Jul 2026 21:54:10 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=93=9D=20Document=20QDMI=20consumer?= =?UTF-8?q?=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b77df59f..12eeaf76cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ releases may include breaking changes. ### Added +- ✨ Add binary-safe QDMI program handling to FoMaC and support embedding the + QIR-enabled DDSIM device with parent-provided LLVM/MLIR and `spdlog` + dependencies ([#1957]) ([**@burgholzer**]) - ✨ Add and improve QIR generation support in the MQT Compiler Collection ([#1264], [#1446], [#1513], [#1521], [#1548], [#1567], [#1569], [#1570], [#1572], [#1580], [#1620], [#1624], [#1626], [#1648], [#1710], [#1751], @@ -652,6 +655,7 @@ changelogs._ +[#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 [#1938]: https://github.com/munich-quantum-toolkit/core/pull/1938 [#1936]: https://github.com/munich-quantum-toolkit/core/pull/1936 [#1935]: https://github.com/munich-quantum-toolkit/core/pull/1935 From 3b332f144fc81daf65131187779c704182a6f37f Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Tue, 28 Jul 2026 22:43:23 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20QDMI=20integration=20C?= =?UTF-8?q?I=20regressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-5.6 via Codex --- docs/qir/index.md | 9 ++++----- include/mqt-core/fomac/FoMaC.hpp | 2 -- src/fomac/FoMaC.cpp | 9 --------- src/qdmi/devices/dd/Device.cpp | 11 +++++++---- test/CMakeLists.txt | 13 ++++++++++--- test/cmake/qdmi-consumer/main.cpp | 4 +++- test/fomac/test_fomac.cpp | 15 +++++++++++++++ test/qdmi/devices/dd/job_parameters_test.cpp | 11 ++++++----- 8 files changed, 45 insertions(+), 29 deletions(-) diff --git a/docs/qir/index.md b/docs/qir/index.md index 243212b708..6d24a606b4 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -89,8 +89,7 @@ for a textual program and `bytes` for an exact binary payload. expects a null-terminated UTF-8 text payload and rejects known binary or non-text formats. -The generic submission and program-access APIs intentionally reject QDMI -calibration and batch-job formats. Calibration jobs do not carry a program, -while batch jobs contain job handles rather than serialized program bytes. Their -format identifiers remain available for capability discovery; they require -dedicated typed APIs. +The generic submission APIs intentionally reject QDMI calibration and batch-job +formats. Calibration jobs do not carry a program, while batch jobs contain job +handles rather than serialized program bytes. Their format identifiers remain +available for capability discovery; they require dedicated typed APIs. diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 3dfde956de..e7e705b43d 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -659,8 +659,6 @@ class Job { /** * @brief Gets the submitted program bytes exactly as returned by the device. - * @throws std::invalid_argument If the format does not carry a generic - * program payload. */ [[nodiscard]] std::vector getProgramBytes() const; diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index c7c280da6e..959e13229b 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -467,11 +467,6 @@ QDMI_Program_Format Job::getProgramFormat() const { } std::vector Job::getProgramBytes() const { - if (hasNoGenericProgramPayload(getProgramFormat())) { - throw std::invalid_argument( - "Calibration and batch jobs do not expose a generic program payload"); - } - size_t size = 0; qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_PROGRAM, 0, @@ -494,10 +489,6 @@ std::string Job::getProgram() const { throw std::invalid_argument( "Cannot decode a binary program as a string; use getProgramBytes()"); } - if (hasNoGenericProgramPayload(format)) { - throw std::invalid_argument( - "Calibration and batch jobs do not expose a generic program payload"); - } const auto program = getProgramBytes(); if (program.empty() || program.back() != std::byte{0}) { diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index c810256135..2c9c6eb252 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -392,12 +392,15 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( if (isTextProgramFormat) { // Text payloads include the trailing '\0' in `size`. // Strip it so it is not counted in the stored string's size. - const auto* text = static_cast(value); - if (size == 0 || text[size - 1] != '\0' || - std::find(text, text + size - 1, '\0') != text + size - 1) { + const std::span text{static_cast(value), size}; + if (text.empty() || text.back() != '\0') { return QDMI_ERROR_INVALIDARGUMENT; } - program_ = std::string(text, size - 1); + const auto contents = text.first(text.size() - 1); + if (std::ranges::find(contents, '\0') != contents.end()) { + return QDMI_ERROR_INVALIDARGUMENT; + } + program_ = std::string(contents.begin(), contents.end()); } else { // Binary payloads are stored exactly as received. const std::span bytes(static_cast(value), size); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9c9142db20..20fcb7e1e3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ if(MLIR_FOUND) -S "${MQT_CORE_QDMI_CONSUMER_SOURCE_DIR}" "-DMQT_CORE_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DCMAKE_BUILD_TYPE=$" "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DMLIR_DIR=${MLIR_DIR}") @@ -48,7 +49,7 @@ if(MLIR_FOUND) add_test(NAME mqt-core-cmake-qdmi-consumer-build COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" --target - mqt-core-qdmi-consumer) + mqt-core-qdmi-consumer --config $) set_tests_properties(mqt-core-cmake-qdmi-consumer-build PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-qdmi-consumer) @@ -68,7 +69,7 @@ if(MLIR_FOUND) add_test(NAME mqt-core-cmake-spdlog-package-install COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR}" --target - install) + install --config $) set_tests_properties( mqt-core-cmake-spdlog-package-install PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package-configure FIXTURES_SETUP @@ -91,7 +92,7 @@ if(MLIR_FOUND) add_test(NAME mqt-core-cmake-qdmi-package-consumer-build COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR}" --target - mqt-core-qdmi-consumer) + mqt-core-qdmi-consumer --config $) set_tests_properties(mqt-core-cmake-qdmi-package-consumer-build PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-qdmi-package-consumer) @@ -108,6 +109,12 @@ if(MLIR_FOUND) mqt-core-cmake-qdmi-incompatible-spdlog PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package WILL_FAIL TRUE PASS_REGULAR_EXPRESSION "requires at least 2.0.0") + + # Give static-analysis builds a compile command for the standalone consumer source. The nested + # CMake tests above remain responsible for exercising the actual add_subdirectory integration. + add_executable(mqt-core-qdmi-consumer-compile-check EXCLUDE_FROM_ALL cmake/qdmi-consumer/main.cpp) + target_link_libraries(mqt-core-qdmi-consumer-compile-check PRIVATE MQT::CoreFoMaC + MQT::CoreQDMI_DDSIM_Device) endif() # copy test circuits to build directory diff --git a/test/cmake/qdmi-consumer/main.cpp b/test/cmake/qdmi-consumer/main.cpp index f04380fa06..0be5b6b89b 100644 --- a/test/cmake/qdmi-consumer/main.cpp +++ b/test/cmake/qdmi-consumer/main.cpp @@ -10,10 +10,12 @@ #include "fomac/FoMaC.hpp" +#include + #include #include -int main(const int argc, char**) { +int main(const int argc, [[maybe_unused]] char** argv) { fomac::Session session; const auto devices = session.getDevices(); if (argc > 1 && !devices.empty()) { diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 935091d847..a59517f6a8 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -732,6 +732,21 @@ c = measure q;)"; EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE); } +TEST_F(DDSimulatorDeviceTest, SubmitJobRejectsIncompatiblePayloadKinds) { + const std::string textProgram = "OPENQASM 3.0;"; + constexpr std::array bytes{std::byte{0}}; + + EXPECT_THROW(std::ignore = device.submitJob( + textProgram, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0), + std::invalid_argument); + EXPECT_THROW(std::ignore = device.submitJob( + textProgram, QDMI_PROGRAM_FORMAT_CALIBRATION, 0), + std::invalid_argument); + EXPECT_THROW(std::ignore = + device.submitJob(bytes, QDMI_PROGRAM_FORMAT_BATCHJOB, 0), + std::invalid_argument); +} + TEST_F(DDSimulatorDeviceTest, SubmitJobCustomSupportedTypes) { constexpr auto qasm3Program = "OPENQASM 3.0;"; diff --git a/test/qdmi/devices/dd/job_parameters_test.cpp b/test/qdmi/devices/dd/job_parameters_test.cpp index 8ada4d4157..da57756f37 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -112,11 +113,11 @@ TEST(JobParameters, RejectsInteriorNullInTextProgram) { sizeof(QDMI_Program_Format), &fmt), QDMI_SUCCESS); - constexpr char program[] = "OPENQASM 3.0;\0garbage"; - EXPECT_EQ( - MQT_DDSIM_QDMI_device_job_set_parameter( - j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, sizeof(program), program), - QDMI_ERROR_INVALIDARGUMENT); + constexpr auto program = std::to_array("OPENQASM 3.0;\0garbage"); + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( + j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, program.size(), + program.data()), + QDMI_ERROR_INVALIDARGUMENT); } TEST(JobParameters, ProgramFormatSupport) { From e8d8f8eae2b4f366191e2f7a6ce980946298c218 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 19:28:26 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20embedded=20?= =?UTF-8?q?dependency=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the FoMaC compatibility property and replace the standalone QDMI consumer matrix with one focused configure test for a parent-provided spdlog target. Assisted-by: GPT-5.6 via Codex --- CHANGELOG.md | 5 +- cmake/ExternalDependencies.cmake | 19 +----- cmake/mqt-core-config.cmake.in | 2 +- docs/qir/index.md | 3 - src/fomac/CMakeLists.txt | 8 --- test/CMakeLists.txt | 88 ++----------------------- test/cmake/parent-spdlog/CMakeLists.txt | 25 +++++++ test/cmake/qdmi-consumer/CMakeLists.txt | 51 -------------- test/cmake/qdmi-consumer/main.cpp | 27 -------- 9 files changed, 36 insertions(+), 192 deletions(-) create mode 100644 test/cmake/parent-spdlog/CMakeLists.txt delete mode 100644 test/cmake/qdmi-consumer/CMakeLists.txt delete mode 100644 test/cmake/qdmi-consumer/main.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ace1c61a4..7ae9ade0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,8 @@ releases may include breaking changes. ### Added -- ✨ Add binary-safe QDMI program handling to FoMaC and support embedding the - DDSIM QDMI device with parent-provided LLVM/MLIR and `spdlog` dependencies - ([#1957]) ([**@burgholzer**]) +- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) + ([**@burgholzer**]) - ✨ Add and improve QIR generation support in the MQT Compiler Collection ([#1264], [#1446], [#1513], [#1521], [#1548], [#1567], [#1569], [#1570], [#1572], [#1580], [#1620], [#1624], [#1626], [#1648], [#1710], [#1751], diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 3e946f6024..5ece29a7d9 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -99,9 +99,6 @@ FetchContent_Declare( FIND_PACKAGE_ARGS ${QDMI_MINIMUM_VERSION}) list(APPEND FETCH_PACKAGES qdmi) -set(SPDLOG_MINIMUM_VERSION - 1.15.3 - CACHE STRING "Minimum spdlog version") set(MQT_CORE_MANAGES_SPDLOG OFF) if(NOT TARGET spdlog::spdlog) set(SPDLOG_VERSION @@ -120,7 +117,7 @@ if(NOT TARGET spdlog::spdlog) CACHE BOOL "Disable upstream spdlog install rules; handled by mqt-core" FORCE) cmake_dependent_option(SPDLOG_BUILD_SHARED "Build spdlog as shared library" ON "BUILD_MQT_CORE_SHARED_LIBS" OFF) - FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_MINIMUM_VERSION}) + FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_VERSION}) list(APPEND FETCH_PACKAGES spdlog) set(MQT_CORE_MANAGES_SPDLOG ON) endif() @@ -128,20 +125,6 @@ endif() # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) -get_target_property(MQT_CORE_SPDLOG_TARGET_VERSION spdlog::spdlog VERSION) -if(NOT MQT_CORE_SPDLOG_TARGET_VERSION OR MQT_CORE_SPDLOG_TARGET_VERSION MATCHES "-NOTFOUND$") - set(MQT_CORE_SPDLOG_TARGET_VERSION "${spdlog_VERSION}") -endif() -if(NOT MQT_CORE_SPDLOG_TARGET_VERSION) - message(FATAL_ERROR "Cannot determine the version of the existing spdlog::spdlog target. " - "Set spdlog_VERSION to a version compatible with MQT Core.") -endif() -if(MQT_CORE_SPDLOG_TARGET_VERSION VERSION_LESS SPDLOG_MINIMUM_VERSION) - message( - FATAL_ERROR "The spdlog::spdlog target provides version ${MQT_CORE_SPDLOG_TARGET_VERSION}, " - "but MQT Core requires at least ${SPDLOG_MINIMUM_VERSION}.") -endif() - # Install nlohmann_json with explicit MQT components. if(MQT_CORE_JSON_INSTALL AND TARGET nlohmann_json) set(MQT_CORE_JSON_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/cmake/nlohmann_json") diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index ddb3b48449..f8d1982f46 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -14,7 +14,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") include(CMakeFindDependencyMacro) find_dependency(nlohmann_json) -find_dependency(spdlog @SPDLOG_MINIMUM_VERSION@) +find_dependency(spdlog) find_dependency(qdmi) option(MQT_CORE_WITH_GMP "Library is configured to use GMP" @MQT_CORE_WITH_GMP@) diff --git a/docs/qir/index.md b/docs/qir/index.md index 6c52ce9548..d10eca9c5d 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -69,9 +69,6 @@ without interpreting its format or removing terminal null bytes; the existing known binary and non-text formats based on their QDMI format identifier, even if their payload happens to end in a null byte. -The `MQT::CoreFoMaC` CMake target advertises this API through the exported -`MQT_CORE_FOMAC_BINARY_PROGRAM_API` target property. - The Python API follows the same distinction: pass `str` to `Device.submit_job` for a textual program and `bytes` for an exact binary payload. `Job.program_bytes` always returns the unmodified payload, while `Job.program` diff --git a/src/fomac/CMakeLists.txt b/src/fomac/CMakeLists.txt index 29d68bedce..631141c998 100644 --- a/src/fomac/CMakeLists.txt +++ b/src/fomac/CMakeLists.txt @@ -25,14 +25,6 @@ if(NOT TARGET ${TARGET_NAME}) PUBLIC qdmi::qdmi MQT::CoreQDMICommon MQT::CoreQDMIDriver PRIVATE spdlog::spdlog) - # Allow embedding projects to reject a pre-existing FoMaC target that predates exact-byte program - # submission. - set_property(TARGET ${TARGET_NAME} PROPERTY MQT_CORE_FOMAC_BINARY_PROGRAM_API ON) - set_property( - TARGET ${TARGET_NAME} - APPEND - PROPERTY EXPORT_PROPERTIES MQT_CORE_FOMAC_BINARY_PROGRAM_API) - # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef5fb978e8..d57d6972e7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,12 +17,13 @@ add_subdirectory(qir) add_subdirectory(qdmi) add_subdirectory(fomac) -set(MQT_CORE_QDMI_CONSUMER_SOURCE_DIR "${PROJECT_SOURCE_DIR}/test/cmake/qdmi-consumer") -set(MQT_CORE_QDMI_CONSUMER_COMMON_ARGS +set(MQT_CORE_PARENT_SPDLOG_TEST_ARGS -G "${CMAKE_GENERATOR}" -S - "${MQT_CORE_QDMI_CONSUMER_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/test/cmake/parent-spdlog" + -B + "${CMAKE_CURRENT_BINARY_DIR}/cmake/parent-spdlog" "-DMQT_CORE_SOURCE_DIR=${PROJECT_SOURCE_DIR}" "-DCMAKE_BUILD_TYPE=$" "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" @@ -32,86 +33,11 @@ foreach(dependency IN ITEMS jeff-mlir jeff capnproto nlohmann_json boost_mp qdmi FetchContent_GetProperties("${dependency}" SOURCE_DIR dependency_source_dir) if(dependency_source_dir) string(TOUPPER "${dependency}" dependency_upper) - list(APPEND MQT_CORE_QDMI_CONSUMER_COMMON_ARGS + list(APPEND MQT_CORE_PARENT_SPDLOG_TEST_ARGS "-DFETCHCONTENT_SOURCE_DIR_${dependency_upper}=${dependency_source_dir}") endif() endforeach() - -set(MQT_CORE_QDMI_CONSUMER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-consumer") -set(MQT_CORE_QDMI_CONSUMER_ARGS - ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} -B "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" - "-DPARENT_SPDLOG_SOURCE_DIR=${spdlog_SOURCE_DIR}") -add_test(NAME mqt-core-cmake-qdmi-consumer-configure COMMAND ${CMAKE_COMMAND} --fresh - ${MQT_CORE_QDMI_CONSUMER_ARGS}) -set_tests_properties(mqt-core-cmake-qdmi-consumer-configure PROPERTIES FIXTURES_SETUP - mqt-core-cmake-qdmi-consumer) - -add_test(NAME mqt-core-cmake-qdmi-consumer-build - COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_CONSUMER_BINARY_DIR}" --target - mqt-core-qdmi-consumer --config $) -set_tests_properties(mqt-core-cmake-qdmi-consumer-build PROPERTIES FIXTURES_REQUIRED - mqt-core-cmake-qdmi-consumer) - -set(MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/spdlog-package") -set(MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake/spdlog-package-install") -add_test( - NAME mqt-core-cmake-spdlog-package-configure - COMMAND - ${CMAKE_COMMAND} --fresh -G "${CMAKE_GENERATOR}" -S "${spdlog_SOURCE_DIR}" -B - "${MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_INSTALL_PREFIX=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}" - "-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}" -DSPDLOG_BUILD_SHARED=OFF - -DSPDLOG_BUILD_PIC=ON -DSPDLOG_INSTALL=ON) -set_tests_properties(mqt-core-cmake-spdlog-package-configure - PROPERTIES FIXTURES_SETUP mqt-core-cmake-spdlog-package-configure) - -add_test(NAME mqt-core-cmake-spdlog-package-install - COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_SPDLOG_PACKAGE_BINARY_DIR}" --target install - --config $) -set_tests_properties( - mqt-core-cmake-spdlog-package-install - PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package-configure FIXTURES_SETUP - mqt-core-cmake-spdlog-package) - -set(MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR - "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-package-consumer") -set(MQT_CORE_QDMI_PACKAGE_CONSUMER_ARGS - ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} - -B - "${MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR}" - "-DPARENT_SPDLOG_DIR=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/spdlog" -) -add_test(NAME mqt-core-cmake-qdmi-package-consumer-configure - COMMAND ${CMAKE_COMMAND} --fresh ${MQT_CORE_QDMI_PACKAGE_CONSUMER_ARGS}) -set_tests_properties( - mqt-core-cmake-qdmi-package-consumer-configure - PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package FIXTURES_SETUP - mqt-core-cmake-qdmi-package-consumer) - -add_test(NAME mqt-core-cmake-qdmi-package-consumer-build - COMMAND ${CMAKE_COMMAND} --build "${MQT_CORE_QDMI_PACKAGE_CONSUMER_BINARY_DIR}" --target - mqt-core-qdmi-consumer --config $) -set_tests_properties(mqt-core-cmake-qdmi-package-consumer-build - PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-qdmi-package-consumer) - -set(MQT_CORE_QDMI_INCOMPATIBLE_SPDLOG_BINARY_DIR - "${CMAKE_CURRENT_BINARY_DIR}/cmake/qdmi-incompatible-spdlog") -add_test( - NAME mqt-core-cmake-qdmi-incompatible-spdlog - COMMAND - ${CMAKE_COMMAND} --fresh ${MQT_CORE_QDMI_CONSUMER_COMMON_ARGS} -B - "${MQT_CORE_QDMI_INCOMPATIBLE_SPDLOG_BINARY_DIR}" - "-DPARENT_SPDLOG_DIR=${MQT_CORE_SPDLOG_PACKAGE_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/spdlog" - -DSPDLOG_MINIMUM_VERSION=2.0.0) -set_tests_properties( - mqt-core-cmake-qdmi-incompatible-spdlog - PROPERTIES FIXTURES_REQUIRED mqt-core-cmake-spdlog-package WILL_FAIL TRUE PASS_REGULAR_EXPRESSION - "requires at least 2.0.0") - -# Give static-analysis builds a compile command for the standalone consumer source. The nested CMake -# tests above remain responsible for exercising the actual add_subdirectory integration. -add_executable(mqt-core-qdmi-consumer-compile-check EXCLUDE_FROM_ALL cmake/qdmi-consumer/main.cpp) -target_link_libraries(mqt-core-qdmi-consumer-compile-check PRIVATE MQT::CoreFoMaC - MQT::CoreQDMI_DDSIM_Device) +add_test(NAME mqt-core-cmake-parent-spdlog COMMAND ${CMAKE_COMMAND} --fresh + ${MQT_CORE_PARENT_SPDLOG_TEST_ARGS}) # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/cmake/parent-spdlog/CMakeLists.txt b/test/cmake/parent-spdlog/CMakeLists.txt new file mode 100644 index 0000000000..bb8037c3f3 --- /dev/null +++ b/test/cmake/parent-spdlog/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +cmake_minimum_required(VERSION 3.24...4.2) + +project(mqt-core-parent-spdlog LANGUAGES C CXX) + +if(NOT MQT_CORE_SOURCE_DIR) + message(FATAL_ERROR "MQT_CORE_SOURCE_DIR is required") +endif() + +add_library(spdlog::spdlog INTERFACE IMPORTED) + +foreach(option IN ITEMS MQT_CORE_INSTALL BUILD_MQT_CORE_BINDINGS BUILD_MQT_CORE_TESTS + BUILD_MQT_CORE_DOCUMENTATION) + set(${option} + OFF + CACHE BOOL "") +endforeach() +add_subdirectory("${MQT_CORE_SOURCE_DIR}" mqt-core EXCLUDE_FROM_ALL) diff --git a/test/cmake/qdmi-consumer/CMakeLists.txt b/test/cmake/qdmi-consumer/CMakeLists.txt deleted file mode 100644 index 4d83bdff53..0000000000 --- a/test/cmake/qdmi-consumer/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -cmake_minimum_required(VERSION 3.24...4.2) - -project(mqt-core-qdmi-consumer LANGUAGES C CXX) - -if(NOT MQT_CORE_SOURCE_DIR) - message(FATAL_ERROR "MQT_CORE_SOURCE_DIR must point to the MQT Core source tree") -endif() - -if(EXISTS "${PARENT_SPDLOG_SOURCE_DIR}/CMakeLists.txt") - set(SPDLOG_BUILD_PIC ON) - set(SPDLOG_INSTALL OFF) - add_subdirectory("${PARENT_SPDLOG_SOURCE_DIR}" spdlog EXCLUDE_FROM_ALL) -elseif(PARENT_SPDLOG_DIR) - set(spdlog_DIR "${PARENT_SPDLOG_DIR}") - find_package(spdlog CONFIG REQUIRED) -else() - message(FATAL_ERROR "A parent spdlog source or package directory is required") -endif() - -set(MQT_CORE_INSTALL - OFF - CACHE BOOL "") -set(BUILD_MQT_CORE_BINDINGS - OFF - CACHE BOOL "") -set(BUILD_MQT_CORE_TESTS - OFF - CACHE BOOL "") -set(BUILD_MQT_CORE_BENCHMARKS - OFF - CACHE BOOL "") -set(BUILD_MQT_CORE_DOCUMENTATION - OFF - CACHE BOOL "") -add_subdirectory("${MQT_CORE_SOURCE_DIR}" mqt-core EXCLUDE_FROM_ALL) - -get_target_property(fomac_binary_program_api MQT::CoreFoMaC MQT_CORE_FOMAC_BINARY_PROGRAM_API) -if(NOT fomac_binary_program_api) - message(FATAL_ERROR "FoMaC does not advertise exact-byte program submission") -endif() - -add_executable(mqt-core-qdmi-consumer main.cpp) -target_link_libraries(mqt-core-qdmi-consumer PRIVATE MQT::CoreFoMaC MQT::CoreQDMI_DDSIM_Device) diff --git a/test/cmake/qdmi-consumer/main.cpp b/test/cmake/qdmi-consumer/main.cpp deleted file mode 100644 index 0be5b6b89b..0000000000 --- a/test/cmake/qdmi-consumer/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "fomac/FoMaC.hpp" - -#include - -#include -#include - -int main(const int argc, [[maybe_unused]] char** argv) { - fomac::Session session; - const auto devices = session.getDevices(); - if (argc > 1 && !devices.empty()) { - constexpr std::array program{std::byte{0}}; - static_cast(devices.front().submitJob( - program, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, 0)); - } - return 0; -} From 5bea1ca3c86dab76470718fc32179578548632af Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 29 Jul 2026 19:42:49 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=A7=B9=20Remove=20embedded=20spdlog?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the dedicated imported-spdlog configure fixture and its registration. Assisted-by: GPT-5.6 via Codex --- test/CMakeLists.txt | 22 ---------------------- test/cmake/parent-spdlog/CMakeLists.txt | 25 ------------------------- 2 files changed, 47 deletions(-) delete mode 100644 test/cmake/parent-spdlog/CMakeLists.txt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d57d6972e7..6203a14e01 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,27 +17,5 @@ add_subdirectory(qir) add_subdirectory(qdmi) add_subdirectory(fomac) -set(MQT_CORE_PARENT_SPDLOG_TEST_ARGS - -G - "${CMAKE_GENERATOR}" - -S - "${PROJECT_SOURCE_DIR}/test/cmake/parent-spdlog" - -B - "${CMAKE_CURRENT_BINARY_DIR}/cmake/parent-spdlog" - "-DMQT_CORE_SOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DCMAKE_BUILD_TYPE=$" - "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DMLIR_DIR=${MLIR_DIR}") -foreach(dependency IN ITEMS jeff-mlir jeff capnproto nlohmann_json boost_mp qdmi) - FetchContent_GetProperties("${dependency}" SOURCE_DIR dependency_source_dir) - if(dependency_source_dir) - string(TOUPPER "${dependency}" dependency_upper) - list(APPEND MQT_CORE_PARENT_SPDLOG_TEST_ARGS - "-DFETCHCONTENT_SOURCE_DIR_${dependency_upper}=${dependency_source_dir}") - endif() -endforeach() -add_test(NAME mqt-core-cmake-parent-spdlog COMMAND ${CMAKE_COMMAND} --fresh - ${MQT_CORE_PARENT_SPDLOG_TEST_ARGS}) # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/cmake/parent-spdlog/CMakeLists.txt b/test/cmake/parent-spdlog/CMakeLists.txt deleted file mode 100644 index bb8037c3f3..0000000000 --- a/test/cmake/parent-spdlog/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -cmake_minimum_required(VERSION 3.24...4.2) - -project(mqt-core-parent-spdlog LANGUAGES C CXX) - -if(NOT MQT_CORE_SOURCE_DIR) - message(FATAL_ERROR "MQT_CORE_SOURCE_DIR is required") -endif() - -add_library(spdlog::spdlog INTERFACE IMPORTED) - -foreach(option IN ITEMS MQT_CORE_INSTALL BUILD_MQT_CORE_BINDINGS BUILD_MQT_CORE_TESTS - BUILD_MQT_CORE_DOCUMENTATION) - set(${option} - OFF - CACHE BOOL "") -endforeach() -add_subdirectory("${MQT_CORE_SOURCE_DIR}" mqt-core EXCLUDE_FROM_ALL)