1use crate::syn::expand_cfg_if;
2use crate::syn::find_cfg_items;
3use crate::syn::find_cfg_mod;
4use crate::transform::simplified_expr;
5
6use codegen_cfg::ast::all as cfg_all;
7use codegen_cfg::ast::any as cfg_any;
8use codegen_cfg::ast::Expr as CfgExpr;
9
10use std::collections::HashMap;
11use std::fs;
12use std::ops::Not as _;
13
14use syn::File;
15use syn::Ident;
16
17use anyhow::Result;
18use camino::{Utf8Path, Utf8PathBuf};
19use log::debug;
20use regex::RegexSet;
21use rust_utils::default::default;
22use rust_utils::iter::map_collect_vec;
23
24pub struct CfgItem {
25 pub cfg: CfgExpr,
26 pub name: String,
27}
28
29pub fn search(libc: impl AsRef<Utf8Path>, re: &RegexSet) -> Result<Vec<CfgItem>> {
30 let items = search_items(libc.as_ref(), re)?;
31
32 Ok(map_collect_vec(items, |(cfg, name)| CfgItem {
33 cfg,
34 name: name.to_string(),
35 }))
36}
37
38fn search_items(libc: &Utf8Path, re: &RegexSet) -> Result<Vec<(CfgExpr, Ident)>> {
39 let mut ctx = DfsContext { re, items: default() };
40
41 {
42 let dir = libc.join("src");
43 let fs_path = libc.join("src/lib.rs");
44
45 let ast = parse_file(&fs_path)?;
46 for (cfg, name) in find_mod_in_file(&ast) {
47 let fs_path = resolve_fs_path(&dir, &name);
48 dfs(&mut ctx, &fs_path, &name, &cfg)?;
49 }
50 }
51
52 debug!("aggregating items");
53 Ok(aggregate_items(ctx.items))
54}
55
56fn parse_file(fs_path: &Utf8Path) -> Result<File> {
57 let s = fs::read_to_string(fs_path)?;
58 let mut ast = syn::parse_file(&s)?;
59 expand_cfg_if(&mut ast);
60 Ok(ast)
61}
62
63fn find_mod_in_file(ast: &File) -> Vec<(CfgExpr, Ident)> {
64 let mut map: HashMap<Ident, Vec<CfgExpr>> = default();
65
66 for (cfg, name) in find_cfg_mod(ast) {
67 map.entry(name).or_default().push(cfg);
68 }
69
70 aggregate_items(map)
71}
72
73fn aggregate_items(map: HashMap<Ident, Vec<CfgExpr>>) -> Vec<(CfgExpr, Ident)> {
74 let mut ans = map_collect_vec(map, |(name, exprs)| (simplified_expr(cfg_any(exprs)), name));
75 ans.sort_by(|lhs, rhs| lhs.1.cmp(&rhs.1));
76 ans
77}
78
79fn resolve_fs_path(dir: &Utf8Path, mod_name: &Ident) -> Utf8PathBuf {
80 let first = dir.join(format!("{}.rs", mod_name));
81 let second = dir.join(format!("{}/mod.rs", mod_name));
82
83 if first.exists() {
84 assert!(second.exists().not());
85 first
86 } else {
87 second
88 }
89}
90
91struct DfsContext<'a> {
92 re: &'a RegexSet,
93 items: HashMap<Ident, Vec<CfgExpr>>,
94}
95
96fn dfs(ctx: &mut DfsContext, fs_path: &Utf8Path, mod_name: &Ident, mod_cfg: &CfgExpr) -> Result<()> {
97 debug!("dfs: mod {mod_name:<16} at {fs_path:<80}: {mod_cfg}");
98
99 let dir = fs_path.parent().unwrap();
100
101 let ast = parse_file(fs_path)?;
102
103 for (cfg, name) in find_cfg_items(&ast, ctx.re) {
104 let item_cfg = join_item_cfg(mod_cfg, cfg);
105 ctx.items.entry(name).or_default().push(item_cfg);
106 }
107
108 for (cfg, name) in find_mod_in_file(&ast) {
109 let mod_name = &name;
110 let fs_path = resolve_fs_path(dir, mod_name);
111 let mod_cfg = join_item_cfg(mod_cfg, cfg);
112
113 dfs(ctx, &fs_path, mod_name, &mod_cfg)?;
114 }
115
116 Ok(())
117}
118
119fn join_item_cfg(mod_cfg: &CfgExpr, item_cfg: CfgExpr) -> CfgExpr {
120 simplified_expr(cfg_all((mod_cfg.clone(), item_cfg)))
121}