nix_dev_env/
nix_version_check.rs1use std::ffi::OsStr;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5use semver::{Comparator, Op, Prerelease, Version, VersionReq};
6
7use crate::nix_command;
8
9static REQUIRED_NIX_VERSION: Lazy<VersionReq> = Lazy::new(|| VersionReq {
10 comparators: vec![Comparator {
11 op: Op::GreaterEq,
12 major: 2,
13 minor: Some(10),
14 patch: Some(0),
15 pre: Prerelease::EMPTY,
16 }],
17});
18
19static SEMVER_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"([0-9]+\.[0-9]+\.[0-9]+)").unwrap());
20
21pub fn check_nix_version() -> anyhow::Result<()> {
22 check_nix_program_version(OsStr::new("nix"))
23}
24
25fn check_nix_program_version(nix_executable_path: impl AsRef<OsStr>) -> anyhow::Result<()> {
26 let stdout_content = nix_command::nix_program(nix_executable_path.as_ref(), ["--version"])?;
27
28 if stdout_content.is_empty() {
29 return Err(anyhow::format_err!("`nix --version` failed to execute."));
30 }
31
32 let nix_version_match = SEMVER_RE
33 .find(&stdout_content)
34 .ok_or_else(|| anyhow::format_err!("SemVer from `nix --version` could not be found."))?;
35 let nix_version = Version::parse(nix_version_match.as_str())?;
36
37 if REQUIRED_NIX_VERSION.matches(&nix_version) {
38 Ok(())
39 } else {
40 Err(anyhow::format_err!("`nix` version too old for flakes."))
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use std::{env, fs, os::unix::fs::PermissionsExt, path::PathBuf};
47
48 use super::check_nix_program_version;
49
50 #[derive(Debug)]
51 struct NixExecutable {
52 pub _dir: tempfile::TempDir,
54 pub file_path: PathBuf,
55 }
56
57 impl NixExecutable {
58 fn new(file_contents: &str) -> Self {
59 let dir = tempfile::tempdir().unwrap();
62 let file_path = dir.path().join("nix");
63 let bash_path = env::var("NIX_BIN_BASH").unwrap_or_else(|_| String::from("/bin/bash"));
64 fs::write(&file_path, format!("#! {bash_path}\n{file_contents}")).unwrap();
65 fs::set_permissions(&file_path, fs::Permissions::from_mode(0o777)).unwrap();
66 Self {
67 _dir: dir,
68 file_path,
69 }
70 }
71 }
72
73 #[test]
74 fn test_error_on_exit_failure() {
75 let nix_executable = NixExecutable::new(r#"exit 1;"#);
76 assert_eq!(
77 check_nix_program_version(&nix_executable.file_path)
78 .unwrap_err()
79 .to_string(),
80 format!(
81 "`{} --extra-experimental-features nix-command' flakes' --version` failed with error:\nprocess exited unsuccessfully: exit status: 1",
82 nix_executable.file_path.to_string_lossy()
83 )
84 );
85 }
86
87 #[test]
88 fn test_error_on_empty_stdout() {
89 let nix_executable = NixExecutable::new(r#"printf "";"#);
90 assert_eq!(
91 check_nix_program_version(nix_executable.file_path)
92 .unwrap_err()
93 .to_string(),
94 "`nix --version` failed to execute."
95 );
96 }
97
98 #[test]
99 fn test_error_on_missing_semver() {
100 let nix_executable = NixExecutable::new(r#"echo "hello";"#);
101 assert_eq!(
102 check_nix_program_version(nix_executable.file_path)
103 .unwrap_err()
104 .to_string(),
105 "SemVer from `nix --version` could not be found."
106 );
107 }
108
109 #[test]
110 fn test_error_on_too_old_version() {
111 let nix_executable = NixExecutable::new(r#"echo "nix (Nix) 0.0.0";"#);
112 assert_eq!(
113 check_nix_program_version(nix_executable.file_path)
114 .unwrap_err()
115 .to_string(),
116 "`nix` version too old for flakes."
117 );
118 }
119
120 #[test]
121 fn test_version_matches_minimum() {
122 let nix_executable = NixExecutable::new(r#"echo "nix (Nix) 2.10.0";"#);
123 check_nix_program_version(nix_executable.file_path).unwrap();
124 }
125
126 #[test]
127 fn test_version_matches_newer() {
128 let nix_executable = NixExecutable::new(r#"echo "nix (Nix) 2.30.0";"#);
129 check_nix_program_version(nix_executable.file_path).unwrap();
130 }
131}