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
16 changes: 14 additions & 2 deletions nix/nix.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,20 @@ const (
//
// The semantic component is sourced from <https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string>.
// It's been modified to tolerate Nix prerelease versions, which don't have a
// hyphen before the prerelease component and contain underscores.
var versionRegexp = regexp.MustCompile(`^(.+) \(.+\) ((?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:(?:-|pre)(?P<prerelease>(?:0|[1-9]\d*|\d*[_a-zA-Z-][_0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[_a-zA-Z-][_0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$`)
// hyphen before the prerelease component and contain underscores. The patch
// component is optional because some Nix prereleases omit it (for example,
// 2.33pre20251107_479b6b73).
var versionRegexp = regexp.MustCompile(`^(.+) \(.+\) ((?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)(?:\.(?P<patch>0|[1-9]\d*))?(?:(?:-|pre)(?P<prerelease>(?:0|[1-9]\d*|\d*[_a-zA-Z-][_0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[_a-zA-Z-][_0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$`)

// preReleaseRegexp matches Nix prerelease version strings, which are not valid
// semvers.
var preReleaseRegexp = regexp.MustCompile(`pre(?P<date>[0-9]+)_(?P<commit>[a-f0-9]{4,40})$`)

// missingPatchRegexp matches a leading major.minor version that has no patch
// component (for example, the "2.33" in "2.33-pre.20251107+479b6b73"). It's
// used to insert a ".0" patch so the version is a valid semver.
var missingPatchRegexp = regexp.MustCompile(`^(\d+\.\d+)(-|\+|$)`)

// Info contains information about a Nix installation.
type Info struct {
// Name identifies the Nix implementation. It is usually "nix" but may
Expand Down Expand Up @@ -303,6 +310,11 @@ func (i Info) AtLeast(version string) bool {
// prerelease (e.g., 2.23.0pre20240526_7de033d6) and coerce it to a
// valid version (2.23.0-pre.20240526+7de033d6) so we can compare it.
prerelease := preReleaseRegexp.ReplaceAllString(i.Version, "-pre.$date+$commit")
// Some Nix prereleases omit the patch component (e.g.,
// 2.33pre20251107_479b6b73 -> 2.33-pre.20251107+479b6b73).
// golang.org/x/mod/semver won't parse a prerelease without an explicit
// patch component, so insert ".0".
prerelease = missingPatchRegexp.ReplaceAllString(prerelease, "${1}.0${2}")
return semver.Compare("v"+prerelease, version) >= 0
}

Expand Down
22 changes: 22 additions & 0 deletions nix/nix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ func TestParseVersionInfoShort(t *testing.T) {
{"nix (Nix) 2.23.0pre20240526_7de033d6", "nix", "2.23.0pre20240526_7de033d6"},
{"command (Nix) name (Nix) 2.21.2", "command (Nix) name", "2.21.2"},
{"nix (Lix, like Nix) 2.90.0-beta.1", "nix", "2.90.0-beta.1"},
// Some Nix prereleases omit the patch component.
// https://github.com/jetify-com/devbox/issues/2766
{"nix (Nix) 2.33pre20251107_479b6b73", "nix", "2.33pre20251107_479b6b73"},
}

for _, tt := range cases {
Expand Down Expand Up @@ -186,6 +189,25 @@ func TestVersionInfoAtLeast(t *testing.T) {
t.Errorf("got %s < %s", info.Version, "2.23.0-pre.1")
}

// Nix prereleases that omit the patch component must still compare.
// https://github.com/jetify-com/devbox/issues/2766
info.Version = "2.33pre20251107_479b6b73"
if !info.AtLeast(Version2_12) {
t.Errorf("got %s < %s", info.Version, Version2_12)
}
if !info.AtLeast(MinVersion) {
t.Errorf("got %s < %s", info.Version, MinVersion)
}
if !info.AtLeast("2.33.0-pre.1") {
t.Errorf("got %s < %s", info.Version, "2.33.0-pre.1")
}
if info.AtLeast("2.33.0") {
t.Errorf("got %s >= %s", info.Version, "2.33.0")
}
if info.AtLeast("2.34.0") {
t.Errorf("got %s >= %s", info.Version, "2.34.0")
}

t.Run("ArgEmptyPanic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down
Loading