Mercurial > crates > nonstick
view libpam-sys/libpam-sys-test/build.rs @ 127:c77846f3a979
GET CTEST WORKING.
This will verify that the functions we're exporting are correct.
It has been a nightmare.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Mon, 30 Jun 2025 22:56:26 -0400 |
parents | 2b255c92417b |
children |
line wrap: on
line source
use bindgen::MacroTypeVariation; use libpam_sys_impls::__pam_impl_enum__; use proc_macro2::{Group, TokenStream, TokenTree}; use quote::{format_ident, ToTokens}; use std::path::Path; use std::process::Command; use std::str::FromStr; use std::{env, fs}; use syn::{Item, ItemConst, Type, TypePath}; // We're using the macro directly so we can match exhaustively. __pam_impl_enum__!(); fn main() { let config = match PamImpl::CURRENT { PamImpl::LinuxPam => TestConfig { headers: vec![ "<security/_pam_types.h>", "<security/pam_appl.h>", "<security/pam_ext.h>", "<security/pam_modules.h>", ], ignore_consts: vec![ "__LINUX_PAM__", "__LINUX_PAM_MINOR__", "PAM_AUTHTOK_RECOVER_ERR", ], ..Default::default() }, PamImpl::OpenPam => TestConfig { headers: vec![ "<security/pam_types.h>", "<security/openpam.h>", "<security/pam_appl.h>", "<security/pam_constants.h>", ], ignore_consts: vec!["OPENPAM_VERSION", "OPENPAM_RELEASE", "PAM_SOEXT"], ..Default::default() }, PamImpl::Sun => TestConfig { headers: vec!["<security/pam_appl.h>", "<security/pam_modules.h>"], block_headers: vec!["sys/.*"], ..Default::default() }, PamImpl::XSso => TestConfig { headers: vec!["\"xsso_pam_appl.h\""], ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"], ..Default::default() }, }; generate_const_test(&config); generate_ctest(&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![ "use libpam_sys::*;".into(), "#[allow(deprecated, overflowing_literals)]".into(), "fn main() {".into(), format!( "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});", PamImpl::CURRENT ), ]; 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}, {name});", tokens = item.expr.to_token_stream(), name = item.ident ) }), ); tests.push("}".into()); let const_test = test_file("constant_test.rs"); fs::write(&const_test, tests.join("\n")).unwrap(); rustfmt(&const_test); } fn generate_ctest(config: &TestConfig) { let mut test = ctest::TestGenerator::new(); for header in config.headers.iter() { if header.starts_with('"') { test.include(env::var("CARGO_MANIFEST_DIR").unwrap()); } test.header(&header[1..header.len() - 1]); } // These are opaque structs. test.skip_struct(|name| matches!(name, "pam_handle" | "AppData")); test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback")); test.type_name(|name, _is_struct, is_union| { assert!(!is_union); // we scabbin' match name { "pam_handle" => "struct pam_handle", "pam_conv" => "struct pam_conv", "pam_message" => "struct pam_message", "pam_response" => "struct pam_response", "AppData" => "void", other => other, } .into() }); // // Welcome to THE HACK ZONE. // // Define away constness because the various PAM implementations // have different const annotations and this will surely drive you crazy. test.define("const", Some("")); // Also replace all the `const`s with `mut`s in the ffi.rs file. let file_contents = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/ffi.rs")); let deconsted_file = test_file("ffi.rs"); remove_consts(file_contents, &deconsted_file); test.generate(&deconsted_file, "ctest.rs"); } fn remove_consts(file_contents: &str, out_file: impl AsRef<Path>) { let deconstified = deconstify( TokenStream::from_str(file_contents).unwrap(), &TokenStream::from_str("mut") .unwrap() .into_iter() .next() .unwrap(), ) .to_string(); let out_file = out_file.as_ref(); fs::write(out_file, deconstified).unwrap(); rustfmt(out_file) } fn rustfmt(file: impl AsRef<Path>) { let status = Command::new(env!("CARGO")) .args(["fmt", "--", file.as_ref().to_str().unwrap()]) .status() .unwrap(); assert!(status.success(), "rustfmt exited with code {status}"); } fn deconstify(stream: TokenStream, mut_token: &TokenTree) -> TokenStream { TokenStream::from_iter(stream.into_iter().map(|token| { match token { TokenTree::Group(g) => { TokenTree::Group(Group::new(g.delimiter(), deconstify(g.stream(), mut_token))) .into_token_stream() } TokenTree::Ident(id) if id == "const" => mut_token.into_token_stream(), other => other.into_token_stream(), } })) } fn test_file(name: impl AsRef<str>) -> String { format!("{}/{}", env::var("OUT_DIR").unwrap(), name.as_ref()) } #[derive(Default)] struct TestConfig { headers: Vec<&'static str>, block_headers: Vec<&'static str>, ignore_consts: Vec<&'static str>, } 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().as_ref()) } }