Skip to content

Commit 2a1c299

Browse files
Add runtime-version checking qk_api_version (#15831)
* Add runtime-version checking `qk_api_version` We currently have `QISKIT_VERSION_HEX` as a macro, which allows programmatically querying the version of the header files used at build time. `qk_api_version` provides the equivalent information for the _library_ version at runtime. This can differ in the case of dynamic linkage, either by regular means or by the manual means in Python extension modules. I wanted to squeeze this into the 2.4 release because it means that safe dynamic-linkage version checking will be available in _every_ version of the C API that practically supports it, including in Python extensions. For aesthetic reasons, I wanted this function (which may become part of the `qk_import` handshake one day) to get a slot at offset 0. * Use the heathen form of hexadecimals Co-authored-by: Max Rossmannek <21973473+mrossinek@users.noreply.github.com> * Handle beta versions completely --------- Co-authored-by: Max Rossmannek <21973473+mrossinek@users.noreply.github.com>
1 parent 85ecc97 commit 2a1c299

6 files changed

Lines changed: 90 additions & 14 deletions

File tree

crates/cext-vtable/src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ pub use crate::impl_::ExportedFunctions;
2121
// =================================================================================================
2222

2323
// We use a (small) number of different tables here so there's more breaks and places to expand.
24-
pub static FUNCTIONS_CIRCUIT: ExportedFunctions = ExportedFunctions::empty()
25-
.add_child(0, &circuit::FUNCTIONS)
26-
.add_child(100, &dag::FUNCTIONS)
27-
.add_child(200, &param::FUNCTIONS)
28-
.add_child(250, &circuit_library::FUNCTIONS);
24+
pub static FUNCTIONS_CIRCUIT: ExportedFunctions =
25+
ExportedFunctions::leaves(5, || vec![impl_::export_fn!(qiskit_cext::qk_api_version)])
26+
.add_child(5, &circuit::FUNCTIONS)
27+
.add_child(105, &dag::FUNCTIONS)
28+
.add_child(205, &param::FUNCTIONS)
29+
.add_child(255, &circuit_library::FUNCTIONS);
2930
pub static FUNCTIONS_QI: ExportedFunctions =
3031
ExportedFunctions::empty().add_child(0, &sparse_observable::FUNCTIONS);
3132
pub use transpiler::FUNCTIONS as FUNCTIONS_TRANSPILE;

crates/cext/src/lib.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,50 @@ pub mod sparse_observable;
2424
pub mod transpiler;
2525

2626
pub use exit_codes::ExitCode;
27+
28+
/// Get the C API version of the loaded library.
29+
///
30+
/// If you are dynamically linking against Qiskit, in either a stand-alone or Python-extension
31+
/// build, this function can be useful to check the version of the library actually loaded at
32+
/// runtime. The header-file macro `QISKIT_VERSION_HEX` is the equivalent of this function, but
33+
/// for the version of the headers used at build time.
34+
///
35+
/// @return The version of the compiled Qiskit C API library, in the same format as the
36+
/// `QISKIT_VERSION_HEX` header-file macro.
37+
#[unsafe(no_mangle)]
38+
pub extern "C" fn qk_api_version() -> u32 {
39+
const VERSION: u32 = {
40+
const fn parse_int(s: &[u8]) -> u32 {
41+
let mut i = 0;
42+
let mut out = 0;
43+
while i < s.len() {
44+
if s[i] < b'0' || s[i] > b'9' {
45+
panic!("not a positive integer");
46+
}
47+
out *= 10;
48+
out += (s[i] - b'0') as u32;
49+
i += 1;
50+
}
51+
out
52+
}
53+
54+
let mut v = (parse_int(env!("CARGO_PKG_VERSION_MAJOR").as_bytes()) << 24)
55+
| (parse_int(env!("CARGO_PKG_VERSION_MINOR").as_bytes()) << 16)
56+
| (parse_int(env!("CARGO_PKG_VERSION_PATCH").as_bytes()) << 8);
57+
let pre = env!("CARGO_PKG_VERSION_PRE");
58+
let pre = pre.as_bytes();
59+
if let Some((b"dev", serial)) = pre.split_at_checked(3) {
60+
v |= 0xa0 | parse_int(serial);
61+
} else if let Some((b"beta", serial)) = pre.split_at_checked(4) {
62+
v |= 0xb0 | parse_int(serial);
63+
} else if let Some((b"rc", serial)) = pre.split_at_checked(2) {
64+
v |= 0xc0 | parse_int(serial);
65+
} else if pre.is_empty() {
66+
v |= 0xf0;
67+
} else {
68+
panic!("could not parse 'CARGO_PKG_VERSION_PRE'");
69+
}
70+
v
71+
};
72+
VERSION
73+
}

crates/pyext/src/capi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ static FFI_QI: LazyLock<VTable> =
7777
pub fn capi_mod(m: &Bound<'_, PyModule>) -> PyResult<()> {
7878
static MODNAME: &str = "qiskit._accelerate.capi";
7979

80+
m.add("API_VERSION", qiskit_cext::qk_api_version())?;
8081
FFI_TRANSPILE.attach_pycapsule(m, MODNAME)?;
8182
FFI_CIRCUIT.attach_pycapsule(m, MODNAME)?;
8283
FFI_QI.attach_pycapsule(m, MODNAME)?;

docs/cdoc/version.rst

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ Qiskit's version can be queried using a set of compiler macros.
66

77
.. c:macro:: QISKIT_VERSION
88
9-
A human-readable version as ``char*``. For example: ``"2.1.0rc1"``.
9+
A human-readable version as ``char*``. For example: ``"2.1.0-rc1"``.
1010

1111
.. c:macro:: QISKIT_VERSION_HEX
1212
13-
The version number as 4-byte hexadecimal. The format is ``0xMMmmppls``, where
13+
The version number as 4-byte hexadecimal. The format is ``0xMMmmppls``, where
1414
``M`` is the major, ``m`` is the minor, ``p`` is the patch, ``l`` is the release level
15-
and ``s`` is the serial. For example, 2.1.0rc1 is ``0x020100C1``.
15+
and ``s`` is the serial. For example, 2.1.0-rc1 is ``0x020100C1``.
16+
17+
Note that final versions, such as ``2.1.0``, will always end with ``0xF0`` in the last byte.
18+
19+
See :c:func:`qk_api_version` for the runtime version of this.
1620

1721
.. c:macro:: QISKIT_VERSION_MAJOR
1822
@@ -28,22 +32,28 @@ Qiskit's version can be queried using a set of compiler macros.
2832

2933
.. c:macro:: QISKIT_RELEASE_LEVEL
3034
31-
The release level: ``0xA`` for the unreleased dev (or alpha) version, ``0xC`` for the
32-
release candidate, and ``0xF`` for the stable (or final) version.
35+
The release level:
36+
37+
* ``0xA`` for an unreleased dev (or alpha) version
38+
* ``0xB`` for a beta version
39+
* ``0xC`` for a release candidate
40+
* ``0xF`` for a stable (or final) version.
3341

3442
.. c:macro:: QISKIT_RELEASE_SERIAL
3543
36-
This can be used to indicate the pre-release number in a pre-release series.
37-
For example, this would be set to ``1`` for ``2.1.0rc1``. This is ``0`` for the final version.
44+
This can be used to indicate the pre-release number in a pre-release series.
45+
For example, this would be set to ``4`` for ``2.1.0-rc4``. This is ``0`` for the final version.
3846

3947
.. c:macro:: QISKIT_GET_VERSION_HEX(major, minor, patch, level, serial)
4048
41-
A macro to pack the version numbers into hexadecimal format. This can be used as
42-
tool to compare numbers, for example to ensure the current version is at least the
49+
A macro to pack the version numbers into hexadecimal format. This can be used as
50+
tool to compare numbers, for example to ensure the current version is at least the
4351
stable 2.1.0 release do:
4452

4553
.. code-block:: c
4654
4755
if (QISKIT_VERSION_HEX >= QISKIT_GET_VERSION_HEX(2, 1, 0, 0xF, 0)) {
4856
// Code for version 2.1.0 (final) or later
4957
}
58+
59+
.. doxygenfunction:: qk_api_version
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
features_c:
3+
- The C API now provides :c:func:`qk_api_version`, which returns the version of the library that
4+
has been loaded at runtime. The return format is the same as :c:macro:`QISKIT_VERSION_HEX`.

test/c/test_version_info.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,23 @@ static int test_version_macros(void) {
7777
return Ok;
7878
}
7979

80+
/**
81+
* Test the version in the header matches the version returned from the library.
82+
*/
83+
static int test_header_vs_lib(void) {
84+
if (qk_api_version() != QISKIT_VERSION_HEX) {
85+
fprintf(stderr, "%s: QISKIT_VERSION_HEX (%x) does not match qk_api_version() (%x)\n",
86+
__func__, QISKIT_VERSION_HEX, qk_api_version());
87+
return EqualityError;
88+
}
89+
return Ok;
90+
}
91+
8092
int test_version_info(void) {
8193
int num_failed = 0;
8294
num_failed += RUN_TEST(test_version);
8395
num_failed += RUN_TEST(test_version_macros);
96+
num_failed += RUN_TEST(test_header_vs_lib);
8497

8598
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);
8699
fflush(stderr);

0 commit comments

Comments
 (0)