-
-
Notifications
You must be signed in to change notification settings - Fork 93
Expand file tree
/
Copy pathcheck.rs
More file actions
311 lines (262 loc) · 9.13 KB
/
check.rs
File metadata and controls
311 lines (262 loc) · 9.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
use std::cmp::Ordering;
use std::env;
use std::sync::Mutex;
use color_eyre::{eyre, Result};
use semver::Version;
use crate::util;
/// Verifies if the installed Nix version meets requirements
///
/// # Returns
///
/// * `Result<()>` - Ok if version requirements are met, error otherwise
pub fn check_nix_version() -> Result<()> {
if env::var("NH_NO_CHECKS").is_ok() {
return Ok(());
}
let version = util::get_nix_version()?;
let is_lix_binary = util::is_lix()?;
// XXX: Both Nix and Lix follow semantic versioning (semver). Update the
// versions below once latest stable for either of those packages change.
// TODO: Set up a CI to automatically update those in the future.
const MIN_LIX_VERSION: &str = "2.91.1";
const MIN_NIX_VERSION: &str = "2.24.14";
// Minimum supported versions. Those should generally correspond to
// latest package versions in the stable branch.
//
// Q: Why are you doing this?
// A: First of all to make sure we do not make baseless assumptions
// about the user's system; we should only work around APIs that we
// are fully aware of, and not try to work around every edge case.
// Also, nh should be responsible for nudging the user to use the
// relevant versions of the software it wraps, so that we do not have
// to try and support too many versions. NixOS stable and unstable
// will ALWAYS be supported, but outdated versions will not. If your
// Nix fork uses a different versioning scheme, please open an issue.
let min_version = if is_lix_binary {
MIN_LIX_VERSION
} else {
MIN_NIX_VERSION
};
let current = Version::parse(&version)?;
let required = Version::parse(min_version)?;
match current.cmp(&required) {
Ordering::Less => {
let binary_name = if is_lix_binary { "Lix" } else { "Nix" };
Err(eyre::eyre!(
"{} version {} is too old. Minimum required version is {}",
binary_name,
version,
min_version
))
}
_ => Ok(()),
}
}
/// Verifies if the required experimental features are enabled
///
/// # Returns
///
/// * `Result<()>` - Ok if all required features are enabled, error otherwise
pub fn check_nix_features() -> Result<()> {
if env::var("NH_NO_CHECKS").is_ok() {
return Ok(());
}
let mut required_features = vec!["nix-command", "flakes"];
// Lix still uses repl-flake, which is removed in the latest version of Nix.
if util::is_lix()? {
required_features.push("repl-flake");
}
if !util::has_all_experimental_features(&required_features)? {
return Err(eyre::eyre!(
"Missing required experimental features. Please enable: {}",
required_features.join(", ")
));
}
Ok(())
}
/// Handles environment variable setup and returns if a warning should be shown
///
/// # Returns
///
/// * `Result<bool>` - True if a warning should be shown about the FLAKE variable, false otherwise
pub fn setup_environment() -> Result<bool> {
let mut do_warn = false;
if let Ok(f) = std::env::var("FLAKE") {
// Set NH_FLAKE if it's not already set
if std::env::var("NH_FLAKE").is_err() {
std::env::set_var("NH_FLAKE", f);
// Only warn if FLAKE is set and we're using it to set NH_FLAKE
// AND none of the command-specific env vars are set
if std::env::var("NH_OS_FLAKE").is_err()
&& std::env::var("NH_HOME_FLAKE").is_err()
&& std::env::var("NH_DARWIN_FLAKE").is_err()
{
do_warn = true;
}
}
}
Ok(do_warn)
}
/// Consolidate all necessary checks for Nix functionality into a single function. This
/// will be executed in the main function, but can be executed before critical commands
/// to double-check if necessary.
///
/// # Returns
///
/// * `Result<()>` - Ok if all checks pass, error otherwise
pub fn verify_nix_environment() -> Result<()> {
if env::var("NH_NO_CHECKS").is_ok() {
return Ok(());
}
check_nix_version()?;
check_nix_features()?;
Ok(())
}
// Environment variables are global state, so tests need to be run sequentially.
// Using a mutex to ensure that env var manipulation in one test doesn't affect others.
// Alternatively, run tests with `cargo test -- --test-threads=1`
#[allow(dead_code)] // suppress 'false' positives
static ENV_LOCK: Mutex<()> = Mutex::new(());
// Clean up environment variables set during tests
#[allow(dead_code)]
fn cleanup_env_vars() {
env::remove_var("FLAKE");
env::remove_var("NH_FLAKE");
env::remove_var("NH_OS_FLAKE");
env::remove_var("NH_HOME_FLAKE");
env::remove_var("NH_DARWIN_FLAKE");
env::remove_var("NH_NO_CHECKS");
}
#[test]
fn test_setup_environment_no_flake_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
let should_warn = setup_environment()?;
assert!(!should_warn, "Should not warn when FLAKE is not set");
assert!(env::var("NH_FLAKE").is_err(), "NH_FLAKE should not be set");
cleanup_env_vars();
Ok(())
}
#[test]
fn test_setup_environment_flake_set_no_nh_flake_no_specifics() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("FLAKE", "test_flake_path");
let should_warn = setup_environment()?;
assert!(
should_warn,
"Should warn when FLAKE is set, NH_FLAKE is not, and no specific NH_*_FLAKE vars are set"
);
assert_eq!(
env::var("NH_FLAKE").unwrap(),
"test_flake_path",
"NH_FLAKE should be set from FLAKE"
);
cleanup_env_vars();
Ok(())
}
#[test]
fn test_setup_environment_flake_set_nh_flake_already_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("FLAKE", "test_flake_path");
env::set_var("NH_FLAKE", "existing_nh_flake_path");
let should_warn = setup_environment()?;
assert!(!should_warn, "Should not warn when NH_FLAKE is already set");
assert_eq!(
env::var("NH_FLAKE").unwrap(),
"existing_nh_flake_path",
"NH_FLAKE should retain its original value"
);
cleanup_env_vars();
Ok(())
}
#[test]
fn test_setup_environment_flake_set_no_nh_flake_nh_os_flake_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("FLAKE", "test_flake_path");
env::set_var("NH_OS_FLAKE", "os_specific_flake");
let should_warn = setup_environment()?;
assert!(
!should_warn,
"Should not warn when FLAKE is set, NH_FLAKE is not, but NH_OS_FLAKE is set"
);
assert_eq!(
env::var("NH_FLAKE").unwrap(),
"test_flake_path",
"NH_FLAKE should be set from FLAKE"
);
assert_eq!(
env::var("NH_OS_FLAKE").unwrap(),
"os_specific_flake",
"NH_OS_FLAKE should remain set"
);
cleanup_env_vars();
Ok(())
}
#[test]
fn test_setup_environment_flake_set_no_nh_flake_nh_home_flake_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("FLAKE", "test_flake_path");
env::set_var("NH_HOME_FLAKE", "home_specific_flake");
let should_warn = setup_environment()?;
assert!(
!should_warn,
"Should not warn when FLAKE is set, NH_FLAKE is not, but NH_HOME_FLAKE is set"
);
assert_eq!(
env::var("NH_FLAKE").unwrap(),
"test_flake_path",
"NH_FLAKE should be set from FLAKE"
);
assert_eq!(
env::var("NH_HOME_FLAKE").unwrap(),
"home_specific_flake",
"NH_HOME_FLAKE should remain set"
);
cleanup_env_vars();
Ok(())
}
#[test]
// Greatest function name ever.
// testSetupEnvironmentFlakeSetNoNhFlakeNhDarwinFlakeSetAbstractFactoryBuilder
fn test_setup_environment_flake_set_no_nh_flake_nh_darwin_flake_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("FLAKE", "test_flake_path");
env::set_var("NH_DARWIN_FLAKE", "darwin_specific_flake");
let should_warn = setup_environment()?;
assert!(
!should_warn,
"Should not warn when FLAKE is set, NH_FLAKE is not, but NH_DARWIN_FLAKE is set"
);
assert_eq!(
env::var("NH_FLAKE").unwrap(),
"test_flake_path",
"NH_FLAKE should be set from FLAKE"
);
assert_eq!(
env::var("NH_DARWIN_FLAKE").unwrap(),
"darwin_specific_flake",
"NH_DARWIN_FLAKE should remain set"
);
cleanup_env_vars(); // Clean up after test
Ok(())
}
#[test]
fn test_checks_skip_when_no_checks_set() -> Result<()> {
let _lock = ENV_LOCK.lock().unwrap();
cleanup_env_vars();
env::set_var("NH_NO_CHECKS", "1");
// These should succeed even with invalid environment
let version_check = check_nix_version();
let features_check = check_nix_features();
let verify_check = verify_nix_environment();
assert!(version_check.is_ok(), "Version check should be skipped");
assert!(features_check.is_ok(), "Features check should be skipped");
assert!(verify_check.is_ok(), "Verify check should be skipped");
cleanup_env_vars();
Ok(())
}