Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions crates/nix_rs/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use url::Url;

use crate::{
command::{NixCmd, NixCmdError},
version::NixVersion,
version::{NixVersion, VersionSpec},
};

use super::flake::system::System;
Expand Down Expand Up @@ -51,11 +51,11 @@ pub struct ConfigVal<T> {

static NIX_CONFIG: OnceCell<Result<NixConfig, NixConfigError>> = OnceCell::const_new();

static NIX_2_20_0: NixVersion = NixVersion {
static NIX_2_20_0: NixVersion = NixVersion::Official(VersionSpec {
major: 2,
minor: 20,
patch: 0,
};
});

impl NixConfig {
/// Get the once version of `NixConfig`.
Expand Down
13 changes: 12 additions & 1 deletion crates/nix_rs/src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
use serde::{Deserialize, Serialize};
use tokio::sync::OnceCell;

use crate::{config::NixConfig, env::NixEnv, version::NixVersion};
use crate::{
config::NixConfig,
env::NixEnv,
version::{NixInstallationType, NixVersion},
};

/// All the information about the user's Nix installation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -15,6 +19,13 @@ pub struct NixInfo {
pub nix_env: NixEnv,
}

impl NixInfo {
/// Get the installation type (derived from nix_version)
pub fn installation_type(&self) -> NixInstallationType {
self.nix_version.installation_type()
}
}

static NIX_INFO: OnceCell<Result<NixInfo, NixInfoError>> = OnceCell::const_new();

impl NixInfo {
Expand Down
198 changes: 171 additions & 27 deletions crates/nix_rs/src/version.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
//! Rust module for `nix --version`
use regex::Regex;
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{fmt, str::FromStr};
use std::{fmt, str::FromStr, sync::LazyLock};
use thiserror::Error;
use tokio::sync::OnceCell;

use tracing::instrument;

use crate::command::{NixCmd, NixCmdError};

/// Nix version as parsed from `nix --version`
#[derive(Clone, Copy, PartialOrd, PartialEq, Eq, Debug, SerializeDisplay, DeserializeFromStr)]
pub struct NixVersion {
/// Simple version triple (major.minor.patch)
#[derive(Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Debug)]
pub struct VersionSpec {
/// Major version
pub major: u32,
/// Minor version
Expand All @@ -20,6 +20,82 @@ pub struct NixVersion {
pub patch: u32,
}

/// Nix version as parsed from `nix --version`, capturing both version and installation type
#[derive(Clone, Copy, Debug, SerializeDisplay, DeserializeFromStr)]
pub enum NixVersion {
/// Official Nix installation: "nix (Nix) 2.28.4" or "2.28.4"
Official(VersionSpec),
/// Determinate Systems Nix: "nix (Determinate Nix 3.8.5) 2.30.2"
DeterminateSystems {
/// The Determinate Systems version (e.g., 3.8.5)
det_sys_version: VersionSpec,
/// The underlying Nix version (e.g., 2.30.2)
nix_version: VersionSpec,
},
}

impl NixVersion {
/// Get the effective Nix version (the actual Nix version being used)
pub fn nix_version(&self) -> VersionSpec {
match self {
NixVersion::Official(version) => *version,
NixVersion::DeterminateSystems { nix_version, .. } => *nix_version,
}
}

/// Check if this is a Determinate Systems installation
pub fn is_determinate_systems(&self) -> bool {
matches!(self, NixVersion::DeterminateSystems { .. })
}

/// Get the installation type
pub fn installation_type(&self) -> NixInstallationType {
match self {
NixVersion::Official(_) => NixInstallationType::Official,
NixVersion::DeterminateSystems { .. } => NixInstallationType::DeterminateSystems,
}
}
}

impl PartialEq for NixVersion {
fn eq(&self, other: &Self) -> bool {
self.nix_version() == other.nix_version()
}
}

impl Eq for NixVersion {}

#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for NixVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for NixVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.nix_version().cmp(&other.nix_version())
}
}

/// Type of Nix installation (derived from NixVersion)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NixInstallationType {
/// Official Nix installation
Official,
/// Determinate Systems Nix
DeterminateSystems,
}

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

/// Error type for parsing `nix --version`
#[derive(Error, Debug, Clone, PartialEq)]
pub enum BadNixVersion {
Expand All @@ -36,25 +112,58 @@ pub enum BadNixVersion {
Command,
}

impl VersionSpec {
/// Parse a version string like "2.28.4" into a VersionSpec
fn parse_version_string(s: &str) -> Result<Self, BadNixVersion> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(BadNixVersion::Command);
}

let major = parts[0].parse::<u32>()?;
let minor = parts[1].parse::<u32>()?;
let patch = parts[2].parse::<u32>()?;

Ok(VersionSpec {
major,
minor,
patch,
})
}
}

impl std::fmt::Display for VersionSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

static DET_SYS_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^nix \(Determinate Nix ([\d.]+)\) ([\d.]+)$").unwrap());
static OFFICIAL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(?:nix \(Nix\) )?([\d.]+)$").unwrap());

impl FromStr for NixVersion {
type Err = BadNixVersion;

/// Parse the string output of `nix --version` into a [NixVersion]
fn from_str(s: &str) -> Result<Self, Self::Err> {
// NOTE: The parser is lenient in allowing pure nix version (produced
// by [Display] instance), so as to work with serde_with instances.
let re = Regex::new(r"(?:nix \(Nix\) )?(\d+)\.(\d+)\.(\d+)$")?;
// Try to match Determinate Systems format: "nix (Determinate Nix 3.8.5) 2.30.2"
if let Some(captures) = DET_SYS_REGEX.captures(s) {
return Ok(NixVersion::DeterminateSystems {
det_sys_version: VersionSpec::parse_version_string(&captures[1])?,
nix_version: VersionSpec::parse_version_string(&captures[2])?,
});
}

let captures = re.captures(s).ok_or(BadNixVersion::Command)?;
let major = captures[1].parse::<u32>()?;
let minor = captures[2].parse::<u32>()?;
let patch = captures[3].parse::<u32>()?;
// Try to match official format: "nix (Nix) 2.28.4" or plain format: "2.28.4"
if let Some(captures) = OFFICIAL_REGEX.captures(s) {
return Ok(NixVersion::Official(VersionSpec::parse_version_string(
&captures[1],
)?));
}

Ok(NixVersion {
major,
minor,
patch,
})
Err(BadNixVersion::Command)
}
}

Expand Down Expand Up @@ -84,7 +193,10 @@ impl NixVersion {
/// The String view for [NixVersion]
impl fmt::Display for NixVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
match self {
NixVersion::Official(version) => write!(f, "{}", version),
NixVersion::DeterminateSystems { nix_version, .. } => write!(f, "{}", nix_version),
}
}
}

Expand All @@ -96,32 +208,64 @@ async fn test_run_nix_version() {

#[tokio::test]
async fn test_parse_nix_version() {
// Test official Nix format
assert_eq!(
NixVersion::from_str("nix (Nix) 2.13.0"),
Ok(NixVersion {
Ok(NixVersion::Official(VersionSpec {
major: 2,
minor: 13,
patch: 0
})
}))
);

// Parse simple nix version
// Test simple version format (treated as official)
assert_eq!(
NixVersion::from_str("2.13.0"),
Ok(NixVersion {
Ok(NixVersion::Official(VersionSpec {
major: 2,
minor: 13,
patch: 0
})
}))
);

// Parse Determinate Nix Version
// Test Determinate Systems format
assert_eq!(
NixVersion::from_str("nix (Determinate Nix 3.6.6) 2.29.0"),
Ok(NixVersion {
major: 2,
minor: 29,
patch: 0
Ok(NixVersion::DeterminateSystems {
det_sys_version: VersionSpec {
major: 3,
minor: 6,
patch: 6
},
nix_version: VersionSpec {
major: 2,
minor: 29,
patch: 0
}
})
);

// Test installation type detection
let official = NixVersion::from_str("nix (Nix) 2.28.4").unwrap();
assert_eq!(official.installation_type(), NixInstallationType::Official);
assert!(!official.is_determinate_systems());

let det_sys = NixVersion::from_str("nix (Determinate Nix 3.8.5) 2.30.2").unwrap();
assert_eq!(
det_sys.installation_type(),
NixInstallationType::DeterminateSystems
);
assert!(det_sys.is_determinate_systems());

// Test version comparison between Official and DeterminateSystems
let official_v2_30 = NixVersion::from_str("2.30.0").unwrap();
let det_sys_v2_30 = NixVersion::from_str("nix (Determinate Nix 3.8.5) 2.30.0").unwrap();
let official_v2_28 = NixVersion::from_str("2.28.0").unwrap();

// Same underlying Nix version should be equal
assert_eq!(official_v2_30, det_sys_v2_30);

// Both should be greater than older version
assert!(official_v2_30 > official_v2_28);
assert!(det_sys_v2_30 > official_v2_28);
}
14 changes: 7 additions & 7 deletions crates/nix_rs/src/version_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use thiserror::Error;

use crate::version::NixVersion;
use crate::version::{NixVersion, VersionSpec};

/// An individual component of [NixVersionReq]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -119,11 +119,11 @@ impl FromStr for NixVersionSpec {
.name("patch")
.map_or(Ok(0), |m| m.as_str().parse::<u32>())?;

let nix_version = NixVersion {
let nix_version = NixVersion::Official(VersionSpec {
major,
minor,
patch,
};
});

match op {
">=" => Ok(Gteq(nix_version)),
Expand Down Expand Up @@ -170,19 +170,19 @@ mod tests {
fn test_parse() {
assert_eq!(
NixVersionSpec::from_str(">2.8").unwrap(),
NixVersionSpec::Gt(NixVersion {
NixVersionSpec::Gt(NixVersion::Official(VersionSpec {
major: 2,
minor: 8,
patch: 0
})
}))
);
assert_eq!(
NixVersionSpec::from_str(">2").unwrap(),
NixVersionSpec::Gt(NixVersion {
NixVersionSpec::Gt(NixVersion::Official(VersionSpec {
major: 2,
minor: 0,
patch: 0
})
}))
);
}

Expand Down
Loading