Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 1 | // Copyright 2022 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | use indexmap::IndexMap; |
| 10 | use proc_macro2::TokenStream; |
| 11 | use serde::{Deserialize, Serialize}; |
| 12 | |
| 13 | use crate::IncludeCppConfig; |
| 14 | |
| 15 | /// Struct which stores multiple sets of bindings and can be serialized |
| 16 | /// to disk. This is used when our build system uses `autocxx_gen`; that |
| 17 | /// can handle multiple `include_cpp!` macros and therefore generate multiple |
| 18 | /// sets of Rust bindings. We can't simply `include!` those because there's |
| 19 | /// no (easy) way to pass their details from the codegen phase across to |
| 20 | /// the Rust macro phase. Instead, we use this data structure to store |
| 21 | /// several sets of .rs bindings in a single file, and then the macro |
| 22 | /// extracts the correct set of bindings at expansion time. |
| 23 | #[derive(Serialize, Deserialize, Default)] |
| 24 | pub struct MultiBindings(IndexMap<u64, String>); |
| 25 | |
| 26 | use thiserror::Error; |
| 27 | |
| 28 | #[derive(Error, Debug)] |
| 29 | pub enum MultiBindingsErr { |
| 30 | #[error("unable to find the desired bindings within the archive of Rust bindings produced by the autocxx code generation phase")] |
| 31 | MissingBindings, |
| 32 | #[error("the stored bindings within the JSON file could not be parsed as valid Rust tokens")] |
| 33 | BindingsNotParseable, |
| 34 | } |
| 35 | |
| 36 | impl MultiBindings { |
| 37 | /// Insert some generated Rust bindings into this data structure. |
| 38 | pub fn insert(&mut self, config: &IncludeCppConfig, bindings: TokenStream) { |
| 39 | self.0.insert(config.get_hash(), bindings.to_string()); |
| 40 | } |
| 41 | |
| 42 | /// Retrieves the bindings corresponding to a given [`IncludeCppConfig`]. |
| 43 | pub fn get(&self, config: &IncludeCppConfig) -> Result<TokenStream, MultiBindingsErr> { |
| 44 | match self.0.get(&(config.get_hash())) { |
| 45 | None => Err(MultiBindingsErr::MissingBindings), |
| 46 | Some(bindings) => Ok(bindings |
| 47 | .parse() |
| 48 | .map_err(|_| MultiBindingsErr::BindingsNotParseable)?), |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | #[cfg(test)] |
| 54 | mod tests { |
| 55 | use proc_macro2::Span; |
| 56 | use quote::quote; |
| 57 | use syn::parse_quote; |
| 58 | |
| 59 | use crate::IncludeCppConfig; |
| 60 | |
| 61 | use super::MultiBindings; |
| 62 | |
| 63 | #[test] |
| 64 | fn test_multi_bindings() { |
| 65 | let hexathorpe = syn::token::Pound(Span::call_site()); |
| 66 | let config1: IncludeCppConfig = parse_quote! { |
| 67 | #hexathorpe include "a.h" |
| 68 | generate!("Foo") |
| 69 | }; |
| 70 | let config2: IncludeCppConfig = parse_quote! { |
| 71 | #hexathorpe include "b.h" |
| 72 | generate!("Bar") |
| 73 | }; |
| 74 | let config3: IncludeCppConfig = parse_quote! { |
| 75 | #hexathorpe include "c.h" |
| 76 | generate!("Bar") |
| 77 | }; |
| 78 | let mut multi_bindings = MultiBindings::default(); |
| 79 | multi_bindings.insert(&config1, quote! { first; }); |
| 80 | multi_bindings.insert(&config2, quote! { second; }); |
| 81 | let json = serde_json::to_string(&multi_bindings).unwrap(); |
| 82 | let multi_bindings2: MultiBindings = serde_json::from_str(&json).unwrap(); |
| 83 | assert_eq!( |
| 84 | multi_bindings2.get(&config2).unwrap().to_string(), |
| 85 | "second ;" |
| 86 | ); |
| 87 | assert_eq!( |
| 88 | multi_bindings2.get(&config1).unwrap().to_string(), |
| 89 | "first ;" |
| 90 | ); |
| 91 | assert!(multi_bindings2.get(&config3).is_err()); |
| 92 | } |
| 93 | } |