system_harness_macros/
lib.rs1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 parse_macro_input, spanned::Spanned, AttrStyle, Attribute, Data, DataEnum, DataStruct,
5 DeriveInput, Field, Fields, FieldsNamed, Ident, LitStr, Variant,
6};
7
8type Result<T> = std::result::Result<T, syn::Error>;
9
10#[proc_macro_derive(PropertyList)]
11pub fn property_list(input: TokenStream) -> TokenStream {
12 let derive_input = parse_macro_input!(input as DeriveInput);
13 match impl_proplist(&derive_input) {
14 Ok(props) => props.into(),
15 Err(err) => err.into_compile_error().into(),
16 }
17}
18
19#[proc_macro_derive(Backend)]
20pub fn backend(input: TokenStream) -> TokenStream {
21 let derive_input = parse_macro_input!(input as DeriveInput);
22 match impl_backends(&derive_input) {
23 Ok(props) => props.into(),
24 Err(err) => err.into_compile_error().into(),
25 }
26}
27
28fn impl_proplist(input: &DeriveInput) -> Result<TokenStream> {
29 let ident = &input.ident;
30 match input.data {
31 Data::Struct(DataStruct {
32 struct_token: _,
33 ref fields,
34 semi_token: _,
35 }) => {
36 let insert_props = impl_insert_props(false, fields)?;
37 Ok(quote! {
38 impl cmdstruct::Arg for #ident {
39
40 fn append_arg(&self, command: &mut std::process::Command) {
41 #insert_props
42 command.arg(&format!("{props}"));
43 }
44
45 }
46 }
47 .into())
48 }
49 _ => Err(syn::Error::new(input.span(), "Only structs are supported.")),
50 }
51}
52
53fn field_identifiers(fields: &Fields) -> Result<Vec<Ident>> {
54 match &fields {
55 Fields::Named(FieldsNamed {
56 brace_token: _,
57 ref named,
58 }) => Ok(named
59 .clone()
60 .iter()
61 .filter_map(|field| field.ident.clone())
62 .collect()),
63 Fields::Unit => Ok(Vec::new()),
64 Fields::Unnamed(_) => Err(syn::Error::new(
65 fields.span(),
66 "Unnamed fields are not supported.",
67 )),
68 }
69}
70
71fn backend_name_matcher(tuple: (&Ident, &Variant)) -> Result<proc_macro2::TokenStream> {
72 let ident = &tuple.1.ident;
73 let enum_ident = &tuple.0;
74 let name = format!("{ident}").to_lowercase();
75 let fields = field_identifiers(&tuple.1.fields)?;
76 let enum_fields = if fields.is_empty() {
77 quote! {}
78 } else {
79 quote! { { #(#fields: _, )* } }
80 };
81 Ok(quote! {
82 #enum_ident::#ident #enum_fields => #name,
83 })
84}
85
86fn backend_properties_matcher(tuple: (&Ident, &Variant)) -> Result<proc_macro2::TokenStream> {
87 let ident = &tuple.1.ident;
88 let insert_props = impl_insert_props(true, &tuple.1.fields)?;
89 let enum_ident = &tuple.0;
90 let fields = field_identifiers(&tuple.1.fields)?;
91 let enum_fields = if fields.is_empty() {
92 quote! {}
93 } else {
94 quote! { { #(#fields, )* } }
95 };
96 let matcher = quote! {
97 #enum_ident::#ident #enum_fields => {
98 #insert_props
99 props
100 }
101 };
102 Ok(matcher)
103}
104
105fn impl_backends(input: &DeriveInput) -> Result<TokenStream> {
106 let ident = &input.ident;
107 match input.data {
108 Data::Enum(DataEnum {
109 enum_token: _,
110 brace_token: _,
111 ref variants,
112 }) => {
113 let name_matches: Vec<_> = variants
114 .iter()
115 .map(|variant| (ident, variant))
116 .map(backend_name_matcher)
117 .collect::<Result<Vec<_>>>()?;
118 let properties_matches: Vec<proc_macro2::TokenStream> = variants
119 .iter()
120 .map(|variant| (ident, variant))
121 .map(backend_properties_matcher)
122 .collect::<Result<Vec<_>>>()?;
123 Ok(quote! {
124 impl crate::qemu::args::Backend for #ident {
125
126 fn name(&self) -> &str {
127 match self {
128 #(#name_matches)*
129 }
130 }
131
132 fn properties<'backend>(&'backend self)
133 -> crate::qemu::args::PropertyList<'backend> {
134 match self {
135 #(#properties_matches, )*
136 }
137 }
138
139 }
140 }
141 .into())
142 }
143 _ => Err(syn::Error::new(input.span(), "Only structs are supported.")),
144 }
145}
146
147enum SerdeAttribute {
148 Flatten,
149 Rename(LitStr),
150}
151
152fn insert_prop(local: bool, field: &Field) -> Option<proc_macro2::TokenStream> {
153 if let Some(ref ident) = field.ident {
154 let value = if local {
155 quote! { #ident }
156 } else {
157 quote! { &self.#ident }
158 };
159 let tokens = match parse_attributes(&field.attrs) {
160 Some(SerdeAttribute::Flatten) => quote! {
161 for (key, value) in #value {
162 props.insert(key, value);
163 }
164 },
165 Some(SerdeAttribute::Rename(ref rename)) => {
166 let name_str = format!("{}", rename.value());
167 quote! {
168 props.insert(#name_str, #value);
169 }
170 }
171 None => {
172 let name_str = format!("{ident}");
173 quote! {
174 props.insert(#name_str, #value);
175 }
176 }
177 };
178 Some(tokens)
179 } else {
180 None
181 }
182}
183
184fn impl_insert_props(local: bool, fields: &Fields) -> Result<proc_macro2::TokenStream> {
185 match fields {
186 Fields::Named(FieldsNamed {
187 brace_token: _,
188 ref named,
189 }) => {
190 let insert_props: Vec<proc_macro2::TokenStream> = named
191 .iter()
192 .filter_map(|field| insert_prop(local, field))
193 .collect();
194 Ok(quote! {
195 let mut props = crate::qemu::args::PropertyList::default();
196 #(#insert_props)*
197 })
198 }
199 Fields::Unit => Ok(quote! {
200 let props = crate::qemu::args::PropertyList::default();
201 }),
202 _ => Err(syn::Error::new(
203 fields.span(),
204 "Unnamed fields are not supported",
205 )),
206 }
207}
208
209fn parse_attributes(attrs: &[Attribute]) -> Option<SerdeAttribute> {
211 let mut flatten = false;
212 let mut rename = None;
213 for attr in attrs {
214 match attr.style {
215 AttrStyle::Outer => match &attr.meta {
216 syn::Meta::List(list) if list.path.is_ident("serde") => {
217 let _ = attr.parse_nested_meta(|meta| {
218 if meta.path.is_ident("flatten") {
219 flatten = true;
220 } else if meta.path.is_ident("rename") {
221 let value = meta.value()?;
222 let s: LitStr = value.parse()?;
223 rename = Some(LitStr::new(&s.value(), attr.span()));
224 }
225 Ok(())
226 });
227 }
228 _ => {}
229 },
230 _ => {}
231 };
232 }
233 if flatten {
234 Some(SerdeAttribute::Flatten)
235 } else if let Some(rename) = rename {
236 Some(SerdeAttribute::Rename(rename))
237 } else {
238 None
239 }
240}