-
Notifications
You must be signed in to change notification settings - Fork 249
[rm-nixpkgs] Fix profile list bug for packages added by store path #1416
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
Conversation
// are confident index has not changed. | ||
List map[string]*NixProfileListItem | ||
Items []*NixProfileListItem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the main change
internal/nix/nixprofile/profile.go
Outdated
if pathInStore == item.nixStorePath { | ||
return item.index, nil | ||
} | ||
} | ||
return -1, errors.Wrap(nix.ErrPackageNotFound, args.DevPkg.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a somewhat subtle change that I thought I'd highlight.
If a package is inCache, then it must have been added by store path. So it follows that we must find a store path match in the items, and if not, then the package is Not Found--that is, there's no need to fall back to the other checks below.
Is this a safe assumption? I think so, because the cache doesn't change much and a package is either in it or not.. right? Please let me know if this isn't as safe an assumption as I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that makes sense.
One scenario I was thinking about is (sequentially):
- Existing devbox user has installed a package.
- We ship a Devbox update that has Remove Nixpkgs feature enabled.
- This code here runs. The Package happens to be in the binary cache, BUT it had previously been installed the "old way" in the nix profile.
I think this code should still work, since the store path is input-addressed and would match here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tested a few different scenarios and verified this is safe. Most importantly, I tested:
-
User has installed a package in the "old" way. Then the user upgrades devbox and runs a devbox command that triggers the installation path. The package is now going to be installed by store-path. Because the store-paths match, then we do nothing. That is, we keep the "old-style" package installed in the profile, because we found it. This is OK.
-
Same as above, but the package store path does NOT match. In this case, devbox will end up removing the old package and adding the new package, which is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
I think we do need a fix in extraPackagesInProfile
. See my discussion below...
Can be done in separate PR (or I can do it, if you'd like).
internal/impl/packages.go
Outdated
index, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{ | ||
Lockfile: d.lockfile, | ||
Writer: d.writer, | ||
Input: input, | ||
DevPkg: pkg, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: make the key Package
since we're standardizing on that (mostly).
I say "(mostly)" because there is a devconfig.Package
but that is usually consumed by devcfg.Package
and not used directly in most of the codebase.
@@ -428,10 +428,10 @@ func (d *Devbox) pendingPackagesForInstallation(ctx context.Context) ([]*devpkg. | |||
} | |||
for _, pkg := range packages { | |||
_, err := nixprofile.ProfileListIndex(&nixprofile.ProfileListIndexArgs{ | |||
List: list, | |||
Items: items, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏾
// ToPackage constructs a nix.Package using the unlocked reference. | ||
// TODO: this doesn't handle well the case where item.unlockedReference == "#" | ||
func (item *NixProfileListItem) ToPackage(locker lock.Locker) *devpkg.Package { | ||
return devpkg.PackageFromString(item.unlockedReference, locker) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm this could be problematic for Items created via store-paths! 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be used in:
-
https://github.com/jetpack-io/devbox/blob/main/internal/nix/nixprofile/profile.go#L144
This call is okay, because we have a separate if-condition that handles BinaryCache (i.e. store-paths) installs and is checked before this call. -
https://github.com/jetpack-io/devbox/blob/main/internal/impl/packages.go#L481
This one inextraPackagesInProfile
does feel problematic. Because theitem.UnlockedReference
here will be#
for items-from-store-paths.
Hmm not sure how to fix (2).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm re-thinking installing via store-path in the profile. Did we go down a wrong path by installing by store-paths in the nix profile?
Downside:
- we cannot recover a
devpkg.Package
from these item-from-store-paths
Since we switched from content-addressed paths back to input-addressed-paths, I'm wondering if we should continue with the old way of installing packages in profiles. What do we gain (to remind me) from installing by store-path? In theory, it should be the same I believe.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, looking at the PR where I made install happen via nix store path: #1255
the reason we must do it is to avoid downloading nixpkgs. Cool, we need it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, to fix (2), I think we can do something like:
extras := []*nixprofile.NixProfileListItem{}
// Note: because nix.Input uses memoization when normalizing attribute paths (slow operation),
// and since we're reusing the Input objects, this O(n*m) loop becomes O(n+m) wrt the slow operation.
outer:
for _, item := range profileItems {
profileInput := item.ToPackage(d.lockfile)
for _, devboxInput := range devboxInputs {
// assuming devpkg.FillNarInfoCache() is already called
if devboxInput.IsInBinaryCache() {
for _, path := range item.storePaths {
if devboxInput.InputAddressedPath() == path {
// mark as present
}
}
}
if profileInput.Equals(devboxInput) {
continue outer
}
}
extras = append(extras, item)
}
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for delay answering. Yes, this function, ProfileListItem.ToPackage()
is definitely a problem with store path-added Items. That's why I left a TODO in there that wasn't there before. And this is the problem I was saying where we sometimes "resolve" a package to <default-hash>##
, which is, well, dumb.
But yes, I think the code as it is today will continue to work just fine, but we should indeed do better here. I think we'll have to change the callsites like you're suggesting, but let's do that in a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While testing the upgrade path, I concluded that this is an important bug to fix (in separate PR). That's because tidyProfile
will run the pairwise checks to match profile items with devbox packages every time the number of items in the profile differs from the number of items in devbox.json. And thus, every single time we run into this condition (which I think will be often), we'll end up removing all store path-installed items from the profile, because none of them will ever match with what's in devbox.json
internal/nix/nixprofile/profile.go
Outdated
if pathInStore == item.nixStorePath { | ||
return item.index, nil | ||
} | ||
} | ||
return -1, errors.Wrap(nix.ErrPackageNotFound, args.DevPkg.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that makes sense.
One scenario I was thinking about is (sequentially):
- Existing devbox user has installed a package.
- We ship a Devbox update that has Remove Nixpkgs feature enabled.
- This code here runs. The Package happens to be in the binary cache, BUT it had previously been installed the "old way" in the nix profile.
I think this code should still work, since the store path is input-addressed and would match here.
3b27d43
to
2ca4543
Compare
2ca4543
to
2166973
Compare
Summary
When a package is added by store path, then when we query
nix profile list
, it'll look like this:instead of this:
Thus, when we read the profile items and try to build a map out of the item's unlockedRef, we were always ending up with a map of size 1.
To fix, I changed the map to a slice, and changed the callsites to handle the slice instead.
I made a few other code organization changes that are no-ops.
How was it tested?
Tested by calling
devbox install
and inspecting the profile withnix profile list
, with and without the rm-nixpkgs feature enabled. I attempted to add unit tests but it's not as easy as I had thought.