Skip to content

Commit 2cfe2cf

Browse files
authored
[nix profile list] parse using --json in new nix version (#1308)
## Summary Problem: Latest nix version of 2.17 changes the output format of `nix profile list` to be: ``` Index: 0 Flake attribute: legacyPackages.aarch64-linux.curl Original flake URL: github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb Locked flake URL: github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb Store paths: /nix/store/3qcvhxxmgcdj6izfs8d3m8csdlsj92ng-curl-8.1.1-bin /nix/store/6skgivna073ziw95xvbksvd5c8n5vi5w-curl-8.1.1-man ``` Previously, the "keys" didn't exist in the output. This broke how we were parsing the output. Fix in pseudo code: ``` try invoke: nix profile list --json if error: do old code: nix profile list ``` the older nix versions don't support --json ## How was it tested? in some devbox projects: ``` # clear old state > rm -rf .devbox > devbox shell # run some commands specific to that project ```
1 parent 8cb1f90 commit 2cfe2cf

File tree

2 files changed

+54
-32
lines changed

2 files changed

+54
-32
lines changed

internal/nix/nixprofile/profile.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package nixprofile
55

66
import (
77
"bufio"
8+
"encoding/json"
89
"fmt"
910
"io"
1011
"strconv"
@@ -14,9 +15,8 @@ import (
1415
"github.com/pkg/errors"
1516
"go.jetpack.io/devbox/internal/boxcli/usererr"
1617
"go.jetpack.io/devbox/internal/devpkg"
17-
"go.jetpack.io/devbox/internal/nix"
18-
1918
"go.jetpack.io/devbox/internal/lock"
19+
"go.jetpack.io/devbox/internal/nix"
2020
"go.jetpack.io/devbox/internal/redact"
2121
)
2222

@@ -26,11 +26,49 @@ func ProfileListItems(
2626
profileDir string,
2727
) (map[string]*NixProfileListItem, error) {
2828

29-
lines, err := nix.ProfileList(writer, profileDir)
29+
output, err := nix.ProfileList(writer, profileDir, true /*useJSON*/)
30+
if err == nil {
31+
type ProfileListElement struct {
32+
Active bool `json:"active"`
33+
AttrPath string `json:"attrPath"`
34+
OriginalURL string `json:"originalUrl"`
35+
Priority int `json:"priority"`
36+
StorePaths []string `json:"storePaths"`
37+
URL string `json:"url"`
38+
}
39+
type ProfileListOutput struct {
40+
Elements []ProfileListElement `json:"elements"`
41+
Version int `json:"version"`
42+
}
43+
44+
var structOutput ProfileListOutput
45+
if err := json.Unmarshal([]byte(output), &structOutput); err != nil {
46+
return nil, err
47+
}
48+
49+
result := map[string]*NixProfileListItem{}
50+
for index, element := range structOutput.Elements {
51+
// We use the unlocked reference as the key, since that is the format
52+
// used for the `nix profile list` output of older nix versions
53+
// (pre 2.17), which our code is designed to support.
54+
unlockedReference := element.OriginalURL + "#" + element.AttrPath
55+
result[unlockedReference] = &NixProfileListItem{
56+
index: index,
57+
unlockedReference: unlockedReference,
58+
lockedReference: element.URL + "#" + element.AttrPath,
59+
nixStorePath: element.StorePaths[0],
60+
}
61+
}
62+
return result, nil
63+
}
64+
65+
output, err = nix.ProfileList(writer, profileDir, false /*useJSON*/)
3066
if err != nil {
31-
return nil, err
67+
return nil, errors.WithStack(err)
3268
}
3369

70+
lines := strings.Split(output, "\n")
71+
3472
// The `line` output is of the form:
3573
// <index> <UnlockedReference> <LockedReference> <NixStorePath>
3674
//
@@ -40,6 +78,10 @@ func ProfileListItems(
4078

4179
items := map[string]*NixProfileListItem{}
4280
for _, line := range lines {
81+
line = strings.TrimSpace(line)
82+
if line == "" {
83+
continue
84+
}
4385
item, err := parseNixProfileListItem(line)
4486
if err != nil {
4587
return nil, err
@@ -133,6 +175,7 @@ func parseNixProfileListItem(line string) (*NixProfileListItem, error) {
133175
if !scanner.Scan() {
134176
return nil, redact.Errorf("error parsing \"nix profile list\" output: line is missing index: %s", line)
135177
}
178+
136179
index, err := strconv.Atoi(scanner.Text())
137180
if err != nil {
138181
return nil, redact.Errorf("error parsing \"nix profile list\" output: %w: %s", err, line)

internal/nix/profiles.go

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package nix
55

66
import (
7-
"bufio"
87
"encoding/json"
98
"fmt"
109
"io"
@@ -17,36 +16,16 @@ import (
1716
"go.jetpack.io/devbox/internal/redact"
1817
)
1918

20-
func ProfileList(writer io.Writer, profilePath string) ([]string, error) {
21-
19+
func ProfileList(writer io.Writer, profilePath string, useJSON bool) (string, error) {
2220
cmd := command("profile", "list", "--profile", profilePath)
23-
cmd.Args = append(cmd.Args, ExperimentalFlags()...)
24-
25-
// We set stderr to a different output than stdout
26-
// to ensure error output is not mingled with the stdout output
27-
// that we need to parse.
28-
cmd.Stderr = writer
29-
30-
out, err := cmd.StdoutPipe()
31-
if err != nil {
32-
return nil, redact.Errorf("error creating stdout pipe: %w", redact.Safe(err))
33-
}
34-
if err := cmd.Start(); err != nil {
35-
return nil, redact.Errorf("error starting \"nix profile list\" command: %w", err)
21+
if useJSON {
22+
cmd.Args = append(cmd.Args, "--json")
3623
}
37-
38-
scanner := bufio.NewScanner(out)
39-
scanner.Split(bufio.ScanLines)
40-
41-
lines := []string{}
42-
for scanner.Scan() {
43-
lines = append(lines, scanner.Text())
44-
}
45-
46-
if err := cmd.Wait(); err != nil {
47-
return nil, redact.Errorf("error running \"nix profile list\": %w", err)
24+
out, err := cmd.Output()
25+
if err != nil {
26+
return "", redact.Errorf("error running \"nix profile list\": %w", err)
4827
}
49-
return lines, nil
28+
return string(out), nil
5029
}
5130

5231
func ProfileInstall(writer io.Writer, profilePath string, installable string) error {

0 commit comments

Comments
 (0)