From 374cd7734696a706470092089de215a6bd61c6e5 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 25 Feb 2026 10:55:20 -0500 Subject: [PATCH 1/5] Separate type/constant declarations from functions in C headers When importing the C header files in Python extension-module mode, we will need to generate the function definitions in a completely different manner to regular function prototypes resolved at link time. This separates off the definitions of types and constants (which are shared) from functions (which are not), so that a later patch can add the alternate form. --- crates/bindgen-c/src/main.rs | 4 +- crates/bindgen/include/qiskit.h | 32 +++++++++++ crates/bindgen/include/qiskit/funcs_py.h | 18 +++++++ crates/bindgen/src/lib.rs | 68 ++++++++++++++---------- crates/pyext/build.rs | 4 +- docs/Doxyfile | 4 +- 6 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 crates/bindgen/include/qiskit.h create mode 100644 crates/bindgen/include/qiskit/funcs_py.h diff --git a/crates/bindgen-c/src/main.rs b/crates/bindgen-c/src/main.rs index 2a71d83d9e8a..950f65586208 100644 --- a/crates/bindgen-c/src/main.rs +++ b/crates/bindgen-c/src/main.rs @@ -23,7 +23,7 @@ struct Args { fn main() -> anyhow::Result<()> { let args = Args::parse(); - let bindings = qiskit_bindgen::generate_bindings(&args.cext_path)?; - qiskit_bindgen::install_c_headers(&bindings, &args.install_path)?; + let mut bindings = qiskit_bindgen::generate_bindings(&args.cext_path)?; + qiskit_bindgen::install_c_headers(&mut bindings, &args.install_path)?; Ok(()) } diff --git a/crates/bindgen/include/qiskit.h b/crates/bindgen/include/qiskit.h new file mode 100644 index 000000000000..fd9055cbdcb8 --- /dev/null +++ b/crates/bindgen/include/qiskit.h @@ -0,0 +1,32 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2026 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +#if !defined(QISKIT_H) +#define QISKIT_H + +#if defined(QISKIT_C_PYTHON_INTERFACE) +#include +#endif + +#include "qiskit/attributes.h" +#include "qiskit/complex.h" +#include "qiskit/version.h" + +#include "qiskit/types.h" // Generated by cbindgen. + +#if defined(QISKIT_PYTHON_EXTENSION) +#include "qiskit/funcs_py.h" // Generated by Qiskit's `pyext` (or stubbed out). +#else +#include "qiskit/funcs.h" // Generated by cbindgen. +#endif // QISKIT_PYTHON_EXTENSION + +#endif // QISKIT_H diff --git a/crates/bindgen/include/qiskit/funcs_py.h b/crates/bindgen/include/qiskit/funcs_py.h new file mode 100644 index 000000000000..eb9215333812 --- /dev/null +++ b/crates/bindgen/include/qiskit/funcs_py.h @@ -0,0 +1,18 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2026 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +// This is a stub error file for distributions of the C API libraries that do not include the +// generated access to Python extension modules. It is overwritten by build scripts of the full +// Python package. + +#error This Qiskit distribution does not include the ability to define Python extension modules. \ + Use `qiskit` as a Python-package build dependency. diff --git a/crates/bindgen/src/lib.rs b/crates/bindgen/src/lib.rs index 64eaa8e01bec..b0a9853cf1d1 100644 --- a/crates/bindgen/src/lib.rs +++ b/crates/bindgen/src/lib.rs @@ -10,12 +10,14 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use anyhow::{Context, anyhow}; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; -pub static GENERATED_FILE: &str = "qiskit.h"; +pub static SCOPED_INCLUDE_DIR: &str = "qiskit"; +pub static GENERATED_FILE_TYPES: &str = "types.h"; +pub static GENERATED_FILE_FUNCS: &str = "funcs.h"; + pub static PYTHON_BINDING_FEATURE: &str = "python_binding"; pub static PYTHON_BINDING_DEFINE: &str = "QISKIT_C_PYTHON_INTERFACE"; @@ -33,7 +35,6 @@ pub static COPYRIGHT: &str = "\ // that they have been altered from the originals. "; -pub static INCLUDE_GUARD: &str = "QISKIT_H"; /// Crates that contain definitions of objects that are exposed through the C API. pub static QISKIT_PUBLIC_API_CRATES: &[&str] = &["qiskit-quantum-info", "qiskit-circuit", "qiskit-transpiler"]; @@ -122,6 +123,15 @@ fn get_config() -> anyhow::Result { COPYRIGHT, guarded_python_import(PYTHON_BINDING_DEFINE) )); + // We need to include the `attributes.h` file in all generated files to make sure Doxygen can + // understand the deprecated attributes (even though `qiskit.h` is organised to include it). + let includes = vec![ + Path::new(SCOPED_INCLUDE_DIR) + .join("attributes.h") + .to_str() + .expect("our paths should always be valid utf-8") + .to_owned(), + ]; let enumeration = cbindgen::EnumConfig { prefix_with_name: true, ..Default::default() @@ -160,31 +170,11 @@ fn get_config() -> anyhow::Result { .iter() .map(|&(cfg, def)| (format!("feature = {cfg}"), String::from(def))) .collect(); - // We always use `/` as the path separator to be portable. - let includes = manual_include_files()? - .into_iter() - .map(|buf| { - buf.iter() - .try_fold(String::new(), |mut acc, part| -> anyhow::Result<_> { - let part = part - .to_os_string() - .into_string() - .map_err(|e| anyhow!(e.to_string_lossy().into_owned())) - .context("path could not be converted to UTF-8")?; - if !acc.is_empty() { - acc.push('/'); - } - acc.push_str(&part); - Ok(acc) - }) - }) - .collect::, _>>()?; Ok(cbindgen::Config { header, language: cbindgen::Language::C, - include_version: true, - include_guard: Some(INCLUDE_GUARD.into()), includes, + include_version: true, style: cbindgen::Style::Type, cpp_compat: true, usize_is_size_t: true, @@ -208,14 +198,36 @@ pub fn generate_bindings(cext_path: impl AsRef) -> anyhow::Result, ) -> anyhow::Result<()> { let install_path = install_path.as_ref(); - fs::create_dir_all(install_path)?; + let scoped_install_path = install_path.join(SCOPED_INCLUDE_DIR); + fs::create_dir_all(&scoped_install_path)?; + // _Probably_ globals and constants can be handled just by putting them in the types file + // (because they're likely shared between all access modes to the hedaer file), but since we + // haven't got any yet, we just stay safe and check when some appear. + assert!(bindings.globals.is_empty(), "globals not handled yet"); let mut buf = Vec::::new(); - bindings.write(&mut buf); - fs::File::create(install_path.join(GENERATED_FILE))?.write_all(&buf)?; + { + // First, write out only the types and constants into one file. + let functions = ::std::mem::take(&mut bindings.functions); + bindings.write(&mut buf); + bindings.functions = functions; + fs::File::create(scoped_install_path.join(GENERATED_FILE_TYPES))?.write_all(&buf)?; + buf.clear(); + } + { + // Now, write out the functions into the part of the generated file that's only read when + // we're not in Python-extension mode. + let items = ::std::mem::take(&mut bindings.items); + let constants = ::std::mem::take(&mut bindings.constants); + bindings.write(&mut buf); + bindings.items = items; + bindings.constants = constants; + fs::File::create(scoped_install_path.join(GENERATED_FILE_FUNCS))?.write_all(&buf)?; + buf.clear(); + } let manual_path = manual_include_dir(); for file in manual_include_files()? { if let Some(parent) = file.parent() { diff --git a/crates/pyext/build.rs b/crates/pyext/build.rs index 1015b5236695..c91493408f1e 100644 --- a/crates/pyext/build.rs +++ b/crates/pyext/build.rs @@ -33,9 +33,9 @@ fn main() -> anyhow::Result<()> { path.push("include"); path }; - let bindings = qiskit_bindgen::generate_bindings(&cext_path)?; + let mut bindings = qiskit_bindgen::generate_bindings(&cext_path)?; // We install the headers into our `OUT_DIR`, then we configure `setuptools-rust` to pick them // up from there and put them into the Python package. - qiskit_bindgen::install_c_headers(&bindings, &out_path)?; + qiskit_bindgen::install_c_headers(&mut bindings, &out_path)?; Ok(()) } diff --git a/docs/Doxyfile b/docs/Doxyfile index 31afe280ad9c..c5292e9515ef 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,5 +1,7 @@ -INPUT = docs/cdoc/ dist/c/include/qiskit.h dist/c/include/qiskit/ +INPUT = docs/cdoc/ dist/c/include/ +RECURSIVE = YES +INCLUDE_PATH = dist/c/include OPTIMIZE_OUTPUT_FOR_C = YES ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES From 8d5f3bd52b18bc39acddefd10de1520f06e64931 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 16 Mar 2026 18:36:44 +0000 Subject: [PATCH 2/5] Update include guards Co-authored-by: Max Rossmannek Co-authored-by: Julien Gacon --- crates/bindgen/include/qiskit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bindgen/include/qiskit.h b/crates/bindgen/include/qiskit.h index fd9055cbdcb8..259d8eec8309 100644 --- a/crates/bindgen/include/qiskit.h +++ b/crates/bindgen/include/qiskit.h @@ -13,9 +13,9 @@ #if !defined(QISKIT_H) #define QISKIT_H -#if defined(QISKIT_C_PYTHON_INTERFACE) +#if defined(QISKIT_C_PYTHON_INTERFACE) || defined(QISKIT_PYTHON_EXTENSION) #include -#endif +#endif // defined(QISKIT_C_PYTHON_INTERFACE) || defined(QISKIT_PYTHON_EXTENSION) #include "qiskit/attributes.h" #include "qiskit/complex.h" From 91b16cc9260c035493399045aa3ecb4d3fdb9fc5 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 16 Mar 2026 18:38:47 +0000 Subject: [PATCH 3/5] Comment on seemingly spurious mutability --- crates/bindgen/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bindgen/src/lib.rs b/crates/bindgen/src/lib.rs index b0a9853cf1d1..c12abefeae81 100644 --- a/crates/bindgen/src/lib.rs +++ b/crates/bindgen/src/lib.rs @@ -197,6 +197,10 @@ pub fn generate_bindings(cext_path: impl AsRef) -> anyhow::Result, From 0bfeb42cf29449e14c0f5a050c6e2133c5c7d774 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 16 Mar 2026 18:40:08 +0000 Subject: [PATCH 4/5] Fix typo Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/bindgen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bindgen/src/lib.rs b/crates/bindgen/src/lib.rs index c12abefeae81..a08522b9b1f0 100644 --- a/crates/bindgen/src/lib.rs +++ b/crates/bindgen/src/lib.rs @@ -209,7 +209,7 @@ pub fn install_c_headers( let scoped_install_path = install_path.join(SCOPED_INCLUDE_DIR); fs::create_dir_all(&scoped_install_path)?; // _Probably_ globals and constants can be handled just by putting them in the types file - // (because they're likely shared between all access modes to the hedaer file), but since we + // (because they're likely shared between all access modes to the header file), but since we // haven't got any yet, we just stay safe and check when some appear. assert!(bindings.globals.is_empty(), "globals not handled yet"); let mut buf = Vec::::new(); From 771962ed8ebed32affc58ad28f33b4aeedab8b58 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 17 Mar 2026 10:22:00 +0000 Subject: [PATCH 5/5] Increase explanation of stub file --- crates/bindgen/include/qiskit/funcs_py.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bindgen/include/qiskit/funcs_py.h b/crates/bindgen/include/qiskit/funcs_py.h index eb9215333812..a0068a179ed7 100644 --- a/crates/bindgen/include/qiskit/funcs_py.h +++ b/crates/bindgen/include/qiskit/funcs_py.h @@ -13,6 +13,12 @@ // This is a stub error file for distributions of the C API libraries that do not include the // generated access to Python extension modules. It is overwritten by build scripts of the full // Python package. +// +// The top-level `qiskit.h` defines all the C API functions in a different manner if +// `QISKIT_PYTHON_EXTENSION` is defined. In Python-aware builds/distributions of the C API, this +// file is replaced by one that is safe to use in those situations. We leave this file in place +// with an explicit stub to provide a better explanations to users who have a version of the C API +// without Python support; the alternative is a preprocessor "file not found" error. #error This Qiskit distribution does not include the ability to define Python extension modules. \ Use `qiskit` as a Python-package build dependency.