@@ -25,45 +25,51 @@ import (
25
25
func ProfileListItems (
26
26
writer io.Writer ,
27
27
profileDir string ,
28
- ) (map [ string ]* NixProfileListItem , error ) {
28
+ ) ([ ]* NixProfileListItem , error ) {
29
29
30
30
output , err := nix .ProfileList (writer , profileDir , true /*useJSON*/ )
31
- if err == nil {
32
- type ProfileListElement struct {
33
- Active bool `json:"active"`
34
- AttrPath string `json:"attrPath"`
35
- OriginalURL string `json:"originalUrl"`
36
- Priority int `json:"priority"`
37
- StorePaths []string `json:"storePaths"`
38
- URL string `json:"url"`
39
- }
40
- type ProfileListOutput struct {
41
- Elements []ProfileListElement `json:"elements"`
42
- Version int `json:"version"`
43
- }
31
+ if err != nil {
32
+ // fallback to legacy profile list
33
+ // NOTE: maybe we should check the nix version first, instead of falling back on _any_ error.
34
+ return profileListLegacy (writer , profileDir )
35
+ }
44
36
45
- var structOutput ProfileListOutput
46
- if err := json .Unmarshal ([]byte (output ), & structOutput ); err != nil {
47
- return nil , err
48
- }
37
+ type ProfileListElement struct {
38
+ Active bool `json:"active"`
39
+ AttrPath string `json:"attrPath"`
40
+ OriginalURL string `json:"originalUrl"`
41
+ Priority int `json:"priority"`
42
+ StorePaths []string `json:"storePaths"`
43
+ URL string `json:"url"`
44
+ }
45
+ type ProfileListOutput struct {
46
+ Elements []ProfileListElement `json:"elements"`
47
+ Version int `json:"version"`
48
+ }
49
49
50
- result := map [string ]* NixProfileListItem {}
51
- for index , element := range structOutput .Elements {
52
- // We use the unlocked reference as the key, since that is the format
53
- // used for the `nix profile list` output of older nix versions
54
- // (pre 2.17), which our code is designed to support.
55
- unlockedReference := element .OriginalURL + "#" + element .AttrPath
56
- result [unlockedReference ] = & NixProfileListItem {
57
- index : index ,
58
- unlockedReference : unlockedReference ,
59
- lockedReference : element .URL + "#" + element .AttrPath ,
60
- nixStorePath : element .StorePaths [0 ],
61
- }
62
- }
63
- return result , nil
50
+ var structOutput ProfileListOutput
51
+ if err := json .Unmarshal ([]byte (output ), & structOutput ); err != nil {
52
+ return nil , err
53
+ }
54
+
55
+ items := []* NixProfileListItem {}
56
+ for index , element := range structOutput .Elements {
57
+ items = append (items , & NixProfileListItem {
58
+ index : index ,
59
+ unlockedReference : element .OriginalURL + "#" + element .AttrPath ,
60
+ lockedReference : element .URL + "#" + element .AttrPath ,
61
+ nixStorePath : element .StorePaths [0 ],
62
+ })
64
63
}
64
+ return items , nil
65
+ }
65
66
66
- output , err = nix .ProfileList (writer , profileDir , false /*useJSON*/ )
67
+ // profileListLegacy lists the items in a nix profile before nix 2.17.0 introduced --json.
68
+ func profileListLegacy (
69
+ writer io.Writer ,
70
+ profileDir string ,
71
+ ) ([]* NixProfileListItem , error ) {
72
+ output , err := nix .ProfileList (writer , profileDir , false /*useJSON*/ )
67
73
if err != nil {
68
74
return nil , errors .WithStack (err )
69
75
}
@@ -77,7 +83,7 @@ func ProfileListItems(
77
83
// 0 github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.go_1_19 github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.go_1_19 /nix/store/w0lyimyyxxfl3gw40n46rpn1yjrl3q85-go-1.19.3
78
84
// 1 github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.vim github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.vim /nix/store/gapbqxx1d49077jk8ay38z11wgr12p23-vim-9.0.0609
79
85
80
- items := map [ string ]* NixProfileListItem {}
86
+ items := [ ]* NixProfileListItem {}
81
87
for _ , line := range lines {
82
88
line = strings .TrimSpace (line )
83
89
if line == "" {
@@ -88,83 +94,75 @@ func ProfileListItems(
88
94
return nil , err
89
95
}
90
96
91
- items [ item . unlockedReference ] = item
97
+ items = append ( items , item )
92
98
}
93
99
return items , nil
94
100
}
95
101
96
102
type ProfileListIndexArgs struct {
97
- // For performance you can reuse the same list in multiple operations if you
103
+ // For performance, you can reuse the same list in multiple operations if you
98
104
// are confident index has not changed.
99
- List map [ string ]* NixProfileListItem
105
+ Items [ ]* NixProfileListItem
100
106
Lockfile * lock.File
101
107
Writer io.Writer
102
- Input * devpkg.Package
108
+ Package * devpkg.Package
103
109
ProfileDir string
104
110
}
105
111
112
+ // ProfileListIndex returns the index of args.Package in the nix profile specified by args.ProfileDir,
113
+ // or -1 if it's not found. Callers can pass in args.Items to avoid having to call `nix-profile list` again.
106
114
func ProfileListIndex (args * ProfileListIndexArgs ) (int , error ) {
107
115
var err error
108
- list := args .List
109
- if list == nil {
110
- list , err = ProfileListItems (args .Writer , args .ProfileDir )
116
+ items := args .Items
117
+ if items == nil {
118
+ items , err = ProfileListItems (args .Writer , args .ProfileDir )
111
119
if err != nil {
112
120
return - 1 , err
113
121
}
114
122
}
115
123
116
- inCache , err := args .Input .IsInBinaryCache ()
124
+ inCache , err := args .Package .IsInBinaryCache ()
117
125
if err != nil {
118
126
return - 1 , err
119
127
}
120
128
if inCache {
121
- pathInStore , err := args .Input .Installable ()
129
+ // Packages in cache are added by store path, which means we only need to check
130
+ // for store path equality to find it.
131
+ pathInStore , err := args .Package .Installable ()
122
132
if err != nil {
123
- return - 1 , err
133
+ return - 1 , errors . Wrapf ( err , "failed to get installable for %s" , args . Package . String ())
124
134
}
125
- for _ , item := range list {
135
+ for _ , item := range items {
126
136
if pathInStore == item .nixStorePath {
127
137
return item .index , nil
128
138
}
129
139
}
140
+ return - 1 , errors .Wrap (nix .ErrPackageNotFound , args .Package .String ())
130
141
}
131
- // else: fallback to checking if the Input matches an item's unlockedReference
132
142
133
- // This is an optimization for happy path. A resolved devbox package
134
- // should match the unlockedReference of an existing profile item.
135
- ref , err := args .Input .NormalizedDevboxPackageReference ()
143
+ // else: check if the Package matches an item's unlockedReference.
144
+ // This is an optimization for happy path. A resolved devbox package *which was added by
145
+ // flake reference* (not by store path) should match the unlockedReference of an existing
146
+ // profile item.
147
+ ref , err := args .Package .NormalizedDevboxPackageReference ()
136
148
if err != nil {
137
149
return - 1 , err
138
150
}
139
- if item , found := list [ref ]; found {
140
- return item .index , nil
151
+ for _ , item := range items {
152
+ if ref == item .unlockedReference {
153
+ return item .index , nil
154
+ }
141
155
}
142
156
143
- for _ , item := range list {
157
+ // Still not found? Check for full pkg equality (may be expensive).
158
+ for _ , item := range items {
144
159
existing := item .ToPackage (args .Lockfile )
145
160
146
- if args .Input .Equals (existing ) {
161
+ if args .Package .Equals (existing ) {
147
162
return item .index , nil
148
163
}
149
164
}
150
- return - 1 , errors .Wrap (nix .ErrPackageNotFound , args .Input .String ())
151
- }
152
-
153
- // NixProfileListItem is a go-struct of a line of printed output from `nix profile list`
154
- // docs: https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-profile-list.html
155
- type NixProfileListItem struct {
156
- // An integer that can be used to unambiguously identify the package in
157
- // invocations of nix profile remove and nix profile upgrade.
158
- index int
159
-
160
- // The original ("unlocked") flake reference and output attribute path used at installation time.
161
- unlockedReference string
162
-
163
- // The locked flake reference to which the unlocked flake reference was resolved.
164
- lockedReference string
165
-
166
- // The store path(s) of the package.
167
- nixStorePath string
165
+ return - 1 , errors .Wrap (nix .ErrPackageNotFound , args .Package .String ())
168
166
}
169
167
170
168
// parseNixProfileListItem reads each line of output (from `nix profile list`) and converts
@@ -205,43 +203,6 @@ func parseNixProfileListItem(line string) (*NixProfileListItem, error) {
205
203
}, nil
206
204
}
207
205
208
- // AttributePath parses the package attribute from the NixProfileListItem.lockedReference
209
- //
210
- // For example:
211
- // if NixProfileListItem.lockedReference = github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.go_1_19
212
- // then AttributePath = legacyPackages.x86_64-darwin.go_1_19
213
- func (item * NixProfileListItem ) AttributePath () (string , error ) {
214
- // lockedReference example:
215
- // github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.go_1_19
216
-
217
- // AttributePath example:
218
- // legacyPackages.x86_64.go_1_19
219
- _ /*nixpkgs*/ , attrPath , found := strings .Cut (item .lockedReference , "#" )
220
- if ! found {
221
- return "" , redact .Errorf (
222
- "expected to find # in lockedReference: %s from NixProfileListItem: %s" ,
223
- redact .Safe (item .lockedReference ),
224
- item ,
225
- )
226
- }
227
- return attrPath , nil
228
- }
229
-
230
- // ToPackage constructs a nix.Package using the unlocked reference
231
- func (item * NixProfileListItem ) ToPackage (locker lock.Locker ) * devpkg.Package {
232
- return devpkg .PackageFromString (item .unlockedReference , locker )
233
- }
234
-
235
- // String serializes the NixProfileListItem back into the format printed by `nix profile list`
236
- func (item * NixProfileListItem ) String () string {
237
- return fmt .Sprintf ("%d %s %s %s" ,
238
- item .index ,
239
- item .unlockedReference ,
240
- item .lockedReference ,
241
- item .nixStorePath ,
242
- )
243
- }
244
-
245
206
type ProfileInstallArgs struct {
246
207
CustomStepMessage string
247
208
Lockfile * lock.File
0 commit comments