@@ -16,15 +16,13 @@ import (
16
16
"runtime"
17
17
"runtime/trace"
18
18
"strings"
19
- "sync"
20
19
"time"
21
20
22
21
"github.com/pkg/errors"
23
22
"go.jetpack.io/devbox/internal/boxcli/featureflag"
24
23
"go.jetpack.io/devbox/internal/boxcli/usererr"
25
24
"go.jetpack.io/devbox/internal/redact"
26
25
"go.jetpack.io/devbox/nix/flake"
27
- "golang.org/x/mod/semver"
28
26
29
27
"go.jetpack.io/devbox/internal/debug"
30
28
)
@@ -51,7 +49,7 @@ type PrintDevEnvArgs struct {
51
49
52
50
// PrintDevEnv calls `nix print-dev-env -f <path>` and returns its output. The output contains
53
51
// all the environment variables and bash functions required to create a nix shell.
54
- func (* Nix ) PrintDevEnv (ctx context.Context , args * PrintDevEnvArgs ) (* PrintDevEnvOut , error ) {
52
+ func (* NixInstance ) PrintDevEnv (ctx context.Context , args * PrintDevEnvArgs ) (* PrintDevEnvOut , error ) {
55
53
defer debug .FunctionTimer ().End ()
56
54
defer trace .StartRegion (ctx , "nixPrintDevEnv" ).End ()
57
55
@@ -128,226 +126,10 @@ func ExperimentalFlags() []string {
128
126
}
129
127
}
130
128
131
- func System () string {
132
- if cachedSystem == "" {
133
- // While this should have been initialized, we do a best-effort to avoid
134
- // a panic.
135
- if err := ComputeSystem (); err != nil {
136
- panic (fmt .Sprintf (
137
- "System called before being initialized by ComputeSystem: %v" ,
138
- err ,
139
- ))
140
- }
141
- }
142
- return cachedSystem
143
- }
144
-
145
- var cachedSystem string
146
-
147
- func ComputeSystem () error {
148
- // For Savil to debug "remove nixpkgs" feature. The Search api lacks x86-darwin info.
149
- // So, I need to fake that I am x86-linux and inspect the output in generated devbox.lock
150
- // and flake.nix files.
151
- // This is also used by unit tests.
152
- if cachedSystem != "" {
153
- return nil
154
- }
155
- override := os .Getenv ("__DEVBOX_NIX_SYSTEM" )
156
- if override != "" {
157
- cachedSystem = override
158
- } else {
159
- cmd := command ("eval" , "--impure" , "--raw" , "--expr" , "builtins.currentSystem" )
160
- out , err := cmd .Output (context .TODO ())
161
- if err != nil {
162
- return err
163
- }
164
- cachedSystem = string (out )
165
- }
166
- return nil
167
- }
168
-
169
129
func SystemIsLinux () bool {
170
130
return strings .Contains (System (), "linux" )
171
131
}
172
132
173
- // All major Nix versions supported by Devbox.
174
- const (
175
- Version2_12 = "2.12.0"
176
- Version2_13 = "2.13.0"
177
- Version2_14 = "2.14.0"
178
- Version2_15 = "2.15.0"
179
- Version2_16 = "2.16.0"
180
- Version2_17 = "2.17.0"
181
- Version2_18 = "2.18.0"
182
- Version2_19 = "2.19.0"
183
- Version2_20 = "2.20.0"
184
- Version2_21 = "2.21.0"
185
- Version2_22 = "2.22.0"
186
-
187
- MinVersion = Version2_12
188
- )
189
-
190
- // versionRegexp matches the first line of "nix --version" output.
191
- //
192
- // The semantic component is sourced from <https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string>.
193
- // It's been modified to tolerate Nix prerelease versions, which don't have a
194
- // hyphen before the prerelease component and contain underscores.
195
- 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-]+)*))?)$` )
196
-
197
- // preReleaseRegexp matches Nix prerelease version strings, which are not valid
198
- // semvers.
199
- var preReleaseRegexp = regexp .MustCompile (`pre(?P<date>[0-9]+)_(?P<commit>[a-f0-9]{4,40})$` )
200
-
201
- // VersionInfo contains information about a Nix installation.
202
- type VersionInfo struct {
203
- // Name is the executed program name (the first element of argv).
204
- Name string
205
-
206
- // Version is the semantic Nix version string.
207
- Version string
208
-
209
- // System is the current Nix system. It follows the pattern <arch>-<os>
210
- // and does not use the same values as GOOS or GOARCH.
211
- System string
212
-
213
- // ExtraSystems are other systems that the current machine supports.
214
- // Usually set by the extra-platforms setting in nix.conf.
215
- ExtraSystems []string
216
-
217
- // Features are the capabilities that the Nix binary was compiled with.
218
- Features []string
219
-
220
- // SystemConfig is the path to the Nix system configuration file,
221
- // usually /etc/nix/nix.conf.
222
- SystemConfig string
223
-
224
- // UserConfigs is a list of paths to the user's Nix configuration files.
225
- UserConfigs []string
226
-
227
- // StoreDir is the path to the Nix store directory, usually /nix/store.
228
- StoreDir string
229
-
230
- // StateDir is the path to the Nix state directory, usually
231
- // /nix/var/nix.
232
- StateDir string
233
-
234
- // DataDir is the path to the Nix data directory, usually somewhere
235
- // within the Nix store. This field is empty for Nix versions <= 2.12.
236
- DataDir string
237
- }
238
-
239
- func parseVersionInfo (data []byte ) (VersionInfo , error ) {
240
- // Example nix --version --debug output from Nix versions 2.12 to 2.21.
241
- // Version 2.12 omits the data directory, but they're otherwise
242
- // identical.
243
- //
244
- // See https://github.com/NixOS/nix/blob/5b9cb8b3722b85191ee8cce8f0993170e0fc234c/src/libmain/shared.cc#L284-L305
245
- //
246
- // nix (Nix) 2.21.2
247
- // System type: aarch64-darwin
248
- // Additional system types: x86_64-darwin
249
- // Features: gc, signed-caches
250
- // System configuration file: /etc/nix/nix.conf
251
- // User configuration files: /Users/nobody/.config/nix/nix.conf:/etc/xdg/nix/nix.conf
252
- // Store directory: /nix/store
253
- // State directory: /nix/var/nix
254
- // Data directory: /nix/store/m0ns07v8by0458yp6k30rfq1rs3kaz6g-nix-2.21.2/share
255
-
256
- info := VersionInfo {}
257
- if len (data ) == 0 {
258
- return info , redact .Errorf ("empty nix --version output" )
259
- }
260
-
261
- lines := strings .Split (string (data ), "\n " )
262
- matches := versionRegexp .FindStringSubmatch (lines [0 ])
263
- if len (matches ) < 3 {
264
- return info , redact .Errorf ("parse nix version: %s" , redact .Safe (lines [0 ]))
265
- }
266
- info .Name = matches [1 ]
267
- info .Version = matches [2 ]
268
- for _ , line := range lines {
269
- name , value , found := strings .Cut (line , ": " )
270
- if ! found {
271
- continue
272
- }
273
-
274
- switch name {
275
- case "System type" :
276
- info .System = value
277
- case "Additional system types" :
278
- info .ExtraSystems = strings .Split (value , ", " )
279
- case "Features" :
280
- info .Features = strings .Split (value , ", " )
281
- case "System configuration file" :
282
- info .SystemConfig = value
283
- case "User configuration files" :
284
- info .UserConfigs = strings .Split (value , ":" )
285
- case "Store directory" :
286
- info .StoreDir = value
287
- case "State directory" :
288
- info .StateDir = value
289
- case "Data directory" :
290
- info .DataDir = value
291
- }
292
- }
293
- return info , nil
294
- }
295
-
296
- // AtLeast returns true if v.Version is >= version per semantic versioning. It
297
- // always returns false if v.Version is empty or invalid, such as when the
298
- // current Nix version cannot be parsed. It panics if version is an invalid
299
- // semver.
300
- func (v VersionInfo ) AtLeast (version string ) bool {
301
- if ! strings .HasPrefix (version , "v" ) {
302
- version = "v" + version
303
- }
304
- if ! semver .IsValid (version ) {
305
- panic (fmt .Sprintf ("nix.atLeast: invalid version %q" , version [1 :]))
306
- }
307
- if semver .IsValid ("v" + v .Version ) {
308
- return semver .Compare ("v" + v .Version , version ) >= 0
309
- }
310
-
311
- // If the version isn't a valid semver, check to see if it's a
312
- // prerelease (e.g., 2.23.0pre20240526_7de033d6) and coerce it to a
313
- // valid version (2.23.0-pre.20240526+7de033d6) so we can compare it.
314
- prerelease := preReleaseRegexp .ReplaceAllString (v .Version , "-pre.$date+$commit" )
315
- return semver .Compare ("v" + prerelease , version ) >= 0
316
- }
317
-
318
- // version is the cached output of `nix --version --debug`.
319
- var versionInfo = sync .OnceValues (runNixVersion )
320
-
321
- func runNixVersion () (VersionInfo , error ) {
322
- // Arbitrary timeout to make sure we don't take too long or hang.
323
- ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
324
- defer cancel ()
325
-
326
- // Intentionally don't use the nix.command function here. We use this to
327
- // perform Nix version checks and don't want to pass any extra-features
328
- // or flags that might be missing from old versions.
329
- cmd := exec .CommandContext (ctx , "nix" , "--version" , "--debug" )
330
- out , err := cmd .Output ()
331
- if err != nil {
332
- var exitErr * exec.ExitError
333
- if errors .As (err , & exitErr ) && len (exitErr .Stderr ) != 0 {
334
- return VersionInfo {}, redact .Errorf ("nix command: %s: %q: %v" , redact .Safe (cmd ), exitErr .Stderr , err )
335
- }
336
- if errors .Is (ctx .Err (), context .DeadlineExceeded ) {
337
- return VersionInfo {}, redact .Errorf ("nix command: %s: timed out while reading output: %v" , redact .Safe (cmd ), err )
338
- }
339
- return VersionInfo {}, redact .Errorf ("nix command: %s: %v" , redact .Safe (cmd ), err )
340
- }
341
-
342
- slog .Debug ("nix --version --debug output" , "out" , out )
343
- return parseVersionInfo (out )
344
- }
345
-
346
- // Version returns the currently installed version of Nix.
347
- func Version () (VersionInfo , error ) {
348
- return versionInfo ()
349
- }
350
-
351
133
var nixPlatforms = []string {
352
134
"aarch64-darwin" ,
353
135
"aarch64-linux" ,
0 commit comments