@@ -16,15 +16,13 @@ import (
1616 "runtime"
1717 "runtime/trace"
1818 "strings"
19- "sync"
2019 "time"
2120
2221 "github.com/pkg/errors"
2322 "go.jetpack.io/devbox/internal/boxcli/featureflag"
2423 "go.jetpack.io/devbox/internal/boxcli/usererr"
2524 "go.jetpack.io/devbox/internal/redact"
2625 "go.jetpack.io/devbox/nix/flake"
27- "golang.org/x/mod/semver"
2826
2927 "go.jetpack.io/devbox/internal/debug"
3028)
@@ -51,7 +49,7 @@ type PrintDevEnvArgs struct {
5149
5250// PrintDevEnv calls `nix print-dev-env -f <path>` and returns its output. The output contains
5351// 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 ) {
5553 defer debug .FunctionTimer ().End ()
5654 defer trace .StartRegion (ctx , "nixPrintDevEnv" ).End ()
5755
@@ -128,226 +126,10 @@ func ExperimentalFlags() []string {
128126 }
129127}
130128
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-
169129func SystemIsLinux () bool {
170130 return strings .Contains (System (), "linux" )
171131}
172132
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-
351133var nixPlatforms = []string {
352134 "aarch64-darwin" ,
353135 "aarch64-linux" ,
0 commit comments