nix_bindings_util/nix_version.rs
1//! Nix version parsing and conditional compilation support.
2
3/// Emit [`cargo:rustc-cfg`] directives for Nix version-based conditional compilation.
4///
5/// Call from build.rs with the Nix version and desired version gates.
6///
7/// [`cargo:rustc-cfg`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-cfg
8///
9/// # Example
10///
11/// ```
12/// use nix_bindings_util::nix_version::emit_version_cfg;
13/// # // Stub pkg_config so that we can render a full usage example
14/// # mod pkg_config { pub fn probe_library(_: &str) -> Result<Library, ()> { Ok(Library { version: "2.33.0pre".into() }) }
15/// # pub struct Library { pub version: String } }
16///
17/// let nix_version = pkg_config::probe_library("nix-store-c").unwrap().version;
18/// emit_version_cfg(&nix_version, &["2.26", "2.33.0pre", "2.33"]);
19/// ```
20///
21/// Emits `nix_at_least="2.26"` and `nix_at_least="2.33.0pre"` for version 2.33.0pre,
22/// usable as `#[cfg(nix_at_least = "2.26")]`.
23pub fn emit_version_cfg(nix_version: &str, relevant_versions: &[&str]) {
24 // Declare the known versions for cargo check-cfg
25 let versions = relevant_versions
26 .iter()
27 .map(|v| format!("\"{}\"", v))
28 .collect::<Vec<_>>()
29 .join(",");
30
31 println!(
32 "cargo:rustc-check-cfg=cfg(nix_at_least,values({}))",
33 versions
34 );
35
36 let nix_version = parse_version(nix_version);
37
38 for version_str in relevant_versions {
39 let version = parse_version(version_str);
40 if nix_version >= version {
41 println!("cargo:rustc-cfg=nix_at_least=\"{}\"", version_str);
42 }
43 }
44}
45
46/// Parse a Nix version string into a comparable tuple `(major, minor, patch)`.
47///
48/// Pre-release versions (containing `"pre"`) get patch = -1, sorting before stable releases.
49/// Omitted patch defaults to 0.
50///
51/// # Examples
52///
53/// ```
54/// use nix_bindings_util::nix_version::parse_version;
55///
56/// assert_eq!(parse_version("2.26"), (2, 26, 0));
57/// assert_eq!(parse_version("2.33.0pre"), (2, 33, -1));
58/// assert_eq!(parse_version("2.33"), (2, 33, 0));
59/// assert_eq!(parse_version("2.33.1"), (2, 33, 1));
60///
61/// // Pre-release versions sort before stable
62/// assert!(parse_version("2.33.0pre") < parse_version("2.33"));
63/// ```
64pub fn parse_version(version_str: &str) -> (u32, u32, i32) {
65 let parts = version_str.split('.').collect::<Vec<&str>>();
66 let major = parts[0].parse::<u32>().unwrap();
67 let minor = parts[1].parse::<u32>().unwrap();
68 let patch = if parts.get(2).is_some_and(|s| s.contains("pre")) {
69 -1i32
70 } else {
71 parts
72 .get(2)
73 .and_then(|s| s.parse::<i32>().ok())
74 .unwrap_or(0)
75 };
76 (major, minor, patch)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_parse_version() {
85 assert_eq!(parse_version("2.26"), (2, 26, 0));
86 assert_eq!(parse_version("2.33.0pre"), (2, 33, -1));
87 assert_eq!(parse_version("2.33"), (2, 33, 0));
88 assert_eq!(parse_version("2.33.1"), (2, 33, 1));
89 }
90
91 #[test]
92 fn test_version_ordering() {
93 // Pre-release versions should sort before stable
94 assert!(parse_version("2.33.0pre") < parse_version("2.33"));
95 assert!(parse_version("2.33.0pre") < parse_version("2.33.0"));
96
97 // Normal version ordering
98 assert!(parse_version("2.26") < parse_version("2.33"));
99 assert!(parse_version("2.33") < parse_version("2.33.1"));
100 }
101}