Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 111 additions & 1 deletion crates/nix_rs/src/info.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,110 @@
//! Information about the user's Nix installation
use serde::{Deserialize, Serialize};
use std::{fmt, sync::OnceLock};
use tokio::sync::OnceCell;

use crate::{config::NixConfig, env::NixEnv, version::NixVersion};
use crate::{command::NixCmd, config::NixConfig, env::NixEnv, version::NixVersion};
use regex::Regex;

static INSTALLATION_TYPE_PATTERNS: OnceLock<Vec<(Regex, NixInstallationType)>> = OnceLock::new();

/// Type of Nix installation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NixInstallationType {
/// Official Nix installation
Official,
/// Determinate Systems Nix
DeterminateSystems,
}
Copy link
Member

@srid srid Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right direction, but still doesn't quite capture the nix --version parsing as single action; this command is now being run twice now (as can be seen in your screenshot).

This is a big problem with using LLM to generate code. I'd suggest discarding it and rewriting from scratch as iterating further can be a waste of time.


So which type best captures the output of nix --version? Perhaps something like:

data NixVersion = NixVersion_Official VersionSpec | NixVersion_DetSys { detSysVersion :: VersionSpec, nixVersion :: VersionSpec

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a big problem with using LLM to generate code.

A human can do better here. Types should be modelled based that which they are actually modelling, rather than to fit an existing implementation.


impl fmt::Display for NixInstallationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NixInstallationType::Official => write!(f, "official"),
NixInstallationType::DeterminateSystems => write!(f, "determinate-systems"),
}
}
}

impl NixInstallationType {
/// Get or initialize the compiled regex patterns
fn get_patterns() -> &'static Vec<(Regex, NixInstallationType)> {
INSTALLATION_TYPE_PATTERNS.get_or_init(|| {
let pattern_strings = [
(
r"^nix \(Determinate Nix [\d.]+\) (\d+)\.(\d+)\.(\d+)$",
NixInstallationType::DeterminateSystems,
),
(
r"^nix \(Nix\) (\d+)\.(\d+)\.(\d+)$",
NixInstallationType::Official,
),
(r"^(\d+)\.(\d+)\.(\d+)$", NixInstallationType::Official),
];

let mut compiled_patterns = Vec::new();
for (pattern_str, installation_type) in pattern_strings {
// If regex compilation fails, we'll panic at startup which is acceptable
let regex = Regex::new(pattern_str).expect("Invalid regex pattern");
compiled_patterns.push((regex, installation_type));
}
compiled_patterns
})
}

/// Detect installation type from a version string
fn from_version_str(version_str: &str) -> Self {
let patterns = Self::get_patterns();
for (regex, installation_type) in patterns {
if regex.is_match(version_str) {
return *installation_type;
}
}

// Default to Official if no pattern matches
NixInstallationType::Official
}

/// Detect the installation type by examining `nix --version` output
async fn detect() -> Result<Self, NixInfoError> {
let cmd = NixCmd::default();
let output = cmd
.run_with_returning_stdout(&[], |cmd| {
cmd.arg("--version");
})
.await
.map_err(|_| NixInfoError::InstallationTypeDetectionError)?;
let version_str = String::from_utf8_lossy(&output).trim().to_string();

Ok(Self::from_version_str(&version_str))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_installation_type_detection() {
let test_cases = [
("nix (Nix) 2.28.4", NixInstallationType::Official),
(
"nix (Determinate Nix 3.8.5) 2.30.2",
NixInstallationType::DeterminateSystems,
),
("2.28.4", NixInstallationType::Official),
];

for (version_str, expected_type) in test_cases {
let detected_type = NixInstallationType::from_version_str(version_str);
assert_eq!(
detected_type, expected_type,
"Failed for version string: '{}'",
version_str
);
}
}
}

/// All the information about the user's Nix installation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -13,6 +115,8 @@ pub struct NixInfo {
pub nix_config: NixConfig,
/// Environment in which Nix was installed
pub nix_env: NixEnv,
/// Type of Nix installation
pub installation_type: NixInstallationType,
}

static NIX_INFO: OnceCell<Result<NixInfo, NixInfoError>> = OnceCell::const_new();
Expand All @@ -36,10 +140,12 @@ impl NixInfo {
nix_config: NixConfig,
) -> Result<NixInfo, NixInfoError> {
let nix_env = NixEnv::detect().await?;
let installation_type = NixInstallationType::detect().await?;
Ok(NixInfo {
nix_version,
nix_config,
nix_env,
installation_type,
})
}
}
Expand All @@ -62,4 +168,8 @@ pub enum NixInfoError {
/// A [crate::config::NixConfigError]
#[error("Nix config error: {0}")]
NixConfigError(#[from] &'static crate::config::NixConfigError),

/// Installation type detection error
#[error("Failed to detect installation type")]
InstallationTypeDetectionError,
}
30 changes: 25 additions & 5 deletions crates/omnix-health/src/check/flake_enabled.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use nix_rs::info;
use nix_rs::info::{self, NixInstallationType};
use serde::{Deserialize, Serialize};

use crate::traits::*;
Expand All @@ -15,12 +15,32 @@ impl Checkable for FlakeEnabled {
_: Option<&nix_rs::flake::url::FlakeUrl>,
) -> Vec<(&'static str, Check)> {
let val = &nix_info.nix_config.experimental_features.value;

// Check if flakes are enabled either through configuration or installation type
let flakes_enabled = match nix_info.installation_type {
NixInstallationType::DeterminateSystems => {
// Determinate Systems Nix has flakes enabled by default
true
}
NixInstallationType::Official => {
// Official Nix requires explicit configuration
val.contains(&"flakes".to_string()) && val.contains(&"nix-command".to_string())
}
};

let info_msg = match nix_info.installation_type {
NixInstallationType::DeterminateSystems => {
"Determinate Systems Nix (flakes enabled by default)".to_string()
}
NixInstallationType::Official => {
format!("experimental-features = {}", val.join(" "))
}
};

let check = Check {
title: "Flakes Enabled".to_string(),
info: format!("experimental-features = {}", val.join(" ")),
result: if val.contains(&"flakes".to_string())
&& val.contains(&"nix-command".to_string())
{
info: info_msg,
result: if flakes_enabled {
CheckResult::Green
} else {
CheckResult::Red {
Expand Down