Skip to content
Merged
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
4 changes: 2 additions & 2 deletions crates/bindgen-c/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Comment thread
jakelishman marked this conversation as resolved.
qiskit_bindgen::install_c_headers(&mut bindings, &args.install_path)?;
Ok(())
}
32 changes: 32 additions & 0 deletions crates/bindgen/include/qiskit.h
Original file line number Diff line number Diff line change
@@ -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) || defined(QISKIT_PYTHON_EXTENSION)
#include <Python.h>
#endif // defined(QISKIT_C_PYTHON_INTERFACE) || defined(QISKIT_PYTHON_EXTENSION)

#include "qiskit/attributes.h"
#include "qiskit/complex.h"
#include "qiskit/version.h"
Comment thread
jakelishman marked this conversation as resolved.

#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
24 changes: 24 additions & 0 deletions crates/bindgen/include/qiskit/funcs_py.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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.
Comment thread
mrossinek marked this conversation as resolved.
//
// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// with an explicit stub to provide a better explanations to users who have a version of the C API
// with an explicit stub to provide a better explanation to users who have a version of the C API

Either this or remove the a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch -- maybe Jake can just add this to one of the follow up PRs.

// 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.
72 changes: 44 additions & 28 deletions crates/bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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"];
Expand Down Expand Up @@ -122,6 +123,15 @@ fn get_config() -> anyhow::Result<cbindgen::Config> {
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).
Comment thread
jakelishman marked this conversation as resolved.
let includes = vec![
Path::new(SCOPED_INCLUDE_DIR)
.join("attributes.h")
.to_str()
.expect("our paths should always be valid utf-8")
.to_owned(),
];
Comment thread
jakelishman marked this conversation as resolved.
let enumeration = cbindgen::EnumConfig {
prefix_with_name: true,
..Default::default()
Expand Down Expand Up @@ -160,31 +170,11 @@ fn get_config() -> anyhow::Result<cbindgen::Config> {
.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::<Result<Vec<_>, _>>()?;
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,
Expand All @@ -207,15 +197,41 @@ pub fn generate_bindings(cext_path: impl AsRef<Path>) -> anyhow::Result<cbindgen
}

/// Install the complete stand-alone C include path into the given directory.
///
/// This takes `&mut Bindings` only as an internal implementation detail of how separated-out
/// include files are written; the bindings will always be returned to their original state when
/// the function returns.
pub fn install_c_headers(
bindings: &cbindgen::Bindings,
bindings: &mut cbindgen::Bindings,
install_path: impl AsRef<Path>,
) -> 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 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::<u8>::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();
}
Comment thread
jakelishman marked this conversation as resolved.
{
// 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() {
Expand Down
4 changes: 2 additions & 2 deletions crates/pyext/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
4 changes: 3 additions & 1 deletion docs/Doxyfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down