libc_cfg/
scan.rs

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    // FIXME: special case
98    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}