Mercurial > crates > nonstick
view libpam-sys/libpam-sys-test/build.rs @ 136:efbc235f01d3 default tip
Separate libpam-sys-helpers from libpam-sys.
This separates the parts of libpam-sys that don't need linking against libpam
from the parts that do need to link against libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 03 Jul 2025 14:28:04 -0400 |
parents | 6c1e1bdb4164 |
children |
line wrap: on
line source
use bindgen::MacroTypeVariation; use libpam_sys_helpers::pam_impl::PamImpl; use libpam_sys_helpers::{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", ], ..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>"], ..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(dead_code, non_camel_case_types, non_upper_case_globals)] mod generated { include!(\"bindgen.rs\"); } #[allow(deprecated, overflowing_literals)] 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());") } 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(), } }); // // 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 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() } 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()) } }