codegen-libc 0.2.1

A codegen tool for extracting libc cfg
Documentation
use crate::syn::expand_cfg_if;
use crate::syn::find_cfg_items;
use crate::syn::find_cfg_mod;
use crate::transform::simplified_expr;

use codegen_cfg::ast::all as cfg_all;
use codegen_cfg::ast::any as cfg_any;
use codegen_cfg::ast::Expr as CfgExpr;

use std::collections::HashMap;
use std::fs;
use std::ops::Not as _;

use syn::File;
use syn::Ident;

use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use log::debug;
use regex::RegexSet;
use rust_utils::default::default;
use rust_utils::iter::map_collect_vec;

pub struct CfgItem {
    pub cfg: CfgExpr,
    pub name: String,
}

pub fn search(libc: impl AsRef<Utf8Path>, re: &RegexSet) -> Result<Vec<CfgItem>> {
    let items = search_items(libc.as_ref(), re)?;

    Ok(map_collect_vec(items, |(cfg, name)| CfgItem {
        cfg,
        name: name.to_string(),
    }))
}

fn search_items(libc: &Utf8Path, re: &RegexSet) -> Result<Vec<(CfgExpr, Ident)>> {
    let mut ctx = DfsContext { re, items: default() };

    {
        let dir = libc.join("src");
        let fs_path = libc.join("src/lib.rs");

        let ast = parse_file(&fs_path)?;
        for (cfg, name) in find_mod_in_file(&ast) {
            let fs_path = resolve_fs_path(&dir, &name);
            dfs(&mut ctx, &fs_path, &name, &cfg)?;
        }
    }

    debug!("aggregating items");
    Ok(aggregate_items(ctx.items))
}

fn parse_file(fs_path: &Utf8Path) -> Result<File> {
    let s = fs::read_to_string(fs_path)?;
    let mut ast = syn::parse_file(&s)?;
    expand_cfg_if(&mut ast);
    Ok(ast)
}

fn find_mod_in_file(ast: &File) -> Vec<(CfgExpr, Ident)> {
    let mut map: HashMap<Ident, Vec<CfgExpr>> = default();

    for (cfg, name) in find_cfg_mod(ast) {
        map.entry(name).or_default().push(cfg);
    }

    aggregate_items(map)
}

fn aggregate_items(map: HashMap<Ident, Vec<CfgExpr>>) -> Vec<(CfgExpr, Ident)> {
    let mut ans = map_collect_vec(map, |(name, exprs)| (simplified_expr(cfg_any(exprs)), name));
    ans.sort_by(|lhs, rhs| lhs.1.cmp(&rhs.1));
    ans
}

fn resolve_fs_path(dir: &Utf8Path, mod_name: &Ident) -> Utf8PathBuf {
    let first = dir.join(format!("{}.rs", mod_name));
    let second = dir.join(format!("{}/mod.rs", mod_name));

    if first.exists() {
        assert!(second.exists().not());
        first
    } else {
        second
    }
}

struct DfsContext<'a> {
    re: &'a RegexSet,
    items: HashMap<Ident, Vec<CfgExpr>>,
}

fn dfs(ctx: &mut DfsContext, fs_path: &Utf8Path, mod_name: &Ident, mod_cfg: &CfgExpr) -> Result<()> {
    debug!("dfs: mod {mod_name:<16} at {fs_path:<80}: {mod_cfg}");

    let dir = fs_path.parent().unwrap();

    let ast = parse_file(fs_path)?;

    for (cfg, name) in find_cfg_items(&ast, ctx.re) {
        let item_cfg = join_item_cfg(mod_cfg, cfg);
        ctx.items.entry(name).or_default().push(item_cfg);
    }

    for (cfg, name) in find_mod_in_file(&ast) {
        let mod_name = &name;
        let fs_path = resolve_fs_path(dir, mod_name);
        let mod_cfg = join_item_cfg(mod_cfg, cfg);

        dfs(ctx, &fs_path, mod_name, &mod_cfg)?;
    }

    Ok(())
}

fn join_item_cfg(mod_cfg: &CfgExpr, item_cfg: CfgExpr) -> CfgExpr {
    simplified_expr(cfg_all((mod_cfg.clone(), item_cfg)))
}