Skip to content

Commit 2ac8350

Browse files
committed
improve validatePackages to only operate on packages to be installed
1 parent 33b3797 commit 2ac8350

File tree

2 files changed

+54
-16
lines changed

2 files changed

+54
-16
lines changed

internal/devbox/nixprofile.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,14 @@ import (
99
"go.jetpack.io/devbox/internal/nix/nixprofile"
1010
)
1111

12-
// syncFlakeToProfile ensures the buildInputs from the flake's devShell are
13-
// installed in the nix profile.
14-
// buildInputs is a space-separated list of store paths from the nix print-dev-env output's buildInputs.
15-
func (d *Devbox) syncFlakeToProfile(ctx context.Context, buildInputs string) error {
12+
// syncNixProfile ensures the nix profile has the packages specified in wantStorePaths.
13+
// It also removes any packages from the nix profile that are not in wantStorePaths.
14+
func (d *Devbox) syncNixProfile(ctx context.Context, wantStorePaths []string) error {
1615
profilePath, err := d.profilePath()
1716
if err != nil {
1817
return err
1918
}
2019

21-
// Get the build inputs (i.e. store paths) from the generated flake's print-dev-env output
22-
wantStorePaths := []string{}
23-
if buildInputs != "" { // if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry
24-
wantStorePaths = strings.Split(buildInputs, " ")
25-
}
26-
2720
// Get the store-paths of the packages currently installed in the nix profile
2821
items, err := nixprofile.ProfileListItems(ctx, d.stderr, profilePath)
2922
if err != nil {

internal/devbox/packages.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"go.jetpack.io/devbox/internal/devbox/devopt"
1919
"go.jetpack.io/devbox/internal/devpkg"
2020
"go.jetpack.io/devbox/internal/devpkg/pkgtype"
21+
"go.jetpack.io/devbox/internal/nix/nixprofile"
2122
"go.jetpack.io/devbox/internal/shellgen"
2223

2324
"go.jetpack.io/devbox/internal/boxcli/usererr"
@@ -267,9 +268,9 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
267268
}
268269

269270
// 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
271272
// inscrutable errors if the package is uninstallable.
272-
if err := d.validatePackages(); err != nil {
273+
if err := d.validatePackagesToBeInstalled(ctx); err != nil {
273274
return err
274275
}
275276

@@ -300,7 +301,13 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
300301
}
301302

302303
// 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 {
304311
return err
305312
}
306313

@@ -388,11 +395,49 @@ func (d *Devbox) InstallRunXPackages(ctx context.Context) error {
388395
return nil
389396
}
390397

391-
// validatePackages will ensure that packages are available to be installed
398+
// validatePackagesToBeInstalled will ensure that packages are available to be installed
392399
// 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+
}
395437

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 {
396441
inCache, err := pkg.IsInBinaryCache()
397442
if err != nil {
398443
return err

0 commit comments

Comments
 (0)