syncNixProfileFromFlake ensures the nix profile has the packages from the buildInputs from the devshell of the generated flake. It also removes any packages from the nix profile that are no longer in the buildInputs.
(ctx context.Context)
| 20 | // |
| 21 | // It also removes any packages from the nix profile that are no longer in the buildInputs. |
| 22 | func (d *Devbox) syncNixProfileFromFlake(ctx context.Context) error { |
| 23 | defer debug.FunctionTimer().End() |
| 24 | // Get the buildInputs from the generated flake |
| 25 | env, err := d.execPrintDevEnv(ctx, false /*usePrintDevEnvCache*/) |
| 26 | if err != nil { |
| 27 | return err |
| 28 | } |
| 29 | buildInputs := env["buildInputs"] |
| 30 | |
| 31 | // Get the store-paths of the packages we want installed in the nix profile |
| 32 | wantStorePaths := []string{} |
| 33 | if buildInputs != "" { |
| 34 | // env["buildInputs"] can be empty string if there are no packages in the project |
| 35 | // if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry |
| 36 | wantStorePaths = strings.Split(buildInputs, " ") |
| 37 | } |
| 38 | |
| 39 | profilePath, err := d.profilePath() |
| 40 | if err != nil { |
| 41 | return err |
| 42 | } |
| 43 | |
| 44 | // Get the store-paths of the packages currently installed in the nix profile |
| 45 | items, err := nixprofile.ProfileListItems(d.stderr, profilePath) |
| 46 | if err != nil { |
| 47 | return fmt.Errorf("nix profile list: %v", err) |
| 48 | } |
| 49 | gotStorePaths := make([]string, 0, len(items)) |
| 50 | for _, item := range items { |
| 51 | gotStorePaths = append(gotStorePaths, item.StorePaths()...) |
| 52 | } |
| 53 | |
| 54 | // Diff the store paths and install/remove packages as needed |
| 55 | remove, add := lo.Difference(gotStorePaths, wantStorePaths) |
| 56 | if len(remove) > 0 { |
| 57 | packagesToRemove := make([]string, 0, len(remove)) |
| 58 | for _, p := range remove { |
| 59 | storePath := nix.NewStorePathParts(p) |
| 60 | packagesToRemove = append(packagesToRemove, fmt.Sprintf("%s@%s", storePath.Name, storePath.Version)) |
| 61 | } |
| 62 | slog.Debug("removing packages from nix profile", "pkgs", strings.Join(packagesToRemove, ", ")) |
| 63 | |
| 64 | if err := nix.ProfileRemove(profilePath, remove...); err != nil { |
| 65 | return err |
| 66 | } |
| 67 | } |
| 68 | if len(add) > 0 { |
| 69 | if err = nix.ProfileInstall(ctx, &nix.ProfileInstallArgs{ |
| 70 | Installables: add, |
| 71 | ProfilePath: profilePath, |
| 72 | Writer: d.stderr, |
| 73 | }); errors.Is(err, nix.ErrPriorityConflict) { |
| 74 | // We need to install the packages one by one because there was possibly a priority conflict |
| 75 | // This is slower, but uncommon. |
| 76 | for _, addPath := range add { |
| 77 | if err = nix.ProfileInstall(ctx, &nix.ProfileInstallArgs{ |
| 78 | Installables: []string{addPath}, |
| 79 | ProfilePath: profilePath, |
no test coverage detected