diff --git a/libc-test/build.rs b/libc-test/build.rs
index 48dd7c524fc93..80e2e24864c0d 100644
--- a/libc-test/build.rs
+++ b/libc-test/build.rs
@@ -78,7 +78,9 @@ fn ctest_cfg() -> ctest::TestGenerator {
 }
 
 fn ctest_next_cfg() -> ctest_next::TestGenerator {
-    ctest_next::TestGenerator::new()
+    let mut cfg = ctest_next::TestGenerator::new();
+    cfg.skip_private(true);
+    cfg
 }
 
 fn do_semver() {
@@ -217,7 +219,8 @@ fn test_apple(target: &str) {
     let x86_64 = target.contains("x86_64");
     let i686 = target.contains("i686");
 
-    let mut cfg = ctest_cfg();
+    let mut cfg = ctest_next_cfg();
+
     cfg.flag("-Wno-deprecated-declarations");
     cfg.define("__APPLE_USE_RFC_3542", None);
 
@@ -330,11 +333,12 @@ fn test_apple(target: &str) {
         [x86_64]: "crt_externs.h",
     }
 
-    cfg.skip_struct(move |ty| {
-        if ty.starts_with("__c_anonymous_") {
-            return true;
-        }
-        match ty {
+    // Skip anonymous unions/structs.
+    cfg.skip_union(|u| u.ident().starts_with("__c_anonymous_"));
+    cfg.skip_struct(|s| s.ident().starts_with("__c_anonymous_"));
+
+    cfg.skip_struct(|s| {
+        match s.ident() {
             // FIXME(macos): The size is changed in recent macOSes.
             "malloc_zone_t" => true,
             // it is a moving target, changing through versions
@@ -344,16 +348,13 @@ fn test_apple(target: &str) {
             "malloc_introspection_t" => true,
             // sonoma changes the padding `rmx_filler` field.
             "rt_metrics" => true,
-
             _ => false,
         }
     });
 
-    cfg.skip_type(move |ty| {
-        if ty.starts_with("__c_anonymous_") {
-            return true;
-        }
-        match ty {
+    cfg.skip_alias(|ty| ty.ident().starts_with("__c_anonymous_"));
+    cfg.skip_alias(|ty| {
+        match ty.ident() {
             // FIXME(macos): Requires the macOS 14.4 SDK.
             "os_sync_wake_by_address_flags_t" | "os_sync_wait_on_address_flags_t" => true,
 
@@ -364,8 +365,8 @@ fn test_apple(target: &str) {
         }
     });
 
-    cfg.skip_const(move |name| {
-        match name {
+    cfg.skip_const(move |constant| {
+        match constant.ident() {
             // These OSX constants are removed in Sierra.
             // https://developer.apple.com/library/content/releasenotes/General/APIDiffsMacOS10_12/Swift/Darwin.html
             "KERN_KDENABLE_BG_TRACE" | "KERN_KDDISABLE_BG_TRACE" => true,
@@ -385,9 +386,9 @@ fn test_apple(target: &str) {
         }
     });
 
-    cfg.skip_fn(move |name| {
+    cfg.skip_fn(move |func| {
         // skip those that are manually verified
-        match name {
+        match func.ident() {
             // close calls the close_nocancel system call
             "close" => true,
 
@@ -416,8 +417,8 @@ fn test_apple(target: &str) {
         }
     });
 
-    cfg.skip_field(move |struct_, field| {
-        match (struct_, field) {
+    cfg.skip_struct_field(move |struct_, field| {
+        match (struct_.ident(), field.ident()) {
             // FIXME(macos): the array size has been changed since macOS 10.15 ([8] -> [7]).
             ("statfs", "f_reserved") => true,
             ("__darwin_arm_neon_state64", "__v") => true,
@@ -430,39 +431,31 @@ fn test_apple(target: &str) {
         }
     });
 
-    cfg.volatile_item(|i| {
-        use ctest::VolatileItemKind::*;
-        match i {
-            StructField(ref n, ref f) if n == "aiocb" && f == "aio_buf" => true,
-            _ => false,
-        }
+    cfg.volatile_struct_field(|s, f| s.ident() == "aiocb" && f.ident() == "aio_buf");
+
+    cfg.rename_struct_ty(move |ty| {
+        // Just pass all these through, no need for a "struct" prefix
+        ["FILE", "DIR", "Dl_info"]
+            .contains(&ty)
+            .then_some(ty.to_string())
     });
 
-    cfg.type_name(move |ty, is_struct, is_union| {
-        match ty {
-            // Just pass all these through, no need for a "struct" prefix
-            "FILE" | "DIR" | "Dl_info" => ty.to_string(),
+    // OSX calls this something else
+    cfg.rename_type(|ty| (ty == "sighandler_t").then_some("sig_t".to_string()));
 
-            // OSX calls this something else
-            "sighandler_t" => "sig_t".to_string(),
+    cfg.rename_struct_ty(|ty| ty.ends_with("_t").then_some(ty.to_string()));
+    cfg.rename_union_ty(|ty| ty.ends_with("_t").then_some(ty.to_string()));
 
-            t if is_union => format!("union {t}"),
-            t if t.ends_with("_t") => t.to_string(),
-            t if is_struct => format!("struct {t}"),
-            t => t.to_string(),
-        }
-    });
-
-    cfg.field_name(move |struct_, field| {
-        match field {
-            s if s.ends_with("_nsec") && struct_.starts_with("stat") => {
-                s.replace("e_nsec", "espec.tv_nsec")
+    cfg.rename_struct_field(|s, f| {
+        match f.ident() {
+            n if n.ends_with("_nsec") && s.ident().starts_with("stat") => {
+                Some(n.replace("e_nsec", "espec.tv_nsec"))
             }
             // FIXME(macos): sigaction actually contains a union with two variants:
             // a sa_sigaction with type: (*)(int, struct __siginfo *, void *)
             // a sa_handler with type sig_t
-            "sa_sigaction" if struct_ == "sigaction" => "sa_handler".to_string(),
-            s => s.to_string(),
+            "sa_sigaction" if s.ident() == "sigaction" => Some("sa_handler".to_string()),
+            _ => None,
         }
     });
 
@@ -473,7 +466,8 @@ fn test_apple(target: &str) {
         "uuid_t" | "vol_capabilities_set_t" => true,
         _ => false,
     });
-    cfg.generate(src_hotfix_dir().join("lib.rs"), "ctest_output.rs");
+
+    ctest_next::generate_test(&mut cfg, "../src/lib.rs", "ctest_output.rs").unwrap();
 }
 
 fn test_openbsd(target: &str) {
@@ -804,7 +798,7 @@ fn test_windows(target: &str) {
     let i686 = target.contains("i686");
 
     let mut cfg = ctest_next_cfg();
-    cfg.skip_private(true);
+
     if target.contains("msvc") {
         cfg.flag("/wd4324");
     }
