Skip to content

Commit 0eae04b

Browse files
committed
test: Set version-dependent skips based on header defines
When there are additions or updates that are made that are only available with newer platform versions, we don't have a great way to handle testing. The only thing we can really do is unconditionally skip anything that doesn't pass on the oldest versions we have on CI. Add a preprocess-only compiler invocation that collects versions from headers, which are then used to skip only if constants aren't available.
1 parent 6300136 commit 0eae04b

1 file changed

Lines changed: 169 additions & 32 deletions

File tree

libc-test/build.rs

Lines changed: 169 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,18 @@ use std::path::{
1111
Path,
1212
PathBuf,
1313
};
14+
use std::process::{
15+
Command,
16+
Stdio,
17+
};
18+
use std::sync::LazyLock;
1419
use std::{
1520
env,
1621
io,
1722
};
1823

24+
use regex::Regex;
25+
1926
fn do_cc() {
2027
let target = env::var("TARGET").unwrap();
2128
if cfg!(unix) || target.contains("cygwin") {
@@ -176,6 +183,8 @@ fn process_semver_file<W: Write, P: AsRef<Path>>(output: &mut W, path: &mut Path
176183
fn main() {
177184
// Avoid unnecessary re-building.
178185
println!("cargo:rerun-if-changed=build.rs");
186+
// Ensure version checking works, even if we don't use it.
187+
LazyLock::force(&VERSIONS);
179188

180189
do_cc();
181190
do_ctest();
@@ -1201,10 +1210,7 @@ fn test_netbsd(target: &str) {
12011210
let mut cfg = ctest_cfg();
12021211

12031212
// Assume netbsd10 but check for netbsd9 for test config.
1204-
let netbsd9 = match try_command_output("uname", &["-sr"]) {
1205-
Some(s) if s.starts_with("NetBSD 9.") => true,
1206-
_ => false,
1207-
};
1213+
let netbsd9 = matches!(VERSIONS.netbsd, Some((9, _)));
12081214

12091215
cfg.flag("-Wno-deprecated-declarations");
12101216
cfg.define("_NETBSD_SOURCE", Some("1"));
@@ -3731,6 +3737,8 @@ fn test_linux(target: &str) {
37313737
let mips = target.contains("mips");
37323738
let mips64 = target.contains("mips64");
37333739
let mips32 = mips && !mips64;
3740+
let kernel = VERSIONS.linux.unwrap_or((0, 0));
3741+
println!("cargo:warning=kernel version: {kernel:?}");
37343742

37353743
let musl_v1_2_3 = env::var("RUST_LIBC_UNSTABLE_MUSL_V1_2_3").is_ok();
37363744
if musl_v1_2_3 {
@@ -4183,13 +4191,13 @@ fn test_linux(target: &str) {
41834191
"sched_attr" => true,
41844192

41854193
// FIXME(linux): Requires >= 6.9 kernel headers.
4186-
"epoll_params" => true,
4194+
"epoll_params" => kernel < (6,9),
41874195

41884196
// FIXME(linux): Requires >= 6.12 kernel headers.
4189-
"dmabuf_cmsg" | "dmabuf_token" => true,
4197+
"dmabuf_cmsg" | "dmabuf_token" => kernel < (6, 12),
41904198

41914199
// FIXME(linux): Requires >= 6.12 kernel headers.
4192-
"mnt_ns_info" => true,
4200+
"mnt_ns_info" => kernel < (6,12),
41934201

41944202
// FIXME(musl): Struct has changed for new musl versions
41954203
"tcp_info" if musl => true,
@@ -4596,7 +4604,7 @@ fn test_linux(target: &str) {
45964604
"PIDFD_THREAD"
45974605
| "PIDFD_SIGNAL_THREAD"
45984606
| "PIDFD_SIGNAL_THREAD_GROUP"
4599-
| "PIDFD_SIGNAL_PROCESS_GROUP" => true,
4607+
| "PIDFD_SIGNAL_PROCESS_GROUP" => kernel < (6, 9),
46004608
// Linux >= 6.11
46014609
"PIDFD_GET_CGROUP_NAMESPACE"
46024610
| "PIDFD_GET_IPC_NAMESPACE"
@@ -4607,15 +4615,15 @@ fn test_linux(target: &str) {
46074615
| "PIDFD_GET_TIME_NAMESPACE"
46084616
| "PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE"
46094617
| "PIDFD_GET_USER_NAMESPACE"
4610-
| "PIDFD_GET_UTS_NAMESPACE" => true,
4618+
| "PIDFD_GET_UTS_NAMESPACE" => kernel < (6, 11),
46114619
// Linux >= 6.13
46124620
"PIDFD_GET_INFO"
46134621
| "PIDFD_INFO_PID"
46144622
| "PIDFD_INFO_CREDS"
46154623
| "PIDFD_INFO_CGROUPID"
4616-
| "PIDFD_INFO_SIZE_VER0" => true,
4624+
| "PIDFD_INFO_SIZE_VER0" => kernel < (6, 13),
46174625
// Linux >= 6.15
4618-
"PIDFD_INFO_EXIT" | "PIDFD_SELF" | "PIDFD_SELF_PROCESS" => true,
4626+
"PIDFD_INFO_EXIT" | "PIDFD_SELF" | "PIDFD_SELF_PROCESS" => kernel < (6, 15),
46194627

46204628
// is a private value for kernel usage normally
46214629
"FUSE_SUPER_MAGIC" => true,
@@ -4635,13 +4643,13 @@ fn test_linux(target: &str) {
46354643
"NF_NETDEV_NUMHOOKS" | "RLIM_NLIMITS" | "NFT_MSG_MAX" if uclibc => true,
46364644

46374645
// kernel 6.9 minimum
4638-
"RWF_NOAPPEND" => true,
4646+
"RWF_NOAPPEND" => kernel < (6, 9),
46394647

46404648
// kernel 6.11 minimum
4641-
"RWF_ATOMIC" => true,
4649+
"RWF_ATOMIC" => kernel < (6, 11),
46424650

46434651
// kernel 6.14 minimum
4644-
"RWF_DONTCACHE" => true,
4652+
"RWF_DONTCACHE" => kernel < (6, 14),
46454653

46464654
// musl doesn't use <linux/fanotify.h> in <sys/fanotify.h>
46474655
"FAN_REPORT_PIDFD"
@@ -4659,7 +4667,7 @@ fn test_linux(target: &str) {
46594667
}
46604668

46614669
// FIXME(linux32): Requires >= 6.6 kernel headers.
4662-
"XDP_USE_SG" | "XDP_PKT_CONTD" if pointer_width == 32 => true,
4670+
"XDP_USE_SG" | "XDP_PKT_CONTD" if pointer_width == 32 => kernel < (6, 6),
46634671

46644672
// FIXME(linux): Missing only on this platform for some reason
46654673
"PR_MDWE_NO_INHERIT" if gnueabihf => true,
@@ -4672,25 +4680,25 @@ fn test_linux(target: &str) {
46724680
| "XDP_TX_METADATA"
46734681
if musl || pointer_width == 32 =>
46744682
{
4675-
true
4683+
musl || kernel < (6, 8)
46764684
}
46774685

46784686
// FIXME(linux): Requires >= 6.11 kernel headers.
4679-
"XDP_UMEM_TX_METADATA_LEN" => true,
4687+
"XDP_UMEM_TX_METADATA_LEN" => kernel < (6, 11),
46804688

46814689
// FIXME(linux): Requires >= 6.11 kernel headers.
46824690
"NS_GET_MNTNS_ID"
46834691
| "NS_GET_PID_FROM_PIDNS"
46844692
| "NS_GET_TGID_FROM_PIDNS"
46854693
| "NS_GET_PID_IN_PIDNS"
4686-
| "NS_GET_TGID_IN_PIDNS" => true,
4694+
| "NS_GET_TGID_IN_PIDNS" => kernel < (6, 11),
46874695
// FIXME(linux): Requires >= 6.12 kernel headers.
46884696
"MNT_NS_INFO_SIZE_VER0" | "NS_MNT_GET_INFO" | "NS_MNT_GET_NEXT" | "NS_MNT_GET_PREV" => {
4689-
true
4697+
kernel < (6, 12)
46904698
}
46914699

46924700
// FIXME(linux): Requires >= 6.10 kernel headers.
4693-
"SYS_mseal" => true,
4701+
"SYS_mseal" => kernel < (6, 10),
46944702

46954703
// FIXME(linux): seems to not be available all the time (from <include/linux/sched.h>:
46964704
"PF_VCPU" | "PF_IDLE" | "PF_EXITING" | "PF_POSTCOREDUMP" | "PF_IO_WORKER"
@@ -4702,42 +4710,42 @@ fn test_linux(target: &str) {
47024710
| "PF_BLOCK_TS" | "PF_SUSPEND_TASK" => true,
47034711

47044712
// FIXME(linux): Requires >= 6.9 kernel headers.
4705-
"EPIOCSPARAMS" | "EPIOCGPARAMS" => true,
4713+
"EPIOCSPARAMS" | "EPIOCGPARAMS" => kernel < (6, 9),
47064714

47074715
// FIXME(linux): Requires >= 6.11 kernel headers.
4708-
"MAP_DROPPABLE" => true,
4716+
"MAP_DROPPABLE" => kernel < (6, 11),
47094717

47104718
// FIXME(linux): Requires >= 6.12 kernel headers.
4711-
"SOF_TIMESTAMPING_OPT_RX_FILTER" => true,
4719+
"SOF_TIMESTAMPING_OPT_RX_FILTER" => kernel < (6, 12),
47124720

47134721
// FIXME(linux): Requires >= 6.12 kernel headers.
47144722
"SO_DEVMEM_LINEAR" | "SO_DEVMEM_DMABUF" | "SO_DEVMEM_DONTNEED"
4715-
| "SCM_DEVMEM_LINEAR" | "SCM_DEVMEM_DMABUF" => true,
4723+
| "SCM_DEVMEM_LINEAR" | "SCM_DEVMEM_DMABUF" => kernel < (6, 12),
47164724

47174725
// FIXME(linux): Requires >= 6.14 kernel headers.
47184726
"SECBIT_EXEC_DENY_INTERACTIVE"
47194727
| "SECBIT_EXEC_DENY_INTERACTIVE_LOCKED"
47204728
| "SECBIT_EXEC_RESTRICT_FILE"
47214729
| "SECBIT_EXEC_RESTRICT_FILE_LOCKED"
4722-
| "SECURE_ALL_UNPRIVILEGED" => true,
4730+
| "SECURE_ALL_UNPRIVILEGED" => kernel < (6, 14),
47234731

47244732
// FIXME(linux): Value changed in 6.14
4725-
"SECURE_ALL_BITS" | "SECURE_ALL_LOCKS" => true,
4733+
"SECURE_ALL_BITS" | "SECURE_ALL_LOCKS" => kernel < (6, 14),
47264734

47274735
// FIXME(linux): Requires >= 6.9 kernel headers.
4728-
"AT_HWCAP3" | "AT_HWCAP4" => true,
4736+
"AT_HWCAP3" | "AT_HWCAP4" => kernel < (6, 9),
47294737

47304738
// Linux 6.14
4731-
"AT_EXECVE_CHECK" => true,
4739+
"AT_EXECVE_CHECK" => kernel < (6, 14),
47324740

47334741
// FIXME(linux): Requires >= 6.16 kernel headers.
4734-
"PTRACE_SET_SYSCALL_INFO" => true,
4742+
"PTRACE_SET_SYSCALL_INFO" => kernel < (6, 16),
47354743

47364744
// FIXME(linux): Requires >= 6.13 kernel headers.
4737-
"AT_HANDLE_CONNECTABLE" => true,
4745+
"AT_HANDLE_CONNECTABLE" => kernel < (6, 13),
47384746

47394747
// FIXME(linux): Requires >= 6.12 kernel headers.
4740-
"AT_HANDLE_MNT_ID_UNIQUE" => true,
4748+
"AT_HANDLE_MNT_ID_UNIQUE" => kernel < (6, 12),
47414749

47424750
// FIXME(musl): This value is not yet in musl.
47434751
// eabihf targets are tested using an older version of glibc
@@ -6091,10 +6099,139 @@ fn test_qurt(target: &str) {
60916099
ctest::generate_test(&mut cfg, "../src/lib.rs", "ctest_output.rs").unwrap();
60926100
}
60936101

6102+
/// Platform versions for checking expected support. These are extracted from headers so should be
6103+
/// accurate for the target we are building, rather than the host (which `uname` would provide).
6104+
static VERSIONS: LazyLock<Versions> = LazyLock::new(Versions::init_from_cc);
6105+
6106+
#[derive(Clone, Copy, Debug, Default)]
6107+
struct Versions {
6108+
linux: Option<(u32, u32)>,
6109+
glibc: Option<(u32, u32)>,
6110+
freebsd: Option<(u32, u32)>,
6111+
openbsd: Option<(u32, u32)>,
6112+
netbsd: Option<(u32, u32)>,
6113+
macos: Option<(u32, u32)>,
6114+
}
6115+
6116+
impl Versions {
6117+
fn init_from_cc() -> Self {
6118+
let src = r#"
6119+
#ifdef __linux__
6120+
/* Defines LINUX_VERSION_MAJOR, LINUX_VERSION_PATCHLEVEL (integers) */
6121+
#include "linux/version.h"
6122+
#endif
6123+
6124+
/* Including a libc header will define __GLIBC__ */
6125+
#include <stdio.h>
6126+
6127+
#ifdef __GLIBC__
6128+
/* Provides __GLIBC__, __GLIBC_MINOR__ (integers) */
6129+
#include "gnu/libc-version.h"
6130+
#endif
6131+
6132+
#if defined(__FreeBSD__) \
6133+
|| defined(__NetBSD__) \
6134+
|| defined(__OpenBSD__) \
6135+
|| defined(__APPLE__)
6136+
/* FreeBSD: __FreeBSD_version (MMmmRxx string, e.g. 1600018)
6137+
* NetBSD: __NetBSD_Version__ (MMmmrrpp00 string, e.g. 1001000000)
6138+
* OpenBSD: OpenBSD (release date, e.g. 202510) and OpenBSDM_m (e.g. OpenBSD7_8)
6139+
* Apple: __MAC_OS_X_VERSION_MAX_ALLOWED __MAC_M_m (e.g. __MAC_26_5)
6140+
*/
6141+
#include "sys/param.h"
6142+
#endif
6143+
"#;
6144+
6145+
let mut ret = Versions::default();
6146+
6147+
let compiler = cc::Build::new().get_compiler();
6148+
6149+
println!("cargo:warning={compiler:?}");
6150+
6151+
if !compiler.is_like_gnu() {
6152+
println!("cargo:warning=non-gcc-like compiler, skipping versions");
6153+
return ret;
6154+
}
6155+
6156+
// `-dM -E` invokes the preprocessor and prints all `#define`s. `cc` automatically
6157+
// sets target-specific flags.
6158+
let mut cmd = compiler.to_command();
6159+
cmd.stdin(Stdio::piped())
6160+
.stdout(Stdio::piped())
6161+
.args(["-dM", "-E", "-"]);
6162+
6163+
println!("cargo:warning=invocation: {cmd:?}");
6164+
let mut child = cmd.spawn().expect("failed to spawn compiler");
6165+
child
6166+
.stdin
6167+
.take()
6168+
.unwrap()
6169+
.write_all(src.as_bytes())
6170+
.expect("failed to send stdin");
6171+
let out = child.wait_with_output().expect("failed to wait on child");
6172+
let out = String::from_utf8_lossy(&out.stdout);
6173+
6174+
// Allow spaces everywhere so we match things like `\n # define foo bar \n`.
6175+
let re = Regex::new(r"^\s*#\s*define\s+(\w+)\s+(.*?)\s*$").unwrap();
6176+
let obsd_re = Regex::new(r"^OpenBSD(\d+)_(\d+)$").unwrap();
6177+
let mac_re = Regex::new(r"^__MAC_(\d+)_(\d+)").unwrap();
6178+
6179+
for line in out.lines() {
6180+
let Some(caps) = re.captures(line) else {
6181+
continue;
6182+
};
6183+
let name = &caps[1];
6184+
let value = &caps[2];
6185+
println!("cargo:warning=#define {name} {value}");
6186+
6187+
match name {
6188+
"LINUX_VERSION_MAJOR" => {
6189+
ret.linux.get_or_insert_default().0 = value.parse().unwrap()
6190+
}
6191+
"LINUX_VERSION_PATCHLEVEL" => {
6192+
ret.linux.get_or_insert_default().1 = value.parse().unwrap()
6193+
}
6194+
"__GLIBC__" => ret.glibc.get_or_insert_default().0 = value.parse().unwrap(),
6195+
"__GLIBC_MINOR__" => ret.glibc.get_or_insert_default().0 = value.parse().unwrap(),
6196+
"__MAC_OS_X_VERSION_MAX_ALLOWED" => {
6197+
let caps = mac_re.captures(value).unwrap();
6198+
let major: u32 = caps[1].parse().unwrap();
6199+
let minor: u32 = caps[2].parse().unwrap();
6200+
ret.macos = Some((major, minor));
6201+
}
6202+
"__FreeBSD_version" => {
6203+
// Format: MmmRxx where M is major (possibly multi-digit), mm is minor, R
6204+
// indicates release status, xx is some sequence.
6205+
let major: u32 = value[..(value.len() - 5)].parse().unwrap();
6206+
let minor: u32 = value[(value.len() - 5)..(value.len() - 3)].parse().unwrap();
6207+
ret.freebsd = Some((major, minor));
6208+
}
6209+
"__NetBSD_Version__" => {
6210+
// Format: MMmmrrpp00 where M is major (possibly multi-digit), mm is minor, r
6211+
// and p are patch level.
6212+
let major: u32 = value[..(value.len() - 8)].parse().unwrap();
6213+
let minor: u32 = value[(value.len() - 8)..(value.len() - 6)].parse().unwrap();
6214+
ret.netbsd = Some((major, minor));
6215+
}
6216+
x if obsd_re.is_match(x) => {
6217+
let caps = obsd_re.captures(name).expect("is_match checked");
6218+
let major: u32 = caps[1].parse().unwrap();
6219+
let minor: u32 = caps[2].parse().unwrap();
6220+
ret.openbsd = Some((major, minor));
6221+
}
6222+
_ => (),
6223+
}
6224+
}
6225+
6226+
println!("cargo:warning=detected versions: {ret:?}");
6227+
ret
6228+
}
6229+
}
6230+
60946231
/// Attempt to execute a command and collect its output, If the command fails for whatever
60956232
/// reason, return `None`.
60966233
fn try_command_output(cmd: &str, args: &[&str]) -> Option<String> {
6097-
let output = std::process::Command::new(cmd).args(args).output().ok()?;
6234+
let output = Command::new(cmd).args(args).output().ok()?;
60986235

60996236
if !output.status.success() {
61006237
return None;

0 commit comments

Comments
 (0)