rusteron_code_gen/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(clippy::all)]
5#![allow(unused_unsafe)]
6#![allow(unused_variables)]
7#![doc = include_str!("../README.md")]
8
9mod common;
10mod generator;
11mod parser;
12
13pub use common::*;
14pub use generator::*;
15pub use parser::*;
16
17use proc_macro2::TokenStream;
18use std::fs::OpenOptions;
19use std::io::Write;
20use std::process::{Command, Stdio};
21
22pub const CUSTOM_AERON_CODE: &str = include_str!("./aeron_custom.rs");
23pub const COMMON_CODE: &str = include_str!("./common.rs");
24
25pub fn append_to_file(file_path: &str, code: &str) -> std::io::Result<()> {
26    // Open the file in append mode
27    let mut file = OpenOptions::new()
28        .create(true)
29        .write(true)
30        .append(true)
31        .open(file_path)?;
32
33    // Write the generated code to the file
34    writeln!(file, "\n{}", code)?;
35
36    Ok(())
37}
38
39#[allow(dead_code)]
40pub fn format_with_rustfmt(code: &str) -> Result<String, std::io::Error> {
41    let mut rustfmt = Command::new("rustfmt")
42        .stdin(Stdio::piped())
43        .stdout(Stdio::piped())
44        .spawn()?;
45
46    if let Some(mut stdin) = rustfmt.stdin.take() {
47        stdin.write_all(code.as_bytes())?;
48    }
49
50    let output = rustfmt.wait_with_output()?;
51    let formatted_code = String::from_utf8_lossy(&output.stdout).to_string();
52
53    Ok(formatted_code)
54}
55
56#[allow(dead_code)]
57pub fn format_token_stream(tokens: TokenStream) -> String {
58    let code = tokens.to_string();
59
60    match format_with_rustfmt(&code) {
61        Ok(formatted_code) if !formatted_code.trim().is_empty() => formatted_code,
62        _ => code.replace("{", "{\n"), // Fallback to unformatted code in case of error
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::generator::MEDIA_DRIVER_BINDINGS;
69    use crate::parser::parse_bindings;
70    use crate::{
71        append_to_file, format_token_stream, format_with_rustfmt, ARCHIVE_BINDINGS,
72        CLIENT_BINDINGS, CUSTOM_AERON_CODE, RB,
73    };
74    use proc_macro2::TokenStream;
75    use std::fs;
76
77    #[test]
78    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
79    fn client() {
80        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/client.rs".into());
81        assert_eq!(
82            "AeronImageFragmentAssembler",
83            bindings
84                .wrappers
85                .get("aeron_image_fragment_assembler_t")
86                .unwrap()
87                .class_name
88        );
89        assert_eq!(
90            0,
91            bindings.methods.len(),
92            "expected all methods to have been matched {:#?}",
93            bindings.methods
94        );
95
96        let file = write_to_file(TokenStream::new(), true, "client.rs");
97        let bindings_copy = bindings.clone();
98        for handler in bindings.handlers.iter_mut() {
99            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
100            let _ = crate::generate_handlers(handler, &bindings_copy);
101        }
102        for (p, w) in bindings.wrappers.values().enumerate() {
103            let code = crate::generate_rust_code(
104                w,
105                &bindings.wrappers,
106                p == 0,
107                true,
108                true,
109                &bindings.handlers,
110            );
111            write_to_file(code, false, "client.rs");
112        }
113        let bindings_copy = bindings.clone();
114        for handler in bindings.handlers.iter_mut() {
115            let code = crate::generate_handlers(handler, &bindings_copy);
116            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
117        }
118
119        let t = trybuild::TestCases::new();
120        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
121        append_to_file(&file, CLIENT_BINDINGS).unwrap();
122        append_to_file(&file, "}").unwrap();
123        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
124        append_to_file(&file, "\npub fn main() {}\n").unwrap();
125        t.pass(file)
126    }
127
128    #[test]
129    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
130    fn media_driver() {
131        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/media-driver.rs".into());
132        assert_eq!(
133            "AeronImageFragmentAssembler",
134            bindings
135                .wrappers
136                .get("aeron_image_fragment_assembler_t")
137                .unwrap()
138                .class_name
139        );
140
141        let file = write_to_file(TokenStream::new(), true, "md.rs");
142
143        let bindings_copy = bindings.clone();
144        for handler in bindings.handlers.iter_mut() {
145            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
146            let _ = crate::generate_handlers(handler, &bindings_copy);
147        }
148        for (p, w) in bindings
149            .wrappers
150            .values()
151            .filter(|w| !w.type_name.contains("_t_") && w.type_name != "in_addr")
152            .enumerate()
153        {
154            let code = crate::generate_rust_code(
155                w,
156                &bindings.wrappers,
157                p == 0,
158                true,
159                true,
160                &bindings.handlers,
161            );
162            write_to_file(code, false, "md.rs");
163        }
164        let bindings_copy = bindings.clone();
165        for handler in bindings.handlers.iter_mut() {
166            let code = crate::generate_handlers(handler, &bindings_copy);
167            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
168        }
169        let t = trybuild::TestCases::new();
170        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
171        append_to_file(&file, MEDIA_DRIVER_BINDINGS).unwrap();
172        append_to_file(&file, "}").unwrap();
173        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
174        append_to_file(&file, "\npub fn main() {}\n").unwrap();
175        t.pass(&file)
176    }
177
178    #[test]
179    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
180    fn rb() {
181        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/rb.rs".into());
182
183        let file = write_to_file(TokenStream::new(), true, "rb.rs");
184
185        let bindings_copy = bindings.clone();
186        for handler in bindings.handlers.iter_mut() {
187            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
188            let _ = crate::generate_handlers(handler, &bindings_copy);
189        }
190
191        for (p, w) in bindings.wrappers.values().enumerate() {
192            let code = crate::generate_rust_code(
193                w,
194                &bindings.wrappers,
195                p == 0,
196                true,
197                false,
198                &bindings.handlers,
199            );
200            if code.to_string().contains("ndler : Option < AeronCloseClientHandlerImpl > , rbd :) -> Result < Self , AeronCError > { let resource = Manage") {
201                panic!("{}", format_token_stream(code));
202            }
203
204            write_to_file(code, false, "rb.rs");
205        }
206
207        let bindings_copy = bindings.clone();
208        for handler in bindings.handlers.iter_mut() {
209            let code = crate::generate_handlers(handler, &bindings_copy);
210            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
211        }
212
213        let t = trybuild::TestCases::new();
214        append_to_file(&file, RB).unwrap();
215        append_to_file(&file, "\npub fn main() {}\n").unwrap();
216        t.pass(file)
217    }
218
219    #[test]
220    #[cfg(not(target_os = "windows"))] // the generated bindings have different sizes
221    fn archive() {
222        let mut bindings = parse_bindings(&"../rusteron-code-gen/bindings/archive.rs".into());
223        assert_eq!(
224            "AeronImageFragmentAssembler",
225            bindings
226                .wrappers
227                .get("aeron_image_fragment_assembler_t")
228                .unwrap()
229                .class_name
230        );
231
232        let file = write_to_file(TokenStream::new(), true, "archive.rs");
233        let bindings_copy = bindings.clone();
234        for handler in bindings.handlers.iter_mut() {
235            // need to run this first so I know the FnMut(xxxx) which is required in generate_rust_code
236            let _ = crate::generate_handlers(handler, &bindings_copy);
237        }
238        for (p, w) in bindings.wrappers.values().enumerate() {
239            let code = crate::generate_rust_code(
240                w,
241                &bindings.wrappers,
242                p == 0,
243                true,
244                true,
245                &bindings.handlers,
246            );
247            write_to_file(code, false, "archive.rs");
248        }
249        let bindings_copy = bindings.clone();
250        for handler in bindings.handlers.iter_mut() {
251            let code = crate::generate_handlers(handler, &bindings_copy);
252            append_to_file(&file, &format_with_rustfmt(&code.to_string()).unwrap()).unwrap();
253        }
254        let t = trybuild::TestCases::new();
255        append_to_file(&file, "use bindings::*; mod bindings { ").unwrap();
256        append_to_file(&file, ARCHIVE_BINDINGS).unwrap();
257        append_to_file(&file, "}").unwrap();
258        append_to_file(&file, CUSTOM_AERON_CODE).unwrap();
259        append_to_file(&file, "\npub fn main() {}\n").unwrap();
260        t.pass(file)
261    }
262
263    fn write_to_file(rust_code: TokenStream, delete: bool, name: &str) -> String {
264        let src = format_token_stream(rust_code);
265        let path = format!("../target/{name}");
266        let path = &path;
267        if delete {
268            let _ = fs::remove_file(path);
269        }
270        append_to_file(path, &src).unwrap();
271        path.to_string()
272    }
273}
274
275#[cfg(test)]
276mod test {
277    use crate::ManagedCResource;
278    use std::sync::atomic::{AtomicBool, Ordering};
279    use std::sync::Arc;
280
281    fn make_resource(val: i32) -> *mut i32 {
282        Box::into_raw(Box::new(val))
283    }
284
285    #[test]
286    fn test_drop_calls_cleanup_non_borrowed_no_cleanup_struct() {
287        let flag = Arc::new(AtomicBool::new(false));
288        let flag_clone = flag.clone();
289        let resource_ptr = make_resource(10);
290
291        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
292            flag_clone.store(true, Ordering::SeqCst);
293            // Set the resource to null to simulate cleanup.
294            unsafe {
295                *res = std::ptr::null_mut();
296            }
297            0
298        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
299
300        {
301            let _resource = ManagedCResource::new(
302                |res: *mut *mut i32| {
303                    unsafe {
304                        *res = resource_ptr;
305                    }
306                    0
307                },
308                cleanup,
309                false,
310                None,
311            )
312            .unwrap();
313        }
314        assert!(flag.load(Ordering::SeqCst));
315    }
316
317    #[test]
318    fn test_drop_calls_cleanup_non_borrowed_with_cleanup_struct() {
319        let flag = Arc::new(AtomicBool::new(false));
320        let flag_clone = flag.clone();
321        let resource_ptr = make_resource(20);
322
323        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
324            flag_clone.store(true, Ordering::SeqCst);
325            unsafe {
326                *res = std::ptr::null_mut();
327            }
328            0
329        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
330
331        {
332            let _resource = ManagedCResource::new(
333                |res: *mut *mut i32| {
334                    unsafe {
335                        *res = resource_ptr;
336                    }
337                    0
338                },
339                cleanup,
340                true,
341                None,
342            )
343            .unwrap();
344        }
345        assert!(flag.load(Ordering::SeqCst));
346    }
347
348    #[test]
349    fn test_drop_does_not_call_cleanup_if_already_closed() {
350        let flag = Arc::new(AtomicBool::new(false));
351        let flag_clone = flag.clone();
352        let resource_ptr = make_resource(30);
353
354        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
355            flag_clone.store(true, Ordering::SeqCst);
356            unsafe {
357                *res = std::ptr::null_mut();
358            }
359            0
360        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
361
362        let mut resource = ManagedCResource::new(
363            |res: *mut *mut i32| {
364                unsafe {
365                    *res = resource_ptr;
366                }
367                0
368            },
369            cleanup,
370            false,
371            None,
372        )
373        .unwrap();
374
375        resource.close().unwrap();
376        // Reset the flag to ensure drop does not call cleanup a second time.
377        flag.store(false, Ordering::SeqCst);
378        drop(resource);
379        assert!(!flag.load(Ordering::SeqCst));
380    }
381
382    #[test]
383    fn test_drop_does_not_call_cleanup_if_check_for_is_closed_returns_true() {
384        let flag = Arc::new(AtomicBool::new(false));
385        let flag_clone = flag.clone();
386        let resource_ptr = make_resource(60);
387
388        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
389            flag_clone.store(true, Ordering::SeqCst);
390            unsafe {
391                *res = std::ptr::null_mut();
392            }
393            0
394        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
395
396        let check_fn = Some(|_res: *mut i32| -> bool { true } as fn(_) -> bool);
397
398        {
399            let _resource = ManagedCResource::new(
400                |res: *mut *mut i32| {
401                    unsafe {
402                        *res = resource_ptr;
403                    }
404                    0
405                },
406                cleanup,
407                false,
408                check_fn,
409            )
410            .unwrap();
411        }
412        assert!(!flag.load(Ordering::SeqCst));
413    }
414
415    #[test]
416    fn test_drop_does_call_cleanup_if_check_for_is_closed_returns_false() {
417        let flag = Arc::new(AtomicBool::new(false));
418        let flag_clone = flag.clone();
419        let resource_ptr = make_resource(60);
420
421        let cleanup = Some(Box::new(move |res: *mut *mut i32| -> i32 {
422            flag_clone.store(true, Ordering::SeqCst);
423            unsafe {
424                *res = std::ptr::null_mut();
425            }
426            0
427        }) as Box<dyn FnMut(*mut *mut i32) -> i32>);
428
429        let check_fn = Some(|_res: *mut i32| -> bool { false } as fn(*mut i32) -> bool);
430
431        {
432            let _resource = ManagedCResource::new(
433                |res: *mut *mut i32| {
434                    unsafe {
435                        *res = resource_ptr;
436                    }
437                    0
438                },
439                cleanup,
440                false,
441                check_fn,
442            )
443            .unwrap();
444        }
445        assert!(flag.load(Ordering::SeqCst));
446    }
447}