@@ -18,6 +18,7 @@ import (
18
18
"go.jetpack.io/devbox/internal/devbox/devopt"
19
19
"go.jetpack.io/devbox/internal/devpkg"
20
20
"go.jetpack.io/devbox/internal/devpkg/pkgtype"
21
+ "go.jetpack.io/devbox/internal/nix/nixprofile"
21
22
"go.jetpack.io/devbox/internal/shellgen"
22
23
23
24
"go.jetpack.io/devbox/internal/boxcli/usererr"
@@ -267,9 +268,9 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
267
268
}
268
269
269
270
// Validate packages. Must be run up-front and definitely prior to computeEnv
270
- // and syncFlakeToProfile below that will evaluate the flake and may give
271
+ // and syncNixProfile below that will evaluate the flake and may give
271
272
// inscrutable errors if the package is uninstallable.
272
- if err := d .validatePackages ( ); err != nil {
273
+ if err := d .validatePackagesToBeInstalled ( ctx ); err != nil {
273
274
return err
274
275
}
275
276
@@ -300,7 +301,13 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
300
301
}
301
302
302
303
// Ensure the nix profile has the packages from the flake.
303
- if err := d .syncFlakeToProfile (ctx , env ["buildInputs" ]); err != nil {
304
+ buildInputs := []string {}
305
+ if env ["buildInputs" ] != "" {
306
+ // env["buildInputs"] can be empty string if there are no packages in the project
307
+ // if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry
308
+ buildInputs = strings .Split (env ["buildInputs" ], " " )
309
+ }
310
+ if err := d .syncNixProfile (ctx , buildInputs ); err != nil {
304
311
return err
305
312
}
306
313
@@ -388,11 +395,49 @@ func (d *Devbox) InstallRunXPackages(ctx context.Context) error {
388
395
return nil
389
396
}
390
397
391
- // validatePackages will ensure that packages are available to be installed
398
+ // validatePackagesToBeInstalled will ensure that packages are available to be installed
392
399
// in the user's current system.
393
- func (d * Devbox ) validatePackages () error {
394
- for _ , pkg := range d .InstallablePackages () {
400
+ func (d * Devbox ) validatePackagesToBeInstalled (ctx context.Context ) error {
401
+ // First, fetch the profile items from the nix-profile,
402
+ profileDir , err := d .profilePath ()
403
+ if err != nil {
404
+ return err
405
+ }
406
+ profileItems , err := nixprofile .ProfileListItems (ctx , d .stderr , profileDir )
407
+ if err != nil {
408
+ return err
409
+ }
410
+
411
+ // Second, get and prepare all the packages that must be installed in this project
412
+ packages , err := d .AllInstallablePackages ()
413
+ if err != nil {
414
+ return err
415
+ }
416
+ packages = lo .Filter (packages , devpkg .IsNix ) // Remove non-nix packages from the list
417
+ if err := devpkg .FillNarInfoCache (ctx , packages ... ); err != nil {
418
+ return err
419
+ }
420
+
421
+ // Third, compute which packages need to be installed
422
+ packagesToInstall := []* devpkg.Package {}
423
+ // Note: because devpkg.Package uses memoization when normalizing attribute paths (slow operation),
424
+ // and since we're reusing the Package objects, this O(n*m) loop becomes O(n+m) wrt the slow operation.
425
+ for _ , pkg := range packages {
426
+ found := false
427
+ for _ , item := range profileItems {
428
+ if item .Matches (pkg , d .lockfile ) {
429
+ found = true
430
+ break
431
+ }
432
+ }
433
+ if ! found {
434
+ packagesToInstall = append (packagesToInstall , pkg )
435
+ }
436
+ }
395
437
438
+ // Last, validate that packages that need to be installed are in fact installable
439
+ // on the user's current system.
440
+ for _ , pkg := range packagesToInstall {
396
441
inCache , err := pkg .IsInBinaryCache ()
397
442
if err != nil {
398
443
return err
0 commit comments