1010// copyright notice, and modified files need to carry a notice indicating
1111// that they have been altered from the originals.
1212
13- use anyhow:: anyhow;
13+ use cbindgen:: bindgen:: ir;
14+ use hashbrown:: HashMap ;
15+ use qiskit_cext_vtable:: { FUNCTIONS_CIRCUIT , FUNCTIONS_QI , FUNCTIONS_TRANSPILE } ;
16+ use std:: fs;
17+ use std:: io:: Write ;
1418use std:: path:: Path ;
1519
20+ static WRAPPER_FUNCS : & str = "funcs_py.h" ;
21+ static GENERATED_FUNCS : & str = "funcs_py_generated.h" ;
22+
23+ /// Render a given type object into a string representing it in C.
24+ fn render_type_as_c ( ty : & ir:: Type , config : & cbindgen:: Config ) -> String {
25+ fn render ( ty : & ir:: Type , config : & cbindgen:: Config , acc : & mut String ) {
26+ dbg ! ( ty) ;
27+ match ty {
28+ ir:: Type :: Ptr {
29+ ty,
30+ is_const,
31+ is_nullable : _,
32+ is_ref,
33+ } => {
34+ assert ! ( !is_ref, "C++ reference-likes not handled" ) ;
35+ if * is_const {
36+ acc. push_str ( "const " ) ;
37+ }
38+ render ( ty, config, acc) ;
39+ acc. push_str ( " *" ) ;
40+ }
41+ ir:: Type :: Path ( p) => acc. push_str ( p. export_name ( ) ) ,
42+ ir:: Type :: Primitive ( ty) => acc. push_str ( ty. to_repr_c ( config) ) ,
43+ ir:: Type :: Array ( ..) => todo ! ( "array types not yet handled" ) ,
44+ ir:: Type :: FuncPtr {
45+ args,
46+ ret,
47+ is_nullable,
48+ never_return,
49+ } => {
50+ assert ! ( !is_nullable, "nullability of funcptrs is not handled" ) ;
51+ assert ! ( !never_return, "diverging functions not handled" ) ;
52+ render ( ret, config, acc) ;
53+ acc. push_str ( "(*)(" ) ;
54+ let mut args = args. iter ( ) ;
55+ if let Some ( ( _, first) ) = args. next ( ) {
56+ render ( first, config, acc) ;
57+ for ( _, arg) in args {
58+ acc. push_str ( ", " ) ;
59+ render ( arg, config, acc)
60+ }
61+ }
62+ acc. push ( ')' ) ;
63+ }
64+ }
65+ }
66+ let mut acc = String :: new ( ) ;
67+ render ( ty, config, & mut acc) ;
68+ acc
69+ }
70+
71+ /// Calculate a mapping of exported function names to C casts to appropriate function-pointer types.
72+ fn functions_as_c_funcptr_casts ( bindings : & cbindgen:: Bindings ) -> HashMap < & str , String > {
73+ let to_funcptr = |func : & ir:: Function | {
74+ let to_funcptr_arg = |arg : & ir:: FunctionArgument | {
75+ let ir:: FunctionArgument {
76+ name : _,
77+ ty,
78+ array_length,
79+ } = arg;
80+ assert ! ( array_length. is_none( ) , "array arguments not handled" ) ;
81+ ( None , ty. clone ( ) )
82+ } ;
83+ ir:: Type :: FuncPtr {
84+ ret : Box :: new ( func. ret . clone ( ) ) ,
85+ args : func. args . iter ( ) . map ( to_funcptr_arg) . collect ( ) ,
86+ is_nullable : false ,
87+ never_return : false ,
88+ }
89+ } ;
90+ let config = & bindings. config ;
91+ bindings
92+ . functions
93+ . iter ( )
94+ . map ( |func| {
95+ let funcptr = to_funcptr ( func) ;
96+ ( func. path . name ( ) , render_type_as_c ( & funcptr, config) )
97+ } )
98+ . collect ( )
99+ }
100+
101+ /// Install (overwriting) the Python-extension-specific header files into the given directory.
102+ fn install_py_function_headers (
103+ bindings : & cbindgen:: Bindings ,
104+ install_path : impl AsRef < Path > ,
105+ ) -> anyhow:: Result < ( ) > {
106+ let mut our_include = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "include" ) ;
107+ our_include. push ( qiskit_bindgen:: SCOPED_INCLUDE_DIR ) ;
108+ // This directory must already have been constructed by the previous "install" command for the
109+ // regular C headers; if it doesn't, writing out the files will be a mistake because we _should_
110+ // be overwriting an existing file (the wrapper that defines `qk_import`).
111+ let install_path = install_path
112+ . as_ref ( )
113+ . join ( qiskit_bindgen:: SCOPED_INCLUDE_DIR ) ;
114+ fs:: copy (
115+ our_include. join ( WRAPPER_FUNCS ) ,
116+ install_path. join ( WRAPPER_FUNCS ) ,
117+ ) ?;
118+ let mut funcs_header = fs:: File :: create ( install_path. join ( GENERATED_FUNCS ) ) ?;
119+ writeln ! ( funcs_header, "{}" , qiskit_bindgen:: COPYRIGHT ) ?;
120+
121+ // Now, each function's name is just a preprocessor macro that resolves to a lookup into the
122+ // corresponding table. The names given here need to match with the handwritten include file
123+ // that sets up the slots in `qk_import`.
124+ let vtables = [
125+ ( "_Qk_API_Circuit" , & FUNCTIONS_CIRCUIT ) ,
126+ ( "_Qk_API_Transpile" , & FUNCTIONS_TRANSPILE ) ,
127+ ( "_Qk_API_QI" , & FUNCTIONS_QI ) ,
128+ ] ;
129+ let funcs = functions_as_c_funcptr_casts ( bindings) ;
130+ for ( vtable_name, vtable) in vtables {
131+ for export in vtable. exports ( 0 ) {
132+ writeln ! (
133+ funcs_header,
134+ "#define {} (*({})({}[{}]))" ,
135+ export. name, funcs[ export. name] , vtable_name, export. slot
136+ ) ?;
137+ }
138+ }
139+ Ok ( ( ) )
140+ }
141+
16142#[ allow( clippy:: print_stdout) ] // We're a build script - we're _supposed_ to print to stdout.
17143fn main ( ) -> anyhow:: Result < ( ) > {
144+ // Our actual requirements for re-running the build script are if `cext-vtable` changes, but
145+ // since that's a build-time dependency, it's already implicit in Cargo's logic, so we just need
146+ // to issue _any_ re-run command to avoid the default behaviour of rerunning if `qiskit_pyext`
147+ // itself changes.
148+ println ! ( "cargo::rerun-if-changed=build.rs" ) ;
18149 let cext_path = {
19150 let mut path = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . to_path_buf ( ) ;
20151 path. pop ( ) ;
21152 path. push ( "cext" ) ;
22153 path
23154 } ;
24- println ! (
25- "cargo::rerun-if-changed={}" ,
26- cext_path
27- . to_str( )
28- . ok_or_else( || anyhow!( "cext path isn't unicode" ) ) ?
29- ) ;
30155 let out_path = {
31156 let out_dir = std:: env:: var ( "OUT_DIR" ) . expect ( "cargo should set this for build scripts" ) ;
32157 let mut path = Path :: new ( & out_dir) . to_path_buf ( ) ;
@@ -37,5 +162,6 @@ fn main() -> anyhow::Result<()> {
37162 // We install the headers into our `OUT_DIR`, then we configure `setuptools-rust` to pick them
38163 // up from there and put them into the Python package.
39164 qiskit_bindgen:: install_c_headers ( & mut bindings, & out_path) ?;
165+ install_py_function_headers ( & bindings, & out_path) ?;
40166 Ok ( ( ) )
41167}
0 commit comments