diff --git a/libc-test/build.rs b/libc-test/build.rs
index aba2a3056852c30f71e7f1ff333cb03f2803eb56..bb0252397b46ef3c4111d29bc201a8e3e88124bf 100644
--- a/libc-test/build.rs
+++ b/libc-test/build.rs
@@ -14,12 +14,10 @@ fn do_cc() {
 
 fn do_ctest() {
     let target = env::var("TARGET").unwrap();
-    let aarch64 = target.contains("aarch64");
     let i686 = target.contains("i686");
     let x86_64 = target.contains("x86_64");
     let x32 = target.ends_with("gnux32");
     let linux = target.contains("unknown-linux");
-    let android = target.contains("android");
     let emscripten = target.contains("asm");
     let musl = target.contains("musl") || emscripten;
     let uclibc = target.contains("uclibc");
@@ -39,22 +37,17 @@ fn do_ctest() {
         t if t.contains("netbsd") => return test_netbsd(t),
         t if t.contains("dragonfly") => return test_dragonflybsd(t),
         t if t.contains("wasi") => return test_wasi(t),
+        t if t.contains("android") => return test_android(t),
         _ => (),
     }
 
     // Pull in extra goodies
-    if linux || android || emscripten {
+    if linux || emscripten {
         cfg.define("_GNU_SOURCE", None);
     } else if freebsd {
         cfg.define("_WITH_GETLINE", None);
     }
 
-    // Android doesn't actually have in_port_t but it's much easier if we
-    // provide one for us to test against
-    if android {
-        cfg.define("in_port_t", Some("uint16_t"));
-    }
-
     cfg.header("errno.h")
         .header("fcntl.h")
         .header("limits.h")
@@ -75,7 +68,7 @@ fn do_ctest() {
     cfg.header("net/if.h");
     cfg.header("net/route.h");
     cfg.header("net/if_arp.h");
-    if linux || android {
+    if linux {
         cfg.header("linux/if_alg.h");
     }
     cfg.header("netdb.h");
@@ -116,41 +109,27 @@ fn do_ctest() {
     cfg.header("sys/statvfs.h");
     cfg.header("sys/times.h");
 
-    if android {
-        if !aarch64 && !x86_64 {
-            // time64_t is not define for aarch64 and x86_64
-            // If included it will generate the error 'Your time_t is already 64-bit'
-            cfg.header("time64.h");
-        }
-        cfg.header("arpa/inet.h");
-        cfg.header("xlocale.h");
-        cfg.header("ifaddrs.h");
-        if i686 || x86_64 {
-            cfg.header("sys/reg.h");
-        }
-    } else {
-        cfg.header("glob.h");
-        cfg.header("ifaddrs.h");
-        cfg.header("langinfo.h");
+    cfg.header("glob.h");
+    cfg.header("ifaddrs.h");
+    cfg.header("langinfo.h");
 
-        if !openbsd && !freebsd {
-            cfg.header("sys/quota.h");
-        }
+    if !openbsd && !freebsd {
+        cfg.header("sys/quota.h");
+    }
 
-        if !musl && !x32 {
-            cfg.header("sys/sysctl.h");
-        }
+    if !musl && !x32 {
+        cfg.header("sys/sysctl.h");
+    }
 
-        if !musl && !uclibc {
-            if !openbsd && !uclibc {
-                cfg.header("execinfo.h");
-            }
+    if !musl && !uclibc {
+        if !openbsd && !uclibc {
+            cfg.header("execinfo.h");
+        }
 
-            if openbsd {
-                cfg.header("utmp.h");
-            } else {
-                cfg.header("utmpx.h");
-            }
+        if openbsd {
+            cfg.header("utmp.h");
+        } else {
+            cfg.header("utmpx.h");
         }
     }
 
@@ -192,7 +171,7 @@ fn do_ctest() {
         }
     }
 
-    if linux || android || emscripten {
+    if linux || emscripten {
         cfg.header("malloc.h");
         cfg.header("net/ethernet.h");
         cfg.header("netpacket/packet.h");
@@ -232,7 +211,7 @@ fn do_ctest() {
         }
     }
 
-    if linux || android {
+    if linux {
         cfg.header("sys/fsuid.h");
         cfg.header("linux/module.h");
         cfg.header("linux/seccomp.h");
@@ -307,7 +286,6 @@ fn do_ctest() {
         }
     });
 
-    let target2 = target.clone();
     cfg.field_name(move |struct_, field| {
         match field {
             "st_birthtime" if openbsd && struct_ == "stat" => {
@@ -319,11 +297,7 @@ fn do_ctest() {
             // Our stat *_nsec fields normally don't actually exist but are part
             // of a timeval struct
             s if s.ends_with("_nsec") && struct_.starts_with("stat") => {
-                if target2.contains("android") {
-                    s.to_string()
-                } else {
-                    s.replace("e_nsec", ".tv_nsec")
-                }
+                s.replace("e_nsec", ".tv_nsec")
             }
             "u64" if struct_ == "epoll_event" => "data.u64".to_string(),
             "type_"
@@ -364,11 +338,6 @@ fn do_ctest() {
             // definition. Because it's tested on other Linux targets, skip it.
             "input_mask" if musl => true,
 
-            // These structs have changed since unified headers in NDK r14b.
-            // `st_atime` and `st_atime_nsec` have changed sign.
-            // FIXME: unskip it for next major release
-            "stat" | "stat64" if android => true,
-
             // These are tested as part of the linux_fcntl tests since there are
             // header conflicts when including them with all the other structs.
             "termios2" => true,
@@ -483,13 +452,13 @@ fn do_ctest() {
                 true
             }
 
-            // Both android and musl use old kernel headers
+            // musl uses old kernel headers
             // These are constants used in getrandom syscall
-            "GRND_NONBLOCK" | "GRND_RANDOM" if musl || android => true,
+            "GRND_NONBLOCK" | "GRND_RANDOM" if musl => true,
 
             // Defined by libattr not libc on linux (hard to test).
             // See constant definition for more details.
-            "ENOATTR" if android || linux => true,
+            "ENOATTR" if linux => true,
 
             // On mips*-unknown-linux-gnu* CMSPAR cannot be included with the set of headers we
             // want to use here for testing. It's originally defined in asm/termbits.h, which is
@@ -520,7 +489,7 @@ fn do_ctest() {
             // x86_64 and i686 builders it seems to be available for all targets, so at least test
             // it there.
             "MFD_HUGETLB"
-                if !(x86_64 || i686) || musl || (x86_64 && android) =>
+                if !(x86_64 || i686) || musl =>
             {
                 true
             }
@@ -584,13 +553,9 @@ fn do_ctest() {
             // send*/recv* syscalls
             "sendmmsg" | "recvmmsg" if musl => true,
 
-            // typed 2nd arg on linux and android
-            "gettimeofday" if linux || android || freebsd || openbsd => true,
-
-            // not declared in newer android toolchains
-            "getdtablesize" if android => true,
+            // typed 2nd arg on linux
+            "gettimeofday" if linux || freebsd || openbsd => true,
 
-            "dlerror" if android => true, // const-ness is added
             "dladdr" if musl => true, // const-ness only added recently
 
             // There seems to be a small error in EGLIBC's eventfd.h header. The
@@ -625,10 +590,6 @@ fn do_ctest() {
             "lio_listio" if freebsd => true,
             "lio_listio" if musl => true,
 
-            // Apparently the NDK doesn't have this defined on android, but
-            // it's in a header file?
-            "endpwent" if android => true,
-
             // These are either unimplemented or optionally built into uClibc
             // or "sysinfo", where it's defined but the structs in linux/sysinfo.h and sys/sysinfo.h
             // clash so it can't be tested
@@ -639,20 +600,11 @@ fn do_ctest() {
             "sysinfo" | "newlocale" | "duplocale" | "freelocale" | "uselocale" |
             "nl_langinfo_l" | "wcslen" | "wcstombs" if uclibc => true,
 
-            // Apparently res_init exists on Android, but isn't defined in a header:
-            // https://mail.gnome.org/archives/commits-list/2013-May/msg01329.html
-            "res_init" if android => true,
-
             // Definition of those functions as changed since unified headers from NDK r14b
             // These changes imply some API breaking changes but are still ABI compatible.
             // We can wait for the next major release to be compliant with the new API.
             // FIXME: unskip these for next major release
             "strerror_r" | "madvise" | "msync" | "mprotect" | "recvfrom" | "getpriority" |
-            "setpriority" | "personality" if android  => true,
-            // In Android 64 bits, these functions have been fixed since unified headers.
-            // Ignore these until next major version.
-            "bind" | "writev" | "readv" | "sendmsg" | "recvmsg" if android && (aarch64 || x86_64) => true,
-
             // Removed in OpenBSD 6.5
             // https://marc.info/?l=openbsd-cvs&m=154723400730318
             "mincore" if openbsd => true,
@@ -661,14 +613,6 @@ fn do_ctest() {
         }
     });
 
-    cfg.skip_static(move |name| {
-        match name {
-            // Internal constant, not declared in any headers.
-            "__progname" if android => true,
-            _ => false,
-        }
-    });
-
     cfg.skip_field_type(move |struct_, field| {
         // This is a weird union, don't check the type.
         (struct_ == "ifaddrs" && field == "ifa_ifu") ||
@@ -711,14 +655,14 @@ fn do_ctest() {
 
     cfg.generate("../src/lib.rs", "main.rs");
 
-    // On Linux or Android also generate another script for testing linux/fcntl declarations.
+    // On Linux also generate another script for testing linux/fcntl declarations.
     // These cannot be tested normally because including both `linux/fcntl.h` and `fcntl.h`
     // fails on a lot of platforms.
     let mut cfg = ctest::TestGenerator::new();
     cfg.skip_type(|_| true)
         .skip_fn(|_| true)
         .skip_static(|_| true);
-    if android || linux {
+    if linux {
         // musl defines these directly in `fcntl.h`
         if musl {
             cfg.header("fcntl.h");
@@ -1839,7 +1783,7 @@ fn test_dragonflybsd(target: &str) {
             "getrlimit" | "getrlimit64" |    // non-int in 1st arg
             "setrlimit" | "setrlimit64" |    // non-int in 1st arg
             "prlimit" | "prlimit64" |        // non-int in 2nd arg
-            // typed 2nd arg on linux and android
+            // typed 2nd arg on linux
             "gettimeofday" => true,
 
             _ => false,
@@ -1937,3 +1881,357 @@ fn test_wasi(target: &str) {
 
     cfg.generate("../src/lib.rs", "main.rs");
 }
+
+fn test_android(target: &str) {
+    assert!(target.contains("android"));
+    let target_pointer_width = match target {
+        t if t.contains("aarch64") || t.contains("x86_64") => 64,
+        t if t.contains("i686") || t.contains("arm") => 32,
+        t => panic!("unsupported target: {}", t),
+    };
+    let x86 = target.contains("i686") || target.contains("x86_64");
+
+    let mut cfg = ctest::TestGenerator::new();
+    cfg.define("_GNU_SOURCE", None);
+
+    // FIXME: still necessary?
+    cfg.flag("-Wno-deprecated-declarations");
+
+    // Android doesn't actually have in_port_t but it's much easier if we
+    // provide one for us to test against
+    // FIXME: still necessary?
+    cfg.define("in_port_t", Some("uint16_t"));
+
+    headers! { cfg:
+               "arpa/inet.h",
+               "asm/mman.h",
+               "ctype.h",
+               "dirent.h",
+               "dlfcn.h",
+               "errno.h",
+               "fcntl.h",
+               "grp.h",
+               "ifaddrs.h",
+               "limits.h",
+               "linux/dccp.h",
+               "linux/fs.h",
+               "linux/genetlink.h",
+               "linux/if_alg.h",
+               "linux/if_ether.h",
+               "linux/if_tun.h",
+               "linux/magic.h",
+               "linux/memfd.h",
+               "linux/module.h",
+               "linux/net_tstamp.h",
+               "linux/netfilter/nf_tables.h",
+               "linux/netfilter_ipv4.h",
+               "linux/netfilter_ipv6.h",
+               "linux/netlink.h",
+               "linux/quota.h",
+               "linux/reboot.h",
+               "linux/seccomp.h",
+               "linux/sockios.h",
+               "locale.h",
+               "malloc.h",
+               "net/ethernet.h",
+               "net/if.h",
+               "net/if_arp.h",
+               "net/route.h",
+               "netdb.h",
+               "netinet/in.h",
+               "netinet/ip.h",
+               "netinet/tcp.h",
+               "netinet/udp.h",
+               "netpacket/packet.h",
+               "poll.h",
+               "pthread.h",
+               "pty.h",
+               "pwd.h",
+               "resolv.h",
+               "sched.h",
+               "semaphore.h",
+               "signal.h",
+               "stddef.h",
+               "stdint.h",
+               "stdio.h",
+               "stdlib.h",
+               "string.h",
+               "sys/epoll.h",
+               "sys/eventfd.h",
+               "sys/file.h",
+               "sys/fsuid.h",
+               "sys/inotify.h",
+               "sys/ioctl.h",
+               "sys/mman.h",
+               "sys/mount.h",
+               "sys/personality.h",
+               "sys/prctl.h",
+               "sys/ptrace.h",
+               "sys/reboot.h",
+               "sys/resource.h",
+               "sys/sendfile.h",
+               "sys/signalfd.h",
+               "sys/socket.h",
+               "sys/stat.h",
+               "sys/statvfs.h",
+               "sys/swap.h",
+               "sys/syscall.h",
+               "sys/sysinfo.h",
+               "sys/time.h",
+               "sys/times.h",
+               "sys/types.h",
+               "sys/uio.h",
+               "sys/un.h",
+               "sys/utsname.h",
+               "sys/vfs.h",
+               "sys/wait.h",
+               "syslog.h",
+               "termios.h",
+               "time.h",
+               "unistd.h",
+               "utime.h",
+               "utmp.h",
+               "wchar.h",
+               "xlocale.h",
+    }
+
+    if target_pointer_width == 32 {
+        // time64_t is not defined for 64-bit targets If included it will
+        // generate the error 'Your time_t is already 64-bit'
+        cfg.header("time64.h");
+    }
+    if x86 {
+        cfg.header("sys/reg.h");
+    }
+
+    cfg.type_name(move |ty, is_struct, is_union| {
+        match ty {
+            // Just pass all these through, no need for a "struct" prefix
+            // FIXME: still required ?
+            "FILE" | "fd_set" | "Dl_info" | "DIR" | "Elf32_Phdr"
+            | "Elf64_Phdr" | "Elf32_Shdr" | "Elf64_Shdr" | "Elf32_Sym"
+            | "Elf64_Sym" | "Elf32_Ehdr" | "Elf64_Ehdr" | "Elf32_Chdr"
+            | "Elf64_Chdr" => ty.to_string(),
+
+            t if is_union => format!("union {}", t),
+
+            t if t.ends_with("_t") => t.to_string(),
+
+            // put `struct` in front of all structs:.
+            t if is_struct => format!("struct {}", t),
+
+            t => t.to_string(),
+        }
+    });
+
+    cfg.field_name(move |struct_, field| {
+        match field {
+            // Our stat *_nsec fields normally don't actually exist but are part
+            // of a timeval struct
+            s if s.ends_with("_nsec") && struct_.starts_with("stat") => {
+                s.to_string()
+            }
+            // FIXME: still necessary?
+            "u64" if struct_ == "epoll_event" => "data.u64".to_string(),
+            s => s.to_string(),
+        }
+    });
+
+    cfg.skip_type(move |ty| {
+        match ty {
+            // sighandler_t is crazy across platforms
+            // FIXME: still necessary?
+            "sighandler_t" => true,
+            _ => false,
+        }
+    });
+
+    cfg.skip_struct(move |ty| {
+        match ty {
+            // This is actually a union, not a struct
+            // FIXME: still necessary
+            "sigval" => true,
+
+            // These structs have changed since unified headers in NDK r14b.
+            // `st_atime` and `st_atime_nsec` have changed sign.
+            // FIXME: unskip it for next major release
+            "stat" | "stat64" => true,
+
+            // These are tested as part of the linux_fcntl tests since there are
+            // header conflicts when including them with all the other structs.
+            // FIXME: still necessary
+            "termios2" => true,
+
+            _ => false,
+        }
+    });
+
+    cfg.skip_signededness(move |c| {
+        match c {
+            // FIXME: still necessary?
+            "LARGE_INTEGER" | "float" | "double" => true,
+            // FIXME: still necessary?
+            n if n.starts_with("pthread") => true,
+            _ => false,
+        }
+    });
+
+    cfg.skip_const(move |name| {
+        match name {
+            // FIXME: still necessary?
+            "SIG_DFL" | "SIG_ERR" | "SIG_IGN" => true, // sighandler_t weirdness
+            // FIXME: still necessary?
+            "SIGUNUSED" => true, // removed in glibc 2.26
+
+            // weird signed extension or something like that?
+            // FIXME: still necessary?
+            "MS_NOUSER" => true,
+            // FIXME: still necessary?
+            "MS_RMT_MASK" => true, // updated in glibc 2.22 and musl 1.1.13
+
+            // Android uses old kernel headers
+            // These are constants used in getrandom syscall
+            // FIXME: still necessary?
+            "GRND_NONBLOCK" | "GRND_RANDOM" => true,
+
+            // Defined by libattr not libc on linux (hard to test).
+            // See constant definition for more details.
+            // FIXME: still necessary?
+            "ENOATTR" => true,
+
+            // FIXME: still necessary?
+            "BOTHER" => true,
+
+            // MFD_HUGETLB is not available in some older libc versions on the CI builders. On the
+            // x86_64 and i686 builders it seems to be available for all targets, so at least test
+            // it there.
+            // FIXME: still necessary?
+            "MFD_HUGETLB" => true,
+
+            // These change all the time from release to release of linux
+            // distros, let's just not bother trying to verify them. They
+            // shouldn't be used in code anyway...
+            // FIXME: still necessary?
+            "AF_MAX" | "PF_MAX" => true,
+
+            _ => false,
+        }
+    });
+
+    cfg.skip_fn(move |name| {
+        // skip those that are manually verified
+        match name {
+            // FIXME: still necessary?
+            "execv" |       // crazy stuff with const/mut
+            "execve" |
+            "execvp" |
+            "execvpe" |
+            "fexecve" => true,
+
+            // typed 2nd arg on android
+            // FIXME: still necessary?
+            "gettimeofday" => true,
+
+            // not declared in newer android toolchains
+            // FIXME: still necessary?
+            "getdtablesize" => true,
+
+            // FIXME: still necessary?
+            "dlerror" => true, // const-ness is added
+
+            // Apparently the NDK doesn't have this defined on android, but
+            // it's in a header file?
+            // FIXME: still necessary?
+            "endpwent" => true,
+
+            // Apparently res_init exists on Android, but isn't defined in a header:
+            // https://mail.gnome.org/archives/commits-list/2013-May/msg01329.html
+            // FIXME: still necessary?
+            "res_init" => true,
+
+            // Definition of those functions as changed since unified headers from NDK r14b
+            // These changes imply some API breaking changes but are still ABI compatible.
+            // We can wait for the next major release to be compliant with the new API.
+            // FIXME: unskip these for next major release
+            "strerror_r" | "madvise" | "msync" | "mprotect" | "recvfrom" | "getpriority" |
+            "setpriority" | "personality"  => true,
+            // In Android 64 bits, these functions have been fixed since unified headers.
+            // Ignore these until next major version.
+            "bind" | "writev" | "readv" | "sendmsg" | "recvmsg"
+                if target_pointer_width == 64 => true,
+
+            _ => false,
+        }
+    });
+
+    cfg.skip_static(move |name| {
+        match name {
+            // Internal constant, not declared in any headers.
+            // FIXME: still necessary
+            "__progname" => true,
+            _ => false,
+        }
+    });
+
+    // FIXME: still necessary?
+    cfg.skip_field_type(move |struct_, field| {
+        // This is a weird union, don't check the type.
+        (struct_ == "ifaddrs" && field == "ifa_ifu") ||
+        // sighandler_t type is super weird
+        (struct_ == "sigaction" && field == "sa_sigaction") ||
+        // sigval is actually a union, but we pretend it's a struct
+        (struct_ == "sigevent" && field == "sigev_value") ||
+        // aio_buf is "volatile void*" and Rust doesn't understand volatile
+        (struct_ == "aiocb" && field == "aio_buf")
+    });
+
+    // FIXME: still necessary?
+    cfg.skip_field(move |struct_, field| {
+        // this is actually a union on linux, so we can't represent it well and
+        // just insert some padding.
+        (struct_ == "siginfo_t" && field == "_pad") ||
+        // sigev_notify_thread_id is actually part of a sigev_un union
+        (struct_ == "sigevent" && field == "sigev_notify_thread_id") ||
+        // signalfd had SIGSYS fields added in Linux 4.18, but no libc release has them yet.
+        (struct_ == "signalfd_siginfo" && (field == "ssi_addr_lsb" ||
+                                           field == "_pad2" ||
+                                           field == "ssi_syscall" ||
+                                           field == "ssi_call_addr" ||
+                                           field == "ssi_arch"))
+    });
+
+    // FIXME: remove
+    cfg.fn_cname(move |name, _cname| name.to_string());
+
+    cfg.generate("../src/lib.rs", "main.rs");
+
+    // On Android also generate another script for testing linux/fcntl
+    // declarations. These cannot be tested normally because including both
+    // `linux/fcntl.h` and `fcntl.h` fails.
+    //
+    // FIXME: is still necessary?
+    let mut cfg = ctest::TestGenerator::new();
+    cfg.skip_type(|_| true)
+        .skip_fn(|_| true)
+        .skip_static(|_| true);
+    cfg.header("linux/fcntl.h");
+    cfg.header("net/if.h");
+    cfg.header("linux/if.h");
+    cfg.header("linux/quota.h");
+    cfg.header("asm/termbits.h");
+    cfg.skip_const(move |name| match name {
+        "F_CANCELLK" | "F_ADD_SEALS" | "F_GET_SEALS" => false,
+        "F_SEAL_SEAL" | "F_SEAL_SHRINK" | "F_SEAL_GROW" | "F_SEAL_WRITE" => {
+            false
+        }
+        "BOTHER" => false,
+        _ => true,
+    });
+    cfg.skip_struct(|s| s != "termios2");
+    cfg.type_name(move |ty, is_struct, is_union| match ty {
+        t if is_struct => format!("struct {}", t),
+        t if is_union => format!("union {}", t),
+        t => t.to_string(),
+    });
+    cfg.generate("../src/lib.rs", "linux_fcntl.rs");
+}