Skip to content

Commit

Permalink
[nix profile] Changes to support format changes from nix 2.20
Browse files Browse the repository at this point in the history
  • Loading branch information
savil committed Feb 1, 2024
1 parent c2ab2ca commit 7674d3d
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 31 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/cli-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +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. latest nix version
nix-version: ["2.12.0", "2.19.2"]
# 2. latest nix version (note, 2.20.1 introduced a new profile change so testing before and after is good)
nix-version: ["2.12.0", "2.19.2", "2.20.1"]
exclude:
- is-main: "not-main"
os: "${{ inputs.run-mac-tests && 'dummy' || 'macos-latest' }}"
Expand Down
21 changes: 18 additions & 3 deletions internal/nix/nixprofile/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ type NixProfileListItem struct {
// invocations of nix profile remove and nix profile upgrade.
index int

// name of the package
// nix 2.20 introduced a new format for the output of nix profile list, which includes the package name.
// This field is used instead of index for `list`, `remove` and `upgrade` subcommands of `nix profile`.
name string

// The original ("unlocked") flake reference and output attribute path used at installation time.
// NOTE that this will be empty if the package was added to the nix profile via store path.
unlockedReference string
Expand Down Expand Up @@ -74,10 +79,10 @@ func (i *NixProfileListItem) addedByStorePath() bool {
return i.unlockedReference == ""
}

// String serializes the NixProfileListItem back into the format printed by `nix profile list`
// String serializes the NixProfileListItem for debuggability
func (i *NixProfileListItem) String() string {
return fmt.Sprintf("{%d %s %s %s}",
i.index,
return fmt.Sprintf("{nameOrIndex:%s unlockedRef:%s lockedRef:%s, nixStorePaths:%s}",
i.NameOrIndex(),
i.unlockedReference,
i.lockedReference,
i.nixStorePaths,
Expand All @@ -87,3 +92,13 @@ func (i *NixProfileListItem) String() string {
func (i *NixProfileListItem) StorePaths() []string {
return i.nixStorePaths
}

// NameOrIndex is a helper method to get the name of the package if it exists, or the index if it doesn't.
// `nix profile` subcommands `list`, `remove`, and `upgrade` use either name (nix >= 2.20) or index (nix < 2.20)
// to identify the package.
func (i *NixProfileListItem) NameOrIndex() string {
if i.name != "" {
return i.name
}
return fmt.Sprintf("%d", i.index)
}
54 changes: 38 additions & 16 deletions internal/nix/nixprofile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,46 @@ func ProfileListItems(
URL string `json:"url"`
}
type ProfileListOutput struct {
Elements []ProfileListElement `json:"elements"`
Version int `json:"version"`
Elements map[string]ProfileListElement `json:"elements"`
Version int `json:"version"`
}

// Modern nix profiles: nix >= 2.20
var structOutput ProfileListOutput
if err := json.Unmarshal([]byte(output), &structOutput); err != nil {
return nil, err
if err := json.Unmarshal([]byte(output), &structOutput); err == nil {
items := []*NixProfileListItem{}
for name, element := range structOutput.Elements {
items = append(items, &NixProfileListItem{
name: name,
unlockedReference: lo.Ternary(element.OriginalURL != "", element.OriginalURL+"#"+element.AttrPath, ""),
lockedReference: lo.Ternary(element.URL != "", element.URL+"#"+element.AttrPath, ""),
nixStorePaths: element.StorePaths,
})
}
return items, nil
}
// Fall back to trying format for nix < version 2.20

// ProfileListOutput for nix < version 2.20 relied on index instead
// of name for each package installed.
type ProfileListOutputPreNix2Dot20 struct {
Elements []ProfileListElement `json:"elements"`
Version int `json:"version"`
}
var structOutput2 ProfileListOutputPreNix2Dot20
if err := json.Unmarshal([]byte(output), &structOutput2); err != nil {
return nil, err
}
items := []*NixProfileListItem{}
for index, element := range structOutput.Elements {
for index, element := range structOutput2.Elements {
items = append(items, &NixProfileListItem{
index: index,
unlockedReference: lo.Ternary(element.OriginalURL != "", element.OriginalURL+"#"+element.AttrPath, ""),
lockedReference: lo.Ternary(element.URL != "", element.URL+"#"+element.AttrPath, ""),
nixStorePaths: element.StorePaths,
})
}

return items, nil
}

Expand Down Expand Up @@ -98,7 +120,7 @@ func profileListLegacy(
return items, nil
}

type ProfileListIndexArgs struct {
type ProfileListNameOrIndexArgs struct {
// For performance, you can reuse the same list in multiple operations if you
// are confident index has not changed.
Items []*NixProfileListItem
Expand All @@ -108,21 +130,21 @@ type ProfileListIndexArgs struct {
ProfileDir string
}

// ProfileListIndex returns the index of args.Package in the nix profile specified by args.ProfileDir,
// or -1 if it's not found. Callers can pass in args.Items to avoid having to call `nix-profile list` again.
func ProfileListIndex(args *ProfileListIndexArgs) (int, error) {
// ProfileListNameOrIndex returns the name or index of args.Package in the nix profile specified by args.ProfileDir,
// or nix.ErrPackageNotFound if it's not found. Callers can pass in args.Items to avoid having to call `nix-profile list` again.
func ProfileListNameOrIndex(args *ProfileListNameOrIndexArgs) (string, error) {
var err error
items := args.Items
if items == nil {
items, err = ProfileListItems(args.Writer, args.ProfileDir)
if err != nil {
return -1, err
return "", err
}
}

inCache, err := args.Package.IsInBinaryCache()
if err != nil {
return -1, err
return "", err
}

if !inCache && args.Package.IsDevboxPackage {
Expand All @@ -131,23 +153,23 @@ func ProfileListIndex(args *ProfileListIndexArgs) (int, error) {
// of an existing profile item.
ref, err := args.Package.NormalizedDevboxPackageReference()
if err != nil {
return -1, errors.Wrapf(err, "failed to get installable for %s", args.Package.String())
return "", errors.Wrapf(err, "failed to get installable for %s", args.Package.String())
}

for _, item := range items {
if ref == item.unlockedReference {
return item.index, nil
return item.NameOrIndex(), nil
}
}
return -1, errors.Wrap(nix.ErrPackageNotFound, args.Package.String())
return "", errors.Wrap(nix.ErrPackageNotFound, args.Package.String())
}

for _, item := range items {
if item.Matches(args.Package, args.Lockfile) {
return item.index, nil
return item.NameOrIndex(), nil
}
}
return -1, errors.Wrap(nix.ErrPackageNotFound, args.Package.String())
return "", errors.Wrap(nix.ErrPackageNotFound, args.Package.String())
}

// parseNixProfileListItem reads each line of output (from `nix profile list`) and converts
Expand Down
8 changes: 4 additions & 4 deletions internal/nix/nixprofile/profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func TestNixProfileListItem(t *testing.T) {
),
expected: expectedTestData{
item: &NixProfileListItem{
2,
"github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.python39Packages.numpy",
"github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.python39Packages.numpy",
[]string{"/nix/store/qly36iy1p4q1h5p4rcbvsn3ll0zsd9pd-python3.9-numpy-1.23.3"},
index: 2,
unlockedReference: "github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.python39Packages.numpy",
lockedReference: "github:NixOS/nixpkgs/52e3e80afff4b16ccb7c52e9f0f5220552f03d04#legacyPackages.x86_64-darwin.python39Packages.numpy",
nixStorePaths: []string{"/nix/store/qly36iy1p4q1h5p4rcbvsn3ll0zsd9pd-python3.9-numpy-1.23.3"},
},
attrPath: "legacyPackages.x86_64-darwin.python39Packages.numpy",
packageName: "python39Packages.numpy",
Expand Down
6 changes: 3 additions & 3 deletions internal/nix/nixprofile/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

func ProfileUpgrade(ProfileDir string, pkg *devpkg.Package, lock *lock.File) error {
idx, err := ProfileListIndex(
&ProfileListIndexArgs{
nameOrIndex, err := ProfileListNameOrIndex(
&ProfileListNameOrIndexArgs{
Lockfile: lock,
Writer: os.Stderr,
Package: pkg,
Expand All @@ -24,5 +24,5 @@ func ProfileUpgrade(ProfileDir string, pkg *devpkg.Package, lock *lock.File) err
return err
}

return nix.ProfileUpgrade(ProfileDir, idx)
return nix.ProfileUpgrade(ProfileDir, nameOrIndex)
}
5 changes: 2 additions & 3 deletions internal/nix/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package nix

import (
"fmt"
"os"
"os/exec"

Expand All @@ -13,11 +12,11 @@ import (
"go.jetpack.io/devbox/internal/vercheck"
)

func ProfileUpgrade(ProfileDir string, idx int) error {
func ProfileUpgrade(ProfileDir, indexOrName string) error {
cmd := command(
"profile", "upgrade",
"--profile", ProfileDir,
fmt.Sprintf("%d", idx),
indexOrName,
)
out, err := cmd.CombinedOutput()
if err != nil {
Expand Down

0 comments on commit 7674d3d

Please sign in to comment.