Mercurial > crates > nonstick
view libpam-sys/libpam-sys-test/build.rs @ 171:e27c5c667a5a
Create full new types for return code and flags, separate end to end.
This plumbs the ReturnCode and RawFlags types through the places where
we call into or are called from PAM.
Also adds Sun documentation to the project.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 25 Jul 2025 20:52:14 -0400 |
parents | 3a7cf05d2b5f |
children | 0730f5f2ee2a |
line wrap: on
line source
use bindgen::MacroTypeVariation; use libpam_sys_consts::pam_impl::PamImpl; use libpam_sys_consts::{pam_impl, pam_impl_name}; use proc_macro2::{Group, Ident, 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}; const REDIR_FD: &str = "pam_modutil_redirect_fd"; fn main() { pam_impl::enable_pam_impl_cfg(); 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>", "<security/pam_modutil.h>", ], allow_types: vec![REDIR_FD], ignore_consts: vec![ "__LINUX_PAM__", "__LINUX_PAM_MINOR__", "PAM_AUTHTOK_RECOVER_ERR", ], }, 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>", "\"illumos_pam_impl.h\"", ], ..Default::default() }, PamImpl::XSso => TestConfig { headers: vec!["\"xsso_pam_appl.h\""], ..Default::default() }, other => panic!("PAM implementation {other:?} is not yet tested"), }; 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())) .allowlist_var("(OPEN)?PAM_.*") .default_macro_constant_type(MacroTypeVariation::Signed); for &typ in config.allow_types.iter() { builder = builder.allowlist_type(typ); } let generated = builder.generate().unwrap(); generated.write_to_file(test_file("bindgen.rs")).unwrap(); let file = syn::parse_file(&generated.to_string()).unwrap(); let mut tests = vec![ "#[allow(deprecated, overflowing_literals)]".into(), "fn main() {".into(), format!( "assert_eq!(libpam_sys::pam_impl::PamImpl::CURRENT, libpam_sys::pam_impl::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)) .map(|item| { let name = item.ident.to_string(); if let Some(stripped) = name.strip_prefix(&format!("{REDIR_FD}_")) { format!("\ assert_eq!(generated::{name} as i32, libpam_sys::{REDIR_FD}::{stripped}.into());\ assert_eq!((generated::{name} as i32).try_into(), Ok(libpam_sys::{REDIR_FD}::{stripped}));\ ") } else { format!("assert_eq!(generated::{name} as i32, libpam_sys::{name});") } }), ); 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(); test.cfg("pam_impl", Some(pam_impl_name!())); 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, is_struct) { ("AppData", _) => "void".into(), (REDIR_FD, _) => format!("enum {REDIR_FD}"), ("passwd", _) => "struct passwd".into(), ("group", _) => "struct group".into(), ("spwd", _) => "struct spwd".into(), (name, true) => format!("struct {name}"), (other, false) => other.into(), } }); test.field_name(|_, name| { match name { "type_" => "type", 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!("../src/lib.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 lines: Vec<_> = file_contents .lines() .filter(|&l| !l.starts_with("pub mod")) .collect(); let file_contents = lines.join("\n"); let deconstified = deconstify( TokenStream::from_str(&file_contents).unwrap(), &format_ident!("mut"), ) .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: &Ident) -> 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() } // Remove all 'consts' from the file and replace them with 'mut'. 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>, allow_types: 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()) } }