Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .license-tools-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"py\\.typed",
"Dockerfile",
"prefix_defs.txt",
"client_version.h.in",
"test_defs.cpp.in",
"LICENSE"
]
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ clients compiled against a different minor or major version.

_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#unreleased)._

### Added

- ✨ Add a versioned, replaceable Client-driver ABI and a mandatory stable
client-visible device ID ([#511]) ([\@burgholzer]).

### Changed

- 💥 Make Client session allocation initialize drivers lazily and remove the
example driver's separate initialization and shutdown functions ([#511])
([\@burgholzer]).
- 💥 Drop x86 macOS from QDMI's tested platforms ([#515]) ([\@denialhaag])
- ⬆️ Raise generated device projects' macOS deployment target to 13.3 ([#515])
([\@denialhaag])
Expand Down Expand Up @@ -229,6 +237,7 @@ for previous changelogs._
<!-- PR links -->

[#515]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/515
[#511]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/511
[#486]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/486
[#485]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/485
[#475]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/475
Expand Down
21 changes: 18 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.24...4.4)
project(
qdmi
LANGUAGES C CXX
VERSION 1.3.4
VERSION 1.4.0
DESCRIPTION "QDMI –– Quantum Device Management Interface")

set(PROJECT_VERSION_PRERELEASE "-dev")
Expand Down Expand Up @@ -85,6 +85,11 @@ if(NOT USE_INSTALLED_QDMI)
set(QDMI_CMAKE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
CACHE INTERNAL "CMake directory")
set(QDMI_GENERATED_INCLUDE_BUILD_DIR "${PROJECT_BINARY_DIR}/include")
file(MAKE_DIRECTORY "${QDMI_GENERATED_INCLUDE_BUILD_DIR}/qdmi")
configure_file(
"${QDMI_CMAKE_DIR}/client_version.h.in"
"${QDMI_GENERATED_INCLUDE_BUILD_DIR}/qdmi/client_version.h" @ONLY)

# make scripts available to cmake
list(APPEND CMAKE_MODULE_PATH ${QDMI_CMAKE_DIR})
Expand Down Expand Up @@ -114,10 +119,20 @@ if(NOT USE_INSTALLED_QDMI)

# collect header files
file(GLOB_RECURSE QDMI_HEADERS ${QDMI_INCLUDE_BUILD_DIR}/qdmi/*.h)
set(QDMI_GENERATED_HEADERS
"${QDMI_GENERATED_INCLUDE_BUILD_DIR}/qdmi/client_version.h")

# add headers using file sets
target_sources(qdmi PUBLIC FILE_SET HEADERS BASE_DIRS
${QDMI_INCLUDE_BUILD_DIR} FILES ${QDMI_HEADERS})
target_sources(
qdmi
PUBLIC FILE_SET
HEADERS
BASE_DIRS
${QDMI_INCLUDE_BUILD_DIR}
${QDMI_GENERATED_INCLUDE_BUILD_DIR}
FILES
${QDMI_HEADERS}
${QDMI_GENERATED_HEADERS})

# set required C standard
target_compile_features(qdmi INTERFACE c_std_11)
Expand Down
41 changes: 41 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,47 @@ releases, please refer to the

## [Unreleased]

### Replaceable Client drivers and stable device IDs

QDMI 1.4 defines a stable ABI for replaceable Client driver libraries. Export
every function declared in `qdmi/client.h` with `QDMI_DRIVER_EXPORT`. Define
`QDMI_driver_EXPORTS` while building the driver. A loader first resolves and
calls `QDMI_driver_get_client_abi_version`. It then resolves the complete Client
Interface before it allocates a session. The returned ABI is compatible if and
only if its packed major and minor fields equal those of
`QDMI_CLIENT_ABI_VERSION`. Ignore the patch field when checking compatibility. A
different major or minor field is incompatible. QDMI 1.4 defines
`QDMI_CLIENT_ABI_VERSION` as 1.4.0. CMake derives the ABI version from the QDMI
release version. Device library versions remain independent.

The ABI version query does not initialize the driver. `QDMI_session_alloc` is
the first stateful Client call. It initializes the driver lazily, sets its
output to `NULL` before work that can fail, and leaves no partial session on
failure. Clients can retry a failed allocation. The example driver no longer
exposes `QDMI_driver_init` or `QDMI_driver_shutdown`.

A process uses one Client implementation and can allocate many sessions. Each
initialized session exposes an immutable authorized device catalog. Device,
site, operation, and job handles belong to that session. Free all jobs before
freeing the session. Freeing the session invalidates every remaining descendant
handle.

`QDMI_DEVICE_PROPERTY_ID` is appended as value 18. It is mandatory through the
Client Interface and optional through the Device Interface. A driver supplies
the value when a device returns `QDMI_ERROR_NOTSUPPORTED`. The ID is a nonempty,
opaque string. It is unique within an initialized session, immutable for one
device handle, and stable across equivalent sessions and process restarts while
the same logical resource exists. Persist the driver deployment with the ID. Do
not use a display name, endpoint, pointer, credential, library version, symbol
prefix, or the `QDMI_DEVICE_ID` CMake target property as the runtime ID.

The example driver configuration now gives each device a runtime ID in a third
column:

```text
/path/to/libdevice.so PREFIX deployment.device-id
```

### macOS support

QDMI no longer tests x86 macOS. Generated device projects now target macOS 13.3
Expand Down
4 changes: 2 additions & 2 deletions cmake/PrefixHandling.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ function(generate_prefixed_qdmi_headers prefix)
endforeach()
endfunction()

# Publish the metadata that build-system consumers need to identify a QDMI
# device target.
# Publish the package metadata that build-system consumers need to identify a
# QDMI device target. The ID is not the runtime QDMI_DEVICE_PROPERTY_ID.
function(configure_qdmi_device_target)
cmake_parse_arguments(ARG "" "TARGET;ID;PREFIX" "" ${ARGN})
foreach(required_argument IN ITEMS TARGET ID PREFIX)
Expand Down
27 changes: 27 additions & 0 deletions cmake/client_version.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 - 2026 QDMI Maintainers
* All rights reserved.
*
* Licensed under the Apache License v2.0 with LLVM Exceptions (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://llvm.org/LICENSE.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/

#ifndef QDMI_CLIENT_VERSION_H
#define QDMI_CLIENT_VERSION_H

#include "qdmi/constants.h"

#define QDMI_CLIENT_ABI_VERSION QDMI_MAKE_VERSION(@PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@)

#endif // QDMI_CLIENT_VERSION_H
2 changes: 2 additions & 0 deletions cmake/prefix_defs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ QDMI_Child_Device
QDMI_Child_Device_impl_d
QDMI_STATIC_DEFINE
QDMI_EXPORT
QDMI_DRIVER_EXPORT
QDMI_NO_EXPORT
QDMI_device_EXPORTS
QDMI_driver_EXPORTS
QDMI_DEPRECATED
QDMI_DEPRECATED_EXPORT
QDMI_DEPRECATED_NO_EXPORT
4 changes: 3 additions & 1 deletion docs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ set(DOXYGEN_CONFIG_FILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)

# Set the input directories for doxygen, i.e., all files that contain source
# code with documentation.
set(DOXYGEN_INPUT_DIRS ${PROJECT_SOURCE_DIR}/include)
set(DOXYGEN_INPUT_DIRS ${PROJECT_SOURCE_DIR}/include
${QDMI_GENERATED_INCLUDE_BUILD_DIR})
# This variable is only needed to setup the dependency tracking of CMake
# correctly and is not passed to Doxygen. Whenever such a file is modified,
# CMake detects that change and will rerun the Doxygen command instead of using
Expand All @@ -31,6 +32,7 @@ file(
GLOB_RECURSE
DOXYGEN_INPUT_FILES
${PROJECT_SOURCE_DIR}/include/*.h
${QDMI_GENERATED_INCLUDE_BUILD_DIR}/*.h
${CMAKE_CURRENT_SOURCE_DIR}/*.md
${PROJECT_SOURCE_DIR}/CHANGELOG.md
${PROJECT_SOURCE_DIR}/UPGRADING.md
Expand Down
1 change: 1 addition & 0 deletions docs/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,7 @@ INCLUDE_FILE_PATTERNS =
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

PREDEFINED = "QDMI_EXPORT=" \
"QDMI_DRIVER_EXPORT=" \
"QDMI_NO_EXPORT=" \
"QDMI_DEPRECATED=" \
"QDMI_DEPRECATED_EXPORT=" \
Expand Down
23 changes: 23 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ contained in the `examples/` directory in the repository.

\tableofcontents

## Implementing a Client Driver {#client-driver}

A Client driver is a replaceable shared library. It exports every function in
`qdmi/client.h` with `QDMI_DRIVER_EXPORT`, including
`QDMI_driver_get_client_abi_version`. A loader accepts a version if and only if
its major and minor fields equal those of `QDMI_CLIENT_ABI_VERSION`; the patch
field does not affect compatibility. The loader then resolves the complete
Client Interface before it calls `QDMI_session_alloc`. The ABI query is
side-effect free. Session allocation initializes the driver lazily and returns a
null handle on failure.

The example driver's `QDMI_CONF` file contains one device per line:

```text
/path/to/libdevice.so PREFIX deployment.device-id
```

The third field is the nonempty client-visible `QDMI_DEVICE_PROPERTY_ID`. IDs
must be unique in the configured catalog. The driver reads and validates the
complete file transactionally when it allocates the first session. A failed
allocation can be retried with a corrected file. Device libraries can omit this
property because the Client driver owns the public ID.

## Implementing a Device {#device}

Below you find mock implementations of a QDMI device in C++.
Expand Down
12 changes: 6 additions & 6 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ that. To this end, adjust the `QDMI_REV` variable in
+ set(QDMI_REV "v1.2.0"
```

The generated project assigns the stable ID `prefix.default` to its device.
Change the project-specific `PREFIX_QDMI_DEVICE_ID` CMake cache variable if the
device needs a different ID. Once selected and distributed, keep this ID stable
so that applications and configuration files can continue to refer to the same
device.
The generated project assigns the package ID `prefix.default` to its device
target. Change the project-specific `PREFIX_QDMI_DEVICE_ID` CMake cache variable
if the device package needs a different ID. This build-system value identifies a
packaged device implementation. It is not the runtime `QDMI_DEVICE_PROPERTY_ID`,
which a Client driver assigns to a logical resource.

The device target calls `configure_qdmi_device_target` to export its stable ID
The device target calls `configure_qdmi_device_target` to export its package ID
and symbol prefix as the `QDMI_DEVICE_ID` and `QDMI_DEVICE_PREFIX` target
properties. Build-system consumers such as MQT Core can use this metadata to
package and register the device without project-specific loader code or a
Expand Down
4 changes: 2 additions & 2 deletions examples/device/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ set(CMAKE_VERIFY_INTERFACE_HEADER_SETS
set(QDMI_PREFIX "CXX")
set(CXX_QDMI_DEVICE_ID
"cxx.default"
CACHE STRING "Stable identifier for the CXX QDMI Device")
CACHE STRING "Package identifier for the CXX QDMI Device")

cmake_dependent_option(
INSTALL_CXX_QDMI_DEVICE
Expand Down Expand Up @@ -86,7 +86,7 @@ if(BUILD_CXX_QDMI_TESTS)
get_target_property(EXPORTED_QDMI_DEVICE_PREFIX ${QDMI_TARGET_NAME}
QDMI_DEVICE_PREFIX)
if(NOT EXPORTED_QDMI_DEVICE_ID STREQUAL "${CXX_QDMI_DEVICE_ID}")
message(FATAL_ERROR "The CXX QDMI target does not export its stable ID")
message(FATAL_ERROR "The CXX QDMI target does not export its package ID")
endif()
if(NOT EXPORTED_QDMI_DEVICE_PREFIX STREQUAL "${QDMI_PREFIX}")
message(FATAL_ERROR "The CXX QDMI target does not export its symbol prefix")
Expand Down
6 changes: 3 additions & 3 deletions examples/device/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

A C++20 library that implements the QDMI Device interface.

The exported CMake target publishes the stable device ID configured through
The exported CMake target publishes the package ID configured through
`CXX_QDMI_DEVICE_ID` and the QDMI symbol prefix through
`configure_qdmi_device_target`. Consumers such as MQT Core can use this metadata
to package and register the device without project-specific loader code. This
metadata does not add MQT Core as a dependency.
to package and register the device without project-specific loader code. The
package ID is not the runtime `QDMI_DEVICE_PROPERTY_ID`.

## Documentation

Expand Down
18 changes: 17 additions & 1 deletion examples/device/test/test_cxx_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <cstddef>
#include <gtest/gtest.h>
#include <string>
#include <string_view>
#include <vector>

class QDMIImplementationTest : public ::testing::Test {
Expand All @@ -35,14 +36,23 @@ class QDMIImplementationTest : public ::testing::Test {
ASSERT_EQ(CXX_QDMI_device_session_alloc(&session), QDMI_SUCCESS)
<< "Failed to allocate a session";

constexpr std::string_view token = "token";
ASSERT_EQ(CXX_QDMI_device_session_set_parameter(
session, QDMI_DEVICE_SESSION_PARAMETER_TOKEN,
token.size() + 1, token.data()),
QDMI_SUCCESS);

ASSERT_EQ(CXX_QDMI_device_session_init(session), QDMI_SUCCESS)
<< "Failed to initialize a session. Potential errors: Wrong or missing "
"authentication information, device status is offline, or in "
"maintenance. To provide credentials, take a look in " __FILE__
<< ":" << (__LINE__ - 4);
}

void TearDown() override { CXX_QDMI_device_finalize(); }
void TearDown() override {
CXX_QDMI_device_session_free(session);
CXX_QDMI_device_finalize();
}
};

TEST_F(QDMIImplementationTest, SessionSetParameterImplemented) {
Expand Down Expand Up @@ -170,6 +180,12 @@ TEST_F(QDMIImplementationTest, QueryDeviceVersionImplemented) {
ASSERT_FALSE(value.empty()) << "Devices must provide a version";
}

TEST_F(QDMIImplementationTest, ClientVisibleDeviceIdIsDriverOwned) {
EXPECT_EQ(CXX_QDMI_device_session_query_device_property(
session, QDMI_DEVICE_PROPERTY_ID, 0, nullptr, nullptr),
QDMI_ERROR_NOTSUPPORTED);
}

TEST_F(QDMIImplementationTest, QueryDeviceLibraryVersionImplemented) {
size_t size = 0;
ASSERT_EQ(
Expand Down
12 changes: 7 additions & 5 deletions examples/driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
# add C++ language support
enable_language(CXX)

add_library(qdmi_example_driver qdmi_example_driver.cpp qdmi_example_driver.h)
add_library(qdmi_example_driver SHARED qdmi_example_driver.cpp)
target_link_libraries(qdmi_example_driver PRIVATE qdmi::qdmi
qdmi::qdmi_project_warnings)
target_include_directories(qdmi_example_driver
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(qdmi_example_driver PRIVATE QDMI_driver_EXPORTS)
target_compile_features(qdmi_example_driver PRIVATE cxx_std_20)
set_target_properties(qdmi_example_driver PROPERTIES POSITION_INDEPENDENT_CODE
ON)
set_target_properties(
qdmi_example_driver
PROPERTIES CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
POSITION_INDEPENDENT_CODE ON)
add_library(qdmi::example_driver ALIAS qdmi_example_driver)
Loading