Skip to content

Commit 136ed2e

Browse files
committed
nh: move checks to a dedicated module; validate nix setup
1 parent 990e60c commit 136ed2e

File tree

3 files changed

+164
-40
lines changed

3 files changed

+164
-40
lines changed

src/check.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use std::cmp::Ordering;
2+
3+
use color_eyre::{eyre, Result};
4+
use semver::Version;
5+
6+
use crate::util;
7+
8+
/// Verifies if the installed Nix version meets requirements
9+
///
10+
/// # Returns
11+
///
12+
/// * `Result<()>` - Ok if version requirements are met, error otherwise
13+
pub fn check_nix_version() -> Result<()> {
14+
let version = util::get_nix_version()?;
15+
let is_lix_binary = util::is_lix()?;
16+
17+
let min_version = if is_lix_binary { "2.91.0" } else { "2.26.1" };
18+
19+
let current = Version::parse(&version)?;
20+
let required = Version::parse(min_version)?;
21+
22+
match current.cmp(&required) {
23+
Ordering::Less => {
24+
let binary_name = if is_lix_binary { "Lix" } else { "Nix" };
25+
Err(eyre::eyre!(
26+
"{} version {} is too old. Minimum required version is {}",
27+
binary_name,
28+
version,
29+
min_version
30+
))
31+
}
32+
_ => Ok(()),
33+
}
34+
}
35+
36+
/// Verifies if the required experimental features are enabled
37+
///
38+
/// # Returns
39+
///
40+
/// * `Result<()>` - Ok if all required features are enabled, error otherwise
41+
pub fn check_nix_features() -> Result<()> {
42+
let mut required_features = vec!["nix-command", "flakes"];
43+
44+
// Lix still uses repl-flake, which is removed in the latest version of Nix.
45+
if util::is_lix()? {
46+
required_features.push("repl-flake");
47+
}
48+
49+
if !util::has_all_experimental_features(&required_features)? {
50+
return Err(eyre::eyre!(
51+
"Missing required experimental features. Please enable: {}",
52+
required_features.join(", ")
53+
));
54+
}
55+
56+
Ok(())
57+
}
58+
59+
/// Handles environment variable setup and returns if a warning should be shown
60+
///
61+
/// # Returns
62+
///
63+
/// * `Result<bool>` - True if a warning should be shown about the FLAKE variable, false otherwise
64+
pub fn setup_environment() -> Result<bool> {
65+
let mut do_warn = false;
66+
67+
if let Ok(f) = std::env::var("FLAKE") {
68+
// Set NH_FLAKE if it's not already set
69+
if std::env::var("NH_FLAKE").is_err() {
70+
std::env::set_var("NH_FLAKE", f);
71+
72+
// Only warn if FLAKE is set and we're using it to set NH_FLAKE
73+
// AND none of the command-specific env vars are set
74+
if std::env::var("NH_OS_FLAKE").is_err()
75+
&& std::env::var("NH_HOME_FLAKE").is_err()
76+
&& std::env::var("NH_DARWIN_FLAKE").is_err()
77+
{
78+
do_warn = true;
79+
}
80+
}
81+
}
82+
83+
Ok(do_warn)
84+
}
85+
86+
/// Runs all necessary checks for Nix functionality
87+
///
88+
/// # Returns
89+
///
90+
/// * `Result<()>` - Ok if all checks pass, error otherwise
91+
pub fn verify_nix_environment() -> Result<()> {
92+
check_nix_version()?;
93+
check_nix_features()?;
94+
Ok(())
95+
}

src/main.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod check;
12
mod clean;
23
mod commands;
34
mod completion;
@@ -20,22 +21,7 @@ const NH_VERSION: &str = env!("CARGO_PKG_VERSION");
2021
const NH_REV: Option<&str> = option_env!("NH_REV");
2122

2223
fn main() -> Result<()> {
23-
let mut do_warn = false;
24-
if let Ok(f) = std::env::var("FLAKE") {
25-
// Set NH_FLAKE if it's not already set
26-
if std::env::var("NH_FLAKE").is_err() {
27-
std::env::set_var("NH_FLAKE", f);
28-
29-
// Only warn if FLAKE is set and we're using it to set NH_FLAKE
30-
// AND none of the command-specific env vars are set
31-
if std::env::var("NH_OS_FLAKE").is_err()
32-
&& std::env::var("NH_HOME_FLAKE").is_err()
33-
&& std::env::var("NH_DARWIN_FLAKE").is_err()
34-
{
35-
do_warn = true;
36-
}
37-
}
38-
}
24+
let do_warn = check::setup_environment()?;
3925

4026
let args = <crate::interface::Main as clap::Parser>::parse();
4127
crate::logging::setup_logging(args.verbose)?;
@@ -48,9 +34,13 @@ fn main() -> Result<()> {
4834
);
4935
}
5036

37+
// Verify the Nix environment before running commands
38+
check::verify_nix_environment()?;
39+
5140
args.command.run()
5241
}
5342

43+
/// Self-elevates the current process by re-executing it with sudo
5444
fn self_elevate() -> ! {
5545
use std::os::unix::process::CommandExt;
5646

src/util.rs

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,11 @@
1-
extern crate semver;
2-
1+
use std::collections::HashSet;
32
use std::path::{Path, PathBuf};
43
use std::process::Command;
54
use std::str;
65

76
use color_eyre::{eyre, Result};
8-
use semver::Version;
97
use tempfile::TempDir;
108

11-
/// Compares two semantic versions and returns their order.
12-
///
13-
/// This function takes two version strings, parses them into `semver::Version` objects, and compares them.
14-
/// It returns an `Ordering` indicating whether the current version is less than, equal to, or
15-
/// greater than the target version.
16-
///
17-
/// # Arguments
18-
///
19-
/// * `current` - A string slice representing the current version.
20-
/// * `target` - A string slice representing the target version to compare against.
21-
///
22-
/// # Returns
23-
///
24-
/// * `Result<std::cmp::Ordering>` - The comparison result.
25-
pub fn compare_semver(current: &str, target: &str) -> Result<std::cmp::Ordering> {
26-
let current = Version::parse(current)?;
27-
let target = Version::parse(target)?;
28-
29-
Ok(current.cmp(&target))
30-
}
31-
329
/// Retrieves the installed Nix version as a string.
3310
///
3411
/// This function executes the `nix --version` command, parses the output to extract the version string,
@@ -59,6 +36,19 @@ pub fn get_nix_version() -> Result<String> {
5936
Err(eyre::eyre!("Failed to extract version"))
6037
}
6138

39+
/// Determines if the Nix binary is actually Lix
40+
///
41+
/// # Returns
42+
///
43+
/// * `Result<bool>` - True if the binary is Lix, false if it's standard Nix
44+
pub fn is_lix() -> Result<bool> {
45+
let output = Command::new("nix").arg("--version").output()?;
46+
let output_str = str::from_utf8(&output.stdout)?.to_lowercase();
47+
48+
Ok(output_str.contains("lix"))
49+
}
50+
51+
/// Represents an object that may be a temporary path
6252
pub trait MaybeTempPath: std::fmt::Debug {
6353
fn get_path(&self) -> &Path;
6454
}
@@ -75,6 +65,11 @@ impl MaybeTempPath for (PathBuf, TempDir) {
7565
}
7666
}
7767

68+
/// Gets the hostname of the current system
69+
///
70+
/// # Returns
71+
///
72+
/// * `Result<String>` - The hostname as a string or an error
7873
pub fn get_hostname() -> Result<String> {
7974
#[cfg(not(target_os = "macos"))]
8075
{
@@ -102,3 +97,47 @@ pub fn get_hostname() -> Result<String> {
10297
Ok(name.to_string())
10398
}
10499
}
100+
101+
/// Retrieves all enabled experimental features in Nix.
102+
///
103+
/// This function executes the `nix config show experimental-features` command and returns
104+
/// a HashSet of the enabled features.
105+
///
106+
/// # Returns
107+
///
108+
/// * `Result<HashSet<String>>` - A HashSet of enabled experimental features or an error.
109+
pub fn get_nix_experimental_features() -> Result<HashSet<String>> {
110+
let output = Command::new("nix")
111+
.args(["config", "show", "experimental-features"])
112+
.output()?;
113+
114+
if !output.status.success() {
115+
return Err(eyre::eyre!(
116+
"Failed to get experimental features: {}",
117+
String::from_utf8_lossy(&output.stderr)
118+
));
119+
}
120+
121+
let output_str = str::from_utf8(&output.stdout)?;
122+
let enabled_features: HashSet<String> =
123+
output_str.split_whitespace().map(String::from).collect();
124+
125+
Ok(enabled_features)
126+
}
127+
128+
/// Checks if all specified experimental features are enabled in Nix.
129+
///
130+
/// # Arguments
131+
///
132+
/// * `features` - A slice of string slices representing the features to check for.
133+
///
134+
/// # Returns
135+
///
136+
/// * `Result<bool>` - True if all specified features are enabled, false otherwise.
137+
pub fn has_all_experimental_features(features: &[&str]) -> Result<bool> {
138+
let enabled_features = get_nix_experimental_features()?;
139+
let features_set: HashSet<String> = features.iter().map(|&s| s.to_string()).collect();
140+
141+
// Check if features_set is a subset of enabled_features
142+
Ok(features_set.is_subset(&enabled_features))
143+
}

0 commit comments

Comments
 (0)