view libpam-sys/libpam-sys-test/build.rs @ 121:397743cb70e2

Make libpam-sys-tests work on Illumos!
author Paul Fisher <paul@pfish.zone>
date Mon, 30 Jun 2025 03:43:48 -0400
parents 39760dfc9b3b
children 9e05e44050d0
line wrap: on
line source

use bindgen::MacroTypeVariation;
use libpam_sys::PamImpl;
use quote::{format_ident, ToTokens};
use std::path::PathBuf;
use std::{env, fs};
use syn::{Item, ItemConst, Type, TypePath};

fn main() {
    let config = match PamImpl::CURRENT {
        PamImpl::LinuxPam => TestConfig {
            headers: vec![
                "security/_pam_types.h".into(),
                "security/pam_appl.h".into(),
                "security/pam_ext.h".into(),
                "security/pam_modules.h".into(),
            ],
            ignore_consts: vec![
                "__LINUX_PAM__".into(),
                "__LINUX_PAM_MINOR__".into(),
                "PAM_AUTHTOK_RECOVER_ERR".into(),
            ],
            ..Default::default()
        },
        PamImpl::OpenPam => TestConfig {
            headers: vec![
                "security/pam_types.h".into(),
                "security/openpam.h".into(),
                "security/pam_appl.h".into(),
                "security/pam_constants.h".into(),
            ],
            ignore_consts: vec![
                "OPENPAM_VERSION".into(),
                "OPENPAM_RELEASE".into(),
                "PAM_SOEXT".into(),
            ],
            ..Default::default()
        },
        PamImpl::Sun => TestConfig {
            headers: vec![
                "security/pam_appl.h".into(),
                "security/pam_modules.h".into(),
            ],
            block_headers: vec!["sys/types.h".into()],
            ..Default::default()
        },
        PamImpl::XSso => Default::default(),
        other => panic!("Unknown PAM implementation {other:?}"),
    };
    generate_const_test(&config);
}

fn generate_const_test(config: &TestConfig) {
    let mut builder = bindgen::Builder::default()
        .header_contents("_.h", &config.header_contents())
        .merge_extern_blocks(true)
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .blocklist_type(".*")
        .blocklist_function(".*")
        .allowlist_var(".*")
        .default_macro_constant_type(MacroTypeVariation::Signed);
    for hdr in config.block_headers.iter() {
        builder = builder.blocklist_file(".*?/".to_owned() + hdr)
    }

    let generated = builder.generate().unwrap().to_string();
    let file = syn::parse_file(&generated).unwrap();
    let mut tests = vec![];
    tests.push("{".into());
    tests.extend(
        file.items
            .iter()
            .filter_map(|item| {
                if let Item::Const(item) = item {
                    Some(item)
                } else {
                    None
                }
            })
            .filter(|item| config.should_check_const(item))
            .cloned()
            .map(|mut item| {
                item.ty = Box::new(Type::Path(TypePath {
                    qself: None,
                    path: format_ident!("i32").into(),
                }));
                format!(
                    "assert_eq!({tokens}, libpam_sys::{name});",
                    tokens = item.expr.to_token_stream(),
                    name = item.ident
                )
            }),
    );
    tests.push("}".into());
    fs::write(
        PathBuf::from(env::var("OUT_DIR").unwrap()).join("constant_test.rs"),
        tests.join("\n"),
    )
    .unwrap();
}

#[derive(Default)]
struct TestConfig {
    headers: Vec<String>,
    block_headers: Vec<String>,
    ignore_consts: Vec<String>,
}

impl TestConfig {
    fn header_contents(&self) -> String {
        let vec: Vec<_> = self
            .headers
            .iter()
            .map(|h| format!("#include <{h}>\n"))
            .collect();
        vec.join("")
    }

    fn should_check_const(&self, item: &ItemConst) -> bool {
        !self.ignore_consts.contains(&item.ident.to_string())
    }
}