Skip to content

Commit 4bd4b42

Browse files
SapphicCodegcurtis
andauthored
internal/nix: add support for Nix forks (#2091)
Increases leniency for `nix --version` output to support Nix forks, such as Lix: nix (Lix, like Nix) 2.90.0-beta.1-lixpre20240506-b6799ab Co-authored-by: Greg Curtis <[email protected]>
1 parent b95f4cd commit 4bd4b42

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

internal/nix/nix.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ const (
191191
MinVersion = Version2_12
192192
)
193193

194+
// versionRegexp matches the first line of "nix --version" output.
195+
// Semantic component sourced from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
196+
var versionRegexp = regexp.MustCompile(`^(.+) \(.+\) ((?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?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-]+)*))?)$`)
197+
194198
// VersionInfo contains information about a Nix installation.
195199
type VersionInfo struct {
196200
// Name is the executed program name (the first element of argv).
@@ -252,11 +256,12 @@ func parseVersionInfo(data []byte) (VersionInfo, error) {
252256
}
253257

254258
lines := strings.Split(string(data), "\n")
255-
found := false
256-
info.Name, info.Version, found = strings.Cut(lines[0], " (Nix) ")
257-
if !found {
259+
matches := versionRegexp.FindStringSubmatch(lines[0])
260+
if len(matches) < 3 {
258261
return info, redact.Errorf("parse nix version: %s", redact.Safe(lines[0]))
259262
}
263+
info.Name = matches[1]
264+
info.Version = matches[2]
260265
for _, line := range lines {
261266
name, value, found := strings.Cut(line, ": ")
262267
if !found {

internal/nix/nix_test.go

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//nolint:dupl
12
package nix
23

34
import (
@@ -118,17 +119,79 @@ Data directory: /nix/store/m0ns07v8by0458yp6k30rfq1rs3kaz6g-nix-2.21.2/share
118119
}
119120
}
120121

121-
func TestParseVersionInfoShort(t *testing.T) {
122-
info, err := parseVersionInfo([]byte("nix (Nix) 2.21.2"))
122+
func TestParseLixVersionInfo(t *testing.T) {
123+
raw := `nix (Lix, like Nix) 2.90.0-beta.1
124+
System type: aarch64-darwin
125+
Additional system types: x86_64-darwin
126+
Features: gc, signed-caches
127+
System configuration file: /etc/nix/nix.conf
128+
User configuration files: /Users/nobody/.config/nix/nix.conf:/etc/xdg/nix/nix.conf
129+
Store directory: /nix/store
130+
State directory: /nix/var/nix
131+
Data directory: /nix/store/12asl5a17ffj78njcy2fj31v59rdmanx-lix-2.90-beta.1/share
132+
`
133+
134+
info, err := parseVersionInfo([]byte(raw))
123135
if err != nil {
124136
t.Error("got parse error:", err)
125137
}
126138
if got, want := info.Name, "nix"; got != want {
127139
t.Errorf("got Name = %q, want %q", got, want)
128140
}
129-
if got, want := info.Version, "2.21.2"; got != want {
141+
if got, want := info.Version, "2.90.0-beta.1"; got != want {
130142
t.Errorf("got Version = %q, want %q", got, want)
131143
}
144+
if got, want := info.System, "aarch64-darwin"; got != want {
145+
t.Errorf("got System = %q, want %q", got, want)
146+
}
147+
if got, want := info.ExtraSystems, []string{"x86_64-darwin"}; !slices.Equal(got, want) {
148+
t.Errorf("got ExtraSystems = %q, want %q", got, want)
149+
}
150+
if got, want := info.Features, []string{"gc", "signed-caches"}; !slices.Equal(got, want) {
151+
t.Errorf("got Features = %q, want %q", got, want)
152+
}
153+
if got, want := info.SystemConfig, "/etc/nix/nix.conf"; got != want {
154+
t.Errorf("got SystemConfig = %q, want %q", got, want)
155+
}
156+
if got, want := info.UserConfigs, []string{"/Users/nobody/.config/nix/nix.conf", "/etc/xdg/nix/nix.conf"}; !slices.Equal(got, want) {
157+
t.Errorf("got UserConfigs = %q, want %q", got, want)
158+
}
159+
if got, want := info.StoreDir, "/nix/store"; got != want {
160+
t.Errorf("got StoreDir = %q, want %q", got, want)
161+
}
162+
if got, want := info.StateDir, "/nix/var/nix"; got != want {
163+
t.Errorf("got StateDir = %q, want %q", got, want)
164+
}
165+
if got, want := info.DataDir, "/nix/store/12asl5a17ffj78njcy2fj31v59rdmanx-lix-2.90-beta.1/share"; got != want {
166+
t.Errorf("got DataDir = %q, want %q", got, want)
167+
}
168+
}
169+
170+
func TestParseVersionInfoShort(t *testing.T) {
171+
cases := []struct {
172+
in string
173+
name string
174+
version string
175+
}{
176+
{"nix (Nix) 2.21.2", "nix", "2.21.2"},
177+
{"command (Nix) name (Nix) 2.21.2", "command (Nix) name", "2.21.2"},
178+
{"nix (Lix, like Nix) 2.90.0-beta.1", "nix", "2.90.0-beta.1"},
179+
}
180+
181+
for _, tt := range cases {
182+
t.Run(tt.in, func(t *testing.T) {
183+
got, err := parseVersionInfo([]byte(tt.in))
184+
if err != nil {
185+
t.Error("got parse error:", err)
186+
}
187+
if got.Name != tt.name {
188+
t.Errorf("got Name = %q, want %q", got.Name, tt.name)
189+
}
190+
if got.Version != tt.version {
191+
t.Errorf("got Version = %q, want %q", got.Version, tt.version)
192+
}
193+
})
194+
}
132195
}
133196

134197
func TestParseVersionInfoError(t *testing.T) {

0 commit comments

Comments
 (0)