freebsd-kpi-r14-0 0.2.0

FreeBSD 14.0-RELEASE Kernel Programming Interface
extern crate toml;

use std::{path::{Path, PathBuf}, env, fs::{OpenOptions, File}, io::{BufReader, BufRead}, collections::HashSet};
use std::io::Read;
use toml::Value;

macro_rules! msg 
{
    ($($tokens: tt)*) => {
        println!("cargo:warning={}", format!($($tokens)*))
    }
}

enum RetType
{
    Feature(String),
    Environment(String),
    None
}

pub struct PlatformConfParser<'a>
{
    path: &'a Path,
    conf_file: BufReader<File>,
    line: u32,
}

impl<'a> PlatformConfParser<'a>
{
    fn read_conf(path: &'a Path) -> Result<(Vec<String>, Vec<String>), String>
    {
        let conf_file = 
            OpenOptions::new()
                .append(false)
                .create(false)
                .read(true)
                .write(false)
                .truncate(false)
                .open(path)
                .map_err(|e| e.to_string())?;

        let mut pcp = Self{ path: path, conf_file: BufReader::new(conf_file), line: 0 };

        return pcp.parse_conf();
    }

    fn read_string(buf: String) -> Result<RetType, String>
    {
        let mut p = buf.chars().peekable();

        let mut left: String = String::with_capacity(15);

        while let Some(c) = p.next()
        {
            if c.is_ascii_control() == true
            {
                break;
            }
            else if c.is_ascii_alphanumeric() == true 
            {
                left.push(c);
            }
        }

        if left.as_str() == "makeoptions" || left.as_str() == "cpu" || left.as_str() == "ident"
        {
            // skip
            return Ok(RetType::None);
        }
        // skip control

        while let Some(c) = p.peek()
        {
            if c.is_ascii_control() == true 
            {
                p.next();
            }
            else if c.is_ascii_alphanumeric() == true || c.is_ascii_graphic() == true
            {
                break;
            }
        }

        let mut right: String = String::with_capacity(15);

        while let Some(c) = p.next()
        {
            if c.is_ascii_control() == true || c == '#'
            {
                break;
            }
            else if c.is_ascii_alphanumeric() == true || c.is_ascii_graphic() == true
            {
                right.push(c);
            }
        }

        if right.is_empty() == true
        {
            return Err(format!("empty right value, left: {}", left));
        }

        if right.contains("=") == true
        {
            return Ok(RetType::Environment(right));
        }
        else
        {
            return Ok(RetType::Feature(right));
        }

    }

    fn parse_conf(&mut self) -> Result<(Vec<String>, Vec<String>), String>
    {   
        let mut features: Vec<String> = Vec::new();
        let mut envi: Vec<String> = Vec::new();

        loop
        {
            let mut buf = String::new();

            let n = 
                self.conf_file
                    .read_line(&mut buf)
                    .map_err(|e| e.to_string())?;

            self.line += 1;

            // reached EOF
            if n == 0
            {
                break;
            }
        
            // skip comments
            if buf.starts_with("#") == true
            {
                continue;
            }
            else if buf.starts_with("\n") == true
            {
                continue;
            }

            match Self::read_string(buf)
            {
                Ok(RetType::Feature(r)) => 
                {
                    features.push(r);
                },
                Ok(RetType::Environment(r)) => 
                {
                    envi.push(r);
                },
                Ok(RetType::None) => {},
                Err(e) =>
                {
                    msg!("path: '{}', line: '{}', error: {}", self.path.display(), self.line, e);
                }
            }
        }

        return Ok((features, envi));
    }
}



//#[path = "src/building.rs"]
//mod building;
/*
mod building 
{
     include!("src/building.rs");
}
*/


fn main() 
{

    // don't forget to change same fields in src/machine/{arch}/param.rs
    // bullsh@t called "conditional compilation" in Rust s@cks a lot.
    #[cfg(target_pointer_width = "64")]
    println!("cargo:rustc-cfg=feature=\"__LP64\"");

    

    pub const PAGE_SHIFT: u32 = 12;
    pub const PAGE_SIZE: u32 = 1 << PAGE_SHIFT;

    println!("cargo:rustc-cfg=feature=\"PG_{}\"", PAGE_SIZE);

    // see param.h: #if PAGE_SIZE <= 8192
    if PAGE_SIZE <= 8192
    {
        println!("cargo:rustc-cfg=feature=\"PG_LESSEQ8192\"");
    }

    #[cfg(
        any(
            all(feature = "KLD_MODULE", not(feature = "KLD_TIED")),
            feature = "WITNESS", 
            feature = "INVARIANTS", 
            feature = "LOCK_PROFILING",
            feature = "KTR"
        )
    )]
    println!("cargo:rustc-cfg=feature=\"LOCK_DEBUG\"");

    let read_cfg = env::var_os("CARGO_FEATURE_BUILD_CONFIG_KERNEL");
    if read_cfg.is_some() 
    {
        let mut cargo_cur_dir = env::current_dir().unwrap();

        cargo_cur_dir.push("Cargo.toml");

        let mut carg_file = OpenOptions::new().read(true).open(cargo_cur_dir.as_path()).unwrap();

        let mut cargo_file_txt = String::new();

        carg_file.read_to_string(&mut cargo_file_txt).unwrap();

        let t = cargo_file_txt.parse::<Value>().unwrap();

        let cargo_features: HashSet<String>;
        match t
        {
            Value::Table(t) =>
            {
                let features = t.get("features").unwrap();

                match features
                {
                    Value::Table(arr) => 
                    {
                        cargo_features = arr.keys().map(|s| s.clone()).collect();
                    },
                    _ => 
                    {
                        panic!("expexted table!")
                    }
                }
            },
            _ => 
            {
                panic!("exp tbl");
            }
        }

        let cfg_path = 
            env::var("KPI_PLATFORM_CFG_PATH")
                .map_or(PathBuf::from("/usr/src/sys/amd64/conf/GENERIC"), |d| PathBuf::from(d));

        msg!("CONF: {}", cfg_path.display());

        let (mut features, envi) = 
            match PlatformConfParser::read_conf(cfg_path.as_path())
            {
                Ok(r) => r,
                Err(e) =>
                {
                    panic!("Failed parsing: {}, error: {}", cfg_path.display(), e);
                }
            };

        features.retain(|f| 
            {
                return cargo_features.contains(f);
            }
        );

        msg!("Building rust-kpi crate with the following features: \"{}\"", features.as_slice().join("\", \""));
        msg!("Building rust-kpi crate with the following environment: {}", envi.as_slice().join(" "));

        println!("cargo:rustc-cfg=feature=\"KERNEL\"");

        for f in features
        {
            println!("cargo:rustc-cfg=feature=\"{}\"", f);
        }

        for e in envi
        {
            println!("cargo:rustc-env={}", e);
        }
    }
    else
    {
        let mut features: Vec<String> = Vec::with_capacity(env::vars().count());

        for (name, _value) in env::vars() 
        {
            if name.starts_with("CARGO_FEATURE_") 
            {
                features.push(name.clone());
            }
        }

        msg!("Building with manually provided crate flags: {}", features.join(" "));
    } 

    //println!("cargo:rustc-cfg=feature=\"test_enable\"");
}