Mercurial > crates > nonstick
view libpam-sys/libpam-sys-test/build.rs @ 141:a508a69c068a
Remove a lot of Results from functions.
Many functions are documented to only return failing Results when given
improper inputs or when there is a memory allocation failure (which
can be verified by looking at the source). In cases where we know our
input is correct, we don't need to check for memory allocation errors
for the same reason that Rust doesn't do so when you, e.g., create a
new Vec.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 05 Jul 2025 17:16:56 -0400 |
parents | 33b9622ed6d2 |
children | 4b3a5095f68c |
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", ], }, 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 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()) } }