changeset 190:995aca290452

Restructure the way libpam-sys-impls works to fix cross-compilation. The previous structure of libpam-sys-impls meant that things got confusing (including for me) between what constants were build-time and what constants were run-time. This broke cross-compilation. This simplifies the way that works so that `libpam-sys-impls` has *no* build script itself and is intended mostly as a library to be included in other libraries' build scripts (while also exporting the PamImpl enum).
author Paul Fisher <paul@pfish.zone>
date Sat, 02 Aug 2025 18:47:46 -0400
parents b2456d274576
children e915c54097d6
files libpam-sys/build.rs libpam-sys/libpam-sys-impls/README.md libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/libpam-sys-impls/src/pam_impl.rs libpam-sys/libpam-sys-test/build.rs libpam-sys/src/constants.rs libpam-sys/src/lib.rs src/lib.rs
diffstat 9 files changed, 299 insertions(+), 345 deletions(-) [+]
line wrap: on
line diff
--- a/libpam-sys/build.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/build.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -1,4 +1,26 @@
+use std::{env, fs};
+
 fn main() {
     println!("cargo:rustc-link-lib=pam");
     libpam_sys_impls::enable_pam_impl_cfg();
+
+    let pam_impl = libpam_sys_impls::build_target_impl();
+    let impl_str = pam_impl.map(|i| format!("{i:?}")).unwrap_or("[undefined]".into());
+    println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}");
+    let output = match pam_impl {
+        None => "".into(),
+        Some(pam_impl) => {
+            format!("\
+                /// The implementation of PAM this library was built against.
+                pub const CURRENT: PamImpl = PamImpl::{pam_impl:?};
+                /// The name of the PAM implementation this library was built
+                /// against, as a string.
+                #[macro_export]
+                macro_rules! pam_impl_name {{ () => {{ \"{pam_impl:?}\" }} }}
+                pub(crate) use pam_impl_name;
+            ")
+        }
+    };
+    let outfile = format!("{out}/pam_impl_consts.rs", out = env::var("OUT_DIR").expect("missing OUT_DIR env var"));
+    fs::write(outfile, output).expect("couldn't write output file");
 }
--- a/libpam-sys/libpam-sys-impls/README.md	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/README.md	Sat Aug 02 18:47:46 2025 -0400
@@ -1,66 +1,12 @@
 # `libpam-sys-impls`: LibPAM library detection
 
 This crate detects what implementation of LibPAM should be used, as part of the build script, and exports that information to downstream crates.
+It can also be used at runtime, but is primarily intended for build scripts.
 
-This is mostly a backend crate for [libpam-sys](https://crates.io/crates/libpam-sys/).
+Its main use is as a backend for [libpam-sys](https://crates.io/crates/libpam-sys/).
 That crate re-exports pretty much everything we provide.
 In most cases, you can just use that instead of depending upon this directly.
 
-## Usage
-
-Different PAM implementations have different constants and some different behaviors.
-If you need to change your library's behavior based on PAM implementation, there are a few ways to do so.
-
-### Constants
-
-You can match on the current PAM implementation at runtime.
-All known PAM implementations are in the `PamImpl` enumeration, and `PamImpl::CURRENT` is set to the current implementation.
-This is present as a string literal macro in `pam_impl_name!`.
-
-### Conditional compilation
-
-This package provides custom `#[cfg]`s to compile based on the current PAM implementation.
-
-First, **enable custom `#[cfg]`s in your build.rs**:
-
-```rust
-// build.rs
-use libpam_sys_impls::pam_impl;
-
-fn main() {
-    pam_impl::enable_pam_impl_cfg();
-    
-    // everything else you do at build time
-}
-```
-
-This will then allow you to use the `pam_impl` configuration variable at compile time:
-
-```rust
-#[cfg(pam_impl = "LinuxPam")]
-fn handle_pam() {
-    // do things in a Linux-PAM specific way
-}
-
-#[cfg(not(pam_impl = "LinuxPam"))]
-fn handle_pam() {
-    // do things in another, more different way
-}
-```
-
-## Configuration
-
-Known implementations of PAM are listed in the `PamImpl` enum, and your currently installed implementation is automatically detected.
-
-If you need to configure this, you can override it **at build time** with the `LIBPAMSYS_IMPL` environment variable:
-
-- Unset or empty (the default): Use the version of PAM most commonly found on the target OS.
-  If we don't know what kind of PAM is usually installed on this OS, we fall back to `__installed__`.
-- `__installed__`: Looks at the PAM library installed on the current machine.
-  If none is recognized, falls back to `XSso`.
-- The name of a `PamImpl` entry: The named PAM implementation.
-  For instance, `LIBPAMSYS_IMPL=OpenPam cargo build` will build this library for OpenPAM.
-
 ## MSRV
 
 This library supports **Rust 1.75**, as the version currently (July 2025) available in Debian Trixie and Ubuntu 24.04 LTS.
\ No newline at end of file
--- a/libpam-sys/libpam-sys-impls/build.rs	Thu Jul 31 15:42:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-#![allow(unexpected_cfgs)]
-
-use std::ffi::{c_void, CString};
-use std::ptr::NonNull;
-use std::{env, fs};
-
-include!("src/pam_impl.rs");
-
-/// The strategy to use to detect PAM.
-enum Detect {
-    /// Use the default PAM implementation based on the OS,
-    /// or the currently-installed version if the OS is not recognized.
-    TargetDefault,
-    /// Detect the installed implementation.
-    Installed,
-    /// Use the named version of PAM.
-    Specified(PamImpl),
-}
-
-const INSTALLED: &str = "__installed__";
-
-fn main() {
-    let detection = match option_env!("LIBPAMSYS_IMPL") {
-        Some("") | None => Detect::TargetDefault,
-        Some(INSTALLED) => Detect::Installed,
-        Some(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
-            panic!(
-                "unknown PAM implementation {val:?}. \
-                valid LIBPAMSYS_IMPLs are {:?}, \
-                {INSTALLED:?} to use the currently-installed version, \
-                or unset to use the OS default",
-                PamImpl::items()
-            )
-        })),
-    };
-    let pam_impl = match detection {
-        Detect::TargetDefault => LibPam::target_default(),
-        Detect::Installed => LibPam::probe_detect(),
-        Detect::Specified(other) => other,
-    };
-    let impl_str = format!("{pam_impl:?}");
-    println!("{}", generate_cfg(&impl_str));
-    // We set this environment variable to substitute into docstrings.
-    println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}");
-    fs::write(
-        format!("{}/pam_impl_const.rs", env::var("OUT_DIR").unwrap()),
-        generate_consts(&impl_str),
-    )
-    .unwrap();
-}
-
-fn generate_consts(impl_str: &str) -> String {
-    format!(
-        "\
-impl PamImpl {{
-/// The implementation of libpam this was built for (`{impl_str}`).
-pub const CURRENT: Self = Self::{impl_str};
-}}
-
-/// String name of [`PamImpl::CURRENT`], for substituting into docs.
-#[macro_export]
-macro_rules! pam_impl_name {{ () => ({impl_str:?}) }}
-        "
-    )
-}
-
-struct LibPam(NonNull<c_void>);
-
-impl LibPam {
-    /// Guess the PAM implementation based on the current OS.
-    fn target_default() -> PamImpl {
-        if cfg!(target_os = "linux") {
-            PamImpl::LinuxPam
-        } else if cfg!(any(
-            target_os = "macos",
-            target_os = "freebsd",
-            target_os = "netbsd",
-            target_os = "dragonfly",
-            target_os = "openbsd",
-        )) {
-            PamImpl::OpenPam
-        } else if cfg!(any(target_os = "illumos", target_os = "solaris")) {
-            PamImpl::Sun
-        } else {
-            Self::probe_detect()
-        }
-    }
-
-    /// Look at the currently-installed LibPAM.
-    fn probe_detect() -> PamImpl {
-        if let Some(lib) = Self::open() {
-            if lib.has("pam_syslog") {
-                return PamImpl::LinuxPam;
-            } else if lib.has("_openpam_log") {
-                return PamImpl::OpenPam;
-            } else if lib.has("__pam_get_authtok") {
-                return PamImpl::Sun;
-            }
-        }
-        // idk
-        PamImpl::XSso
-    }
-
-    fn open() -> Option<Self> {
-        let dlopen = |s: &[u8]| unsafe { libc::dlopen(s.as_ptr().cast(), libc::RTLD_LAZY) };
-        NonNull::new(dlopen(b"libpam.so\0"))
-            .or_else(|| NonNull::new(dlopen(b"libpam.dylib\0")))
-            .map(Self)
-    }
-
-    fn has(&self, name: &str) -> bool {
-        let name = CString::new(name).unwrap();
-        let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr()) };
-        !symbol.is_null()
-    }
-}
-
-impl Drop for LibPam {
-    fn drop(&mut self) {
-        unsafe {
-            libc::dlclose(self.0.as_ptr());
-        }
-    }
-}
--- a/libpam-sys/libpam-sys-impls/src/lib.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/src/lib.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -1,53 +1,267 @@
-//! Information about the PAM implementation you're using right now.
+#![allow(clippy::needless_doctest_main)]
+//! An enumeration of PAM implementations and tools to detect them.
 //!
-//! This module contains constants and values that can be used at build-script,
-//! compile, and run time to determine what PAM implementation you're using.
+//! # Configuration
 //!
-//! ## Always available
+//! When used at compile time, this crate uses the target OS by default,
+//! but can be overridden with the `LIBPAMSYS_IMPL` environment variable.
+//! See the documentation of [`build_target_impl`] for details.
 //!
-//! [`PamImpl::CURRENT`] will tell you what version of PAM you're using.
-//! It can be imported in any Rust code, from build scripts to runtime.
+//! # Detecting PAM
 //!
-//! ## Compile time
+//! ## Build time
 //!
 //! Use [`enable_pam_impl_cfg`] in your `build.rs` to generate custom `#[cfg]`s
 //! for conditional compilation based on PAM implementation.
 //!
-//! ```
-//! // Your package's build.rs:
+//! To detect the implementation that will be used at runtime, use the
+//! [`build_target_impl`] function.
 //!
-//! fn main() {
-//!     // Also available at libpam_sys::pam_impl::enable_pam_impl_cfg().
-//!     libpam_sys_impls::enable_pam_impl_cfg();
-//!     // whatever else you do in your build script.
-//! }
-//! ```
-//!
-//! This will set the current `pam_impl` as well as registering all known
-//! PAM implementations with `rustc-check-cfg` to get cfg-checking.
-//!
-//! The names that appear in the `cfg` variables are the same as the values
-//! in the [`PamImpl`] enum.
+//! ## Run time
 //!
-//! ```ignore
-//! #[cfg(pam_impl = "OpenPam")]
-//! fn openpam_specific_func(handle: *const libpam_sys::pam_handle) {
-//!     let environ = libpam_sys::pam_getenvlist(handle);
-//!     // ...
-//!     libpam_sys::openpam_free_envlist()
-//! }
-//!
-//! // This will give you a warning since "UnknownImpl" is not in the cfg.
-//! #[cfg(not(pam_impl = "UnknownImpl"))]
-//! fn do_something() {
-//!     // ...
-//! }
-//! ```
-//!
-//! The [`pam_impl_name!`] macro will expand to this same value, currently
-#![doc = concat!("`", env!("LIBPAMSYS_IMPL"), "`.")]
+//! The implementation of PAM installed on the machine where the code is running
+//! can be detected with [`currently_installed`], or you can use
+//! [`os_default`] to see what implementation is used on a given target.
+
+use std::env;
+use std::env::VarError;
+use std::ffi::c_void;
+use std::ptr::NonNull;
+
+/// An enum that knows its own values.
+macro_rules! self_aware_enum {
+    (
+        $(#[$enumeta:meta])*
+        $viz:vis enum $name:ident {
+            $(
+                $(#[$itemeta:meta])*
+                $item:ident,
+            )*
+        }
+    ) => {
+        $(#[$enumeta])*
+        $viz enum $name {
+            $(
+                $(#[$itemeta])*
+                $item,
+            )*
+        }
+
+        // The implementations in this block are private for now
+        // to avoid putting a contract into the public API.
+        #[allow(dead_code)]
+        impl $name {
+            /// Iterator over the items in the enum. For internal use.
+            pub(crate) fn items() -> Vec<Self> {
+                vec![$(Self::$item),*]
+            }
+
+            /// Attempts to parse the enum from the string. For internal use.
+            pub(crate) fn try_from(value: &str) -> Result<Self, String> {
+                match value {
+                    $(stringify!($item) => Ok(Self::$item),)*
+                    _ => Err(value.into()),
+                }
+            }
+        }
+    };
+}
+
+self_aware_enum! {
+    /// The PAM implementations supported by `libpam-sys`.
+    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+    #[non_exhaustive]
+    pub enum PamImpl {
+        /// [Linux-PAM] is provided by most Linux distributions.
+        ///
+        /// [Linux-PAM]: https://github.com/linux-pam/linux-pam
+        LinuxPam,
+        /// [OpenPAM] is used by most BSDs, including Mac OS X.
+        ///
+        /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM
+        OpenPam,
+        /// Illumos and Solaris use a derivative of [Sun's implementation][sun].
+        ///
+        /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam
+        Sun,
+        /// Only the functionality and constants in [the PAM spec].
+        ///
+        /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm
+        XSso,
+    }
+}
+
+#[allow(clippy::needless_doctest_main)]
+/// Generates `cargo` directives for build scripts to enable `cfg(pam_impl)`.
+///
+/// Print this in your `build.rs` script to be able to use the custom `pam_impl`
+/// configuration directive.
+///
+/// ```
+/// // Your package's build.rs:
+///
+/// fn main() {
+///     // Also available at libpam_sys::pam_impl::enable_pam_impl_cfg().
+///     libpam_sys_impls::enable_pam_impl_cfg();
+///     // whatever else you do in your build script.
+/// }
+/// ```
+///
+/// This will set the current `pam_impl` as well as registering all known
+/// PAM implementations with `rustc-check-cfg` to get cfg-checking.
+///
+/// The names that appear in the `cfg` variables are the same as the values
+/// in the [`PamImpl`] enum.
+///
+/// ```ignore
+/// #[cfg(pam_impl = "OpenPam")]
+/// fn openpam_specific_func(handle: *const libpam_sys::pam_handle) {
+///     let environ = libpam_sys::pam_getenvlist(handle);
+///     // ...
+///     libpam_sys::openpam_free_envlist()
+/// }
+///
+/// // This will give you a warning since "UnknownImpl" is not a known
+/// // PAM implementation.
+/// #[cfg(not(pam_impl = "UnknownImpl"))]
+/// fn do_something() {
+///     // ...
+/// }
+/// ```
+pub fn enable_pam_impl_cfg() {
+    println!("{}", pam_impl_cfg_string())
+}
+
+/// [`enable_pam_impl_cfg`], but returned as a string.
+pub fn pam_impl_cfg_string() -> String {
+    generate_cfg(build_target_impl())
+}
 
-mod pam_impl;
+fn generate_cfg(pam_impl: Option<PamImpl>) -> String {
+    let impls: Vec<_> = PamImpl::items()
+        .into_iter()
+        .map(|i| format!(r#""{i:?}""#))
+        .collect();
+    let mut lines = vec![
+        format!(
+            "cargo:rustc-check-cfg=cfg(pam_impl, values({impls}))",
+            impls = impls.join(",")
+        ),
+        "cargo:rustc-cfg=pam_impl".into(),
+    ];
+    if let Some(pam_impl) = pam_impl {
+        lines.push("cargo:rustc-cfg=pam_impl".into());
+        lines.push(format!("cargo:rustc-cfg=pam_impl=\"{pam_impl:?}\""));
+    }
+    lines.join("\n")
+}
+
+/// The strategy to use to detect PAM.
+enum Detect {
+    /// Use the default PAM implementation based on the target OS.
+    TargetDefault,
+    /// Detect the installed implementation.
+    Installed,
+    /// Use the named version of PAM.
+    Specified(PamImpl),
+}
+
+const INSTALLED: &str = "__installed__";
 
-#[doc(inline)]
-pub use pam_impl::*;
+/// For `build.rs` use: Detects the PAM implementation that should be used
+/// for the target of the currently-running build script.
+///
+/// # Configuration
+///
+/// The PAM implementation selected depends upon the value of the
+/// `LIBPAMSYS_IMPL` environment variable.
+///
+/// - Empty or unset (default): Use the default PAM implementation for the
+///   Cargo target OS (as specified by `CARGO_CFG_TARGET_OS`).
+///   - Linux: Linux-PAM
+///   - BSD (and Mac): OpenPAM
+///   - Illumos/Solaris: Sun PAM
+/// - `__installed__`: Use the PAM implementation installed on the host system.
+///   This opens the `libpam` library and looks for specific functions.
+/// - The name of a [PamImpl] member: Use that PAM implementation.
+///
+/// # Panics
+///
+/// If an unknown PAM implementation is provided in `LIBPAMSYS_IMPL`.
+pub fn build_target_impl() -> Option<PamImpl> {
+    let detection = match env::var("LIBPAMSYS_IMPL").as_deref() {
+        Ok("") | Err(VarError::NotPresent) => Detect::TargetDefault,
+        Ok(INSTALLED) => Detect::Installed,
+        Ok(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
+            panic!(
+                "unknown PAM implementation {val:?}. \
+                valid LIBPAMSYS_IMPL values are {:?}, \
+                {INSTALLED:?} to use the currently-installed version, \
+                or unset to use the OS default",
+                PamImpl::items()
+            )
+        })),
+        Err(other) => panic!("Couldn't detect PAM version: {other}"),
+    };
+    match detection {
+        Detect::TargetDefault => env::var("CARGO_CFG_TARGET_OS")
+            .ok()
+            .as_deref()
+            .and_then(os_default),
+        Detect::Installed => currently_installed(),
+        Detect::Specified(other) => Some(other),
+    }
+}
+
+/// Gets the PAM version based on the target OS.
+///
+/// The target OS name passed in is one of the [Cargo target OS values][os].
+///
+/// [os]: https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.target_os.values
+pub fn os_default(target_os: &str) -> Option<PamImpl> {
+    match target_os {
+        "linux" => Some(PamImpl::LinuxPam),
+        "macos" | "freebsd" | "netbsd" | "dragonfly" | "openbsd" => Some(PamImpl::OpenPam),
+        "illumos" | "solaris" => Some(PamImpl::Sun),
+        _ => None,
+    }
+}
+
+/// The version of LibPAM installed on this machine (as found by `dlopen`).
+pub fn currently_installed() -> Option<PamImpl> {
+    LibPam::open().map(|lib| {
+        if lib.has(b"pam_syslog\0") {
+            PamImpl::LinuxPam
+        } else if lib.has(b"_openpam_log\0") {
+            PamImpl::OpenPam
+        } else if lib.has(b"__pam_get_authtok\0") {
+            PamImpl::Sun
+        } else {
+            PamImpl::XSso
+        }
+    })
+}
+
+struct LibPam(NonNull<c_void>);
+
+impl LibPam {
+    fn open() -> Option<Self> {
+        let dlopen = |s: &[u8]| unsafe { libc::dlopen(s.as_ptr().cast(), libc::RTLD_LAZY) };
+        NonNull::new(dlopen(b"libpam.so\0"))
+            .or_else(|| NonNull::new(dlopen(b"libpam.dylib\0")))
+            .map(Self)
+    }
+
+    fn has(&self, name: &[u8]) -> bool {
+        let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr().cast()) };
+        !symbol.is_null()
+    }
+}
+
+impl Drop for LibPam {
+    fn drop(&mut self) {
+        unsafe {
+            libc::dlclose(self.0.as_ptr());
+        }
+    }
+}
--- a/libpam-sys/libpam-sys-impls/src/pam_impl.rs	Thu Jul 31 15:42:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-// This file is include!d directly by `../build.rs`, so its doc comment
-// is found in lib.rs.
-
-/// An enum that knows its own values.
-macro_rules! self_aware_enum {
-    (
-        $(#[$enumeta:meta])*
-        $viz:vis enum $name:ident {
-            $(
-                $(#[$itemeta:meta])*
-                $item:ident,
-            )*
-        }
-    ) => {
-        $(#[$enumeta])*
-        $viz enum $name {
-            $(
-                $(#[$itemeta])*
-                $item,
-            )*
-        }
-
-        // The implementations in this block are private for now
-        // to avoid putting a contract into the public API.
-        #[allow(dead_code)]
-        impl $name {
-            /// Iterator over the items in the enum. For internal use.
-            fn items() -> Vec<Self> {
-                vec![$(Self::$item),*]
-            }
-
-            /// Attempts to parse the enum from the string. For internal use.
-            fn try_from(value: &str) -> Result<Self, String> {
-                match value {
-                    $(stringify!($item) => Ok(Self::$item),)*
-                    _ => Err(value.into()),
-                }
-            }
-        }
-    };
-}
-
-self_aware_enum! {
-    /// The PAM implementations supported by `libpam-sys`.
-    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
-    #[cfg_attr(pam_impl, non_exhaustive)]
-    pub enum PamImpl {
-        /// [Linux-PAM] is provided by most Linux distributions.
-        ///
-        /// [Linux-PAM]: https://github.com/linux-pam/linux-pam
-        LinuxPam,
-        /// [OpenPAM] is used by most BSDs, including Mac OS X.
-        ///
-        /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM
-        OpenPam,
-        /// Illumos and Solaris use a derivative of [Sun's implementation][sun].
-        ///
-        /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam
-        Sun,
-        /// Only the functionality and constants in [the PAM spec].
-        ///
-        /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm
-        XSso,
-    }
-}
-
-// This generated file contains:
-// - pam_impl_name!
-// - PamImpl::CURRENT
-#[cfg(pam_impl)]
-include!(concat!(env!("OUT_DIR"), "/pam_impl_const.rs"));
-
-#[allow(clippy::needless_doctest_main)]
-/// Generates `cargo` directives for build scripts to enable `cfg(pam_impl)`.
-///
-/// Print this in your `build.rs` script to be able to use the custom `pam_impl`
-/// configuration directive.
-///
-/// ```
-/// // build.rs
-/// fn main() {
-///     libpam_sys_impls::enable_pam_impl_cfg();
-///
-///     // Whatever other stuff you do in your build.rs.
-/// }
-/// ```
-#[cfg(pam_impl)]
-pub fn enable_pam_impl_cfg() {
-    println!("{}", pam_impl_cfg_string())
-}
-
-/// Generates the `cargo:` directives to print in build scripts.
-#[cfg(pam_impl)]
-pub fn pam_impl_cfg_string() -> String {
-    generate_cfg(pam_impl_name!())
-}
-
-fn generate_cfg(name: &str) -> String {
-    let impls: Vec<_> = PamImpl::items()
-        .into_iter()
-        .map(|i| format!(r#""{i:?}""#))
-        .collect();
-    format!(
-        "\
-cargo:rustc-check-cfg=cfg(pam_impl)
-cargo:rustc-check-cfg=cfg(pam_impl, values({impls}))
-cargo:rustc-cfg=pam_impl
-cargo:rustc-cfg=pam_impl={name:?}
-        ",
-        impls = impls.join(",")
-    )
-}
--- a/libpam-sys/libpam-sys-test/build.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -1,5 +1,5 @@
 use bindgen::MacroTypeVariation;
-use libpam_sys_impls::pam_impl_name;
+use libpam_sys::pam_impl_name;
 use libpam_sys_impls::PamImpl;
 use proc_macro2::{Group, Ident, TokenStream, TokenTree};
 use quote::{format_ident, ToTokens};
@@ -13,7 +13,7 @@
 
 fn main() {
     libpam_sys_impls::enable_pam_impl_cfg();
-    let config = match PamImpl::CURRENT {
+    let config = match libpam_sys::pam_impl::CURRENT {
         PamImpl::LinuxPam => TestConfig {
             headers: vec![
                 "<security/_pam_types.h>",
@@ -76,8 +76,8 @@
         "#[allow(deprecated, overflowing_literals)]".into(),
         "fn main() {".into(),
         format!(
-            "assert_eq!(libpam_sys::pam_impl::PamImpl::CURRENT, libpam_sys::pam_impl::PamImpl::{:?});",
-            PamImpl::CURRENT
+            "assert_eq!(libpam_sys::pam_impl::CURRENT, libpam_sys::pam_impl::PamImpl::{:?});",
+            libpam_sys::pam_impl::CURRENT
         ),
     ];
     tests.extend(
--- a/libpam-sys/src/constants.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/src/constants.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -172,6 +172,7 @@
         }
     }
 
+    #[doc(inline)]
     pub use pam_modutil_redirect_fd::*;
 }
 
--- a/libpam-sys/src/lib.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/libpam-sys/src/lib.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -9,12 +9,12 @@
 //!   for callback types used in libpam, so you don't have to type
 //!   `unsafe extern "C" fn(this is so long)` all the time.
 #![doc = ""]
-#![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")]
+#![doc = concat!("This documentation was built for the **", env!("LIBPAMSYS_IMPL"), "** implementation.")]
 //!
 //! You can override this **at build time** by setting the `LIBPAMSYS_IMPL`
 //! environment variable to one of the values of the [`pam_impl::PamImpl`] enum.
-//! For more information about configuration, see the documentation of
-//! [`libpam-sys-impls`](https://crates.io/crates/libpam-sys-impls).
+//! For more information about configuration, see [the documentation of
+//! libpam-sys-impls](libpam_sys_impls::build_target_impl).
 #![allow(non_camel_case_types)]
 #![allow(unused_imports)]
 
@@ -23,7 +23,12 @@
 mod ffi;
 #[doc(inline)]
 pub use crate::{constants::*, ffi::*};
-#[doc(inline)]
-pub use libpam_sys_impls as pam_impl;
-#[doc(inline)]
-pub use libpam_sys_impls::pam_impl_name;
+
+/// Information about the current PAM implementation (or the implementation
+/// that is being built for).
+pub mod pam_impl {
+    #[doc(inline)]
+    pub use libpam_sys_impls::{PamImpl, enable_pam_impl_cfg, pam_impl_cfg_string};
+
+    include!(concat!(env!("OUT_DIR"), "/pam_impl_consts.rs"));
+}
--- a/src/lib.rs	Thu Jul 31 15:42:12 2025 -0400
+++ b/src/lib.rs	Sat Aug 02 18:47:46 2025 -0400
@@ -257,4 +257,6 @@
     handle::{ModuleClient, PamShared, Transaction},
     module::PamModule,
 };
-use libpam_sys_impls::pam_impl_name;
+#[doc(inline)]
+pub use libpam_sys::pam_impl;
+use libpam_sys::pam_impl_name;