Skip to content

Derive nix profile from the generated flake #1692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
5 changes: 2 additions & 3 deletions .github/workflows/cli-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,8 @@ jobs:
run-project-tests: ["project-tests", "project-tests-off"]
# Run tests on:
# 1. the oldest supported nix version (which is 2.9.0? But determinate-systems installer has 2.12.0)
# 2. nix version 2.17.0 which introduces a new code path that minimizes nixpkgs downloads.
# 3. latest nix version
nix-version: ["2.12.0", "2.17.0", "2.19.2"]
# 2. latest nix version, must be > 2.17.0 which introduces a new code path that minimizes nixpkgs downloads.
nix-version: ["2.12.0", "2.19.2"]
exclude:
- is-main: "not-main"
os: "${{ inputs.run-mac-tests && 'dummy' || 'macos-latest' }}"
Expand Down
74 changes: 74 additions & 0 deletions internal/devbox/nixprofile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package devbox

import (
"context"
"fmt"
"strings"

"github.com/samber/lo"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/nix/nixprofile"
"go.jetpack.io/devbox/internal/ux"
)

// syncNixProfile ensures the nix profile has the packages specified in wantStorePaths.
// It also removes any packages from the nix profile that are not in wantStorePaths.
func (d *Devbox) syncNixProfile(ctx context.Context, wantStorePaths []string) error {
profilePath, err := d.profilePath()
if err != nil {
return err
}

// Get the store-paths of the packages currently installed in the nix profile
items, err := nixprofile.ProfileListItems(ctx, d.stderr, profilePath)
if err != nil {
return fmt.Errorf("nix profile list: %v", err)
}
gotStorePaths := make([]string, 0, len(items))
for _, item := range items {
gotStorePaths = append(gotStorePaths, item.StorePaths()...)
}

// Diff the store paths and install/remove packages as needed
remove, add := lo.Difference(gotStorePaths, wantStorePaths)
if len(remove) > 0 {
packagesToRemove := make([]string, 0, len(remove))
for _, p := range remove {
storePath := nix.NewStorePathParts(p)
packagesToRemove = append(packagesToRemove, fmt.Sprintf("%s@%s", storePath.Name, storePath.Version))
}
if len(packagesToRemove) == 1 {
ux.Finfo(d.stderr, "Removing %s\n", strings.Join(packagesToRemove, ", "))
} else {
ux.Finfo(d.stderr, "Removing packages: %s\n", strings.Join(packagesToRemove, ", "))
}

if err := nix.ProfileRemove(profilePath, remove...); err != nil {
return err
}
}
if len(add) > 0 {
total := len(add)
for idx, addPath := range add {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can all of the packages be added to the profile with a single nix profile install command? I think in my POC I was assuming that all of the packages were already installed successfully if Nix was able to build the generated Devbox flake.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC that leads to problems because multiple Devbox packages can install the same binary.

Nix will error with the complaint that it cannot install "" from two different paths at the same priority.

stepNum := idx + 1
storePath := nix.NewStorePathParts(addPath)
nameAndVersion := fmt.Sprintf("%s@%s", storePath.Name, storePath.Version)
stepMsg := fmt.Sprintf("[%d/%d] %s", stepNum, total, nameAndVersion)

if err = nixprofile.ProfileInstall(ctx, &nixprofile.ProfileInstallArgs{
CustomStepMessage: stepMsg,
Installable: addPath,
// Install in offline mode for speed. We know we should have all the files
// locally in /nix/store since we have run `nix print-dev-env` prior to this.
// Also avoids some "substituter not found for store-path" errors.
Offline: true,
PackageName: storePath.Name,
ProfilePath: profilePath,
Writer: d.stderr,
}); err != nil {
return fmt.Errorf("error installing package %s: %w", addPath, err)
}
}
}
return nil
}
Loading