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