use build_cfg_shared::CfgKey;
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, Lit, LitStr, Meta, MetaNameValue, NestedMeta, PathArguments, PathSegment, parse_macro_input, punctuated::Punctuated, spanned::Spanned};
#[proc_macro_attribute]
pub fn build_cfg_main(_: TokenStream, main_fn: TokenStream) -> TokenStream {
let mut main_fn = parse_macro_input!(main_fn as ItemFn);
assert!(main_fn.sig.ident.to_string() == "main", "This is not the main function of the build script");
main_fn.block.stmts.insert(0, syn::parse_str("::build_cfg::__private::populate_cfg(std::env::vars_os());").unwrap());
quote!(#main_fn).into()
}
#[proc_macro]
pub fn build_cfg(cfg: TokenStream) -> TokenStream {
let cfg = parse_macro_input!(cfg as Meta);
fn parse_cfg(cfg: Meta, mut output: String) -> String {
use std::fmt::Write;
match cfg {
Meta::Path(ref path) => {
match path.get_ident() {
Some(ident) => {
let target_family = ident.to_string();
if !matches!(target_family.as_str(), "windows" | "unix" | "wasm") {
panic!("Unknown cfg directive: `{}`", target_family);
};
output = parse_cfg(Meta::NameValue(MetaNameValue {
lit: Lit::Str(LitStr::new(target_family.as_str(), cfg.span())),
path: syn::Path {
leading_colon: None,
segments: {
let mut segments = Punctuated::new();
segments.push_value(PathSegment {
ident: proc_macro2::Ident::new("target_family", cfg.span()),
arguments: PathArguments::None
});
segments
}
},
eq_token: Default::default(),
}), output);
},
None => panic!("`cfg` predicate key must be an identifier")
}
},
Meta::List(list) => {
assert!(list.path.segments.len() == 1, "Syntax error");
let predicate = list.path.segments.first().expect("Missing predicate").ident.to_string();
match predicate.as_str() {
predicate @ ("any" | "all") => {
match predicate {
"any" => output.push_str("::build_cfg::__private::CfgPredicate::Any(vec!["),
"all" => output.push_str("::build_cfg::__private::CfgPredicate::All(vec!["),
_ => unreachable!()
}
for meta in list.nested {
let meta = match meta {
NestedMeta::Meta(meta) => meta,
NestedMeta::Lit(_) => panic!("unsupported literal"),
};
output = parse_cfg(meta, output);
output.push(',');
}
output.pop();
output.push_str("])");
},
"not" => {
output.push_str("::build_cfg::__private::CfgPredicate::Not(Box::new(");
for meta in list.nested {
match meta {
NestedMeta::Meta(meta) => output = parse_cfg(meta, output),
NestedMeta::Lit(_) => panic!("unsupported literal"),
}
}
output.push_str("))");
},
_ => panic!("Unknown predicate (`{}`)", predicate)
}
},
Meta::NameValue(nv) => {
let key = nv.path.segments[0].ident.to_string();
let key = match CfgKey::from_str(&key) {
Some(key) => key,
None => panic!("Unknown cfg directive: `{}`", key)
};
let mut val = match nv.lit {
syn::Lit::Str(str) => str.value(),
_ => panic!("literal in `cfg` predicate value must be a string")
};
if key == CfgKey::Feature {
val = val.replace("-", "_");
}
write!(output, "::build_cfg::__private::CfgPredicate::Directive(::build_cfg::__private::CfgDirective(::build_cfg::__private::CfgKey::{:?},{:?}))", key, val).ok();
},
}
output
}
let mut output = parse_cfg(cfg, String::new());
output.push_str(".test()");
output.parse().unwrap()
}