Skip to content

Commit 85b2c95

Browse files
committed
[remove nixpkgs] add toPath, and edit devbox.lock only on update command
1 parent 6c833de commit 85b2c95

File tree

10 files changed

+185
-59
lines changed

10 files changed

+185
-59
lines changed

devbox.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"packages": [
33
4-
54
65
],
76
"env": {

devbox.lock

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
{
22
"lockfile_version": "1",
33
"packages": {
4-
5-
"last_modified": "2023-03-31T22:52:29Z",
6-
"resolved": "github:NixOS/nixpkgs/242246ee1e58f54d2322227fc5eef53b4a616a31#actionlint",
7-
"version": "1.6.23"
8-
},
94
105
"last_modified": "2023-05-25T03:54:59Z",
116
"resolved": "github:NixOS/nixpkgs/8d4d822bc0efa9de6eddc79cb0d82897a9baa750#go",

internal/devpkg/package.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"net/url"
12+
"os"
1213
"path/filepath"
1314
"regexp"
1415
"strings"
@@ -19,6 +20,7 @@ import (
1920
"go.jetpack.io/devbox/internal/cuecfg"
2021
"go.jetpack.io/devbox/internal/lock"
2122
"go.jetpack.io/devbox/internal/nix"
23+
"go.jetpack.io/devbox/internal/ux"
2224
)
2325

2426
// Package represents a "package" added to the devbox.json config.
@@ -432,7 +434,7 @@ func (p *Package) IsInBinaryStore() (bool, error) {
432434
}
433435

434436
// PathInBinaryStore is the key in the BinaryCacheStore for this package
435-
// This is used as FromPath in builtins.fetchClosure
437+
// This is used as BinaryStorePath in builtins.fetchClosure
436438
func (p *Package) PathInBinaryStore() (string, error) {
437439
if isInStore, err := p.IsInBinaryStore(); err != nil {
438440
return "", err
@@ -452,10 +454,40 @@ func (p *Package) PathInBinaryStore() (string, error) {
452454
}
453455

454456
sysInfo := entry.Systems[userSystem]
455-
storeDirParts := []string{sysInfo.FromHash, sysInfo.StoreName}
456-
if sysInfo.StoreVersion != "" {
457-
storeDirParts = append(storeDirParts, sysInfo.StoreVersion)
457+
return sysInfo.BinaryStorePath, nil
458+
}
459+
460+
func (p *Package) PathInLocalStore() (string, error) {
461+
462+
if isInStore, err := p.IsInBinaryStore(); err != nil {
463+
return "", err
464+
} else if !isInStore {
465+
return "",
466+
errors.Errorf("Package %q cannot be fetched from binary cache store", p.Raw)
467+
}
468+
469+
entry, err := p.lockfile.Resolve(p.Raw)
470+
if err != nil {
471+
return "", err
472+
}
473+
474+
userSystem, err := nix.System()
475+
if err != nil {
476+
return "", err
477+
}
478+
479+
sysInfo := entry.Systems[userSystem]
480+
if sysInfo.LocalStorePath != "" {
481+
return sysInfo.LocalStorePath, nil
482+
}
483+
484+
ux.Fwarning(
485+
os.Stderr,
486+
"calculating local_store_path. This may be slow.\n" +
487+
"Run `devbox update` to speed this up for next time.")
488+
localPath, err := nix.ContentAddressedStorePath(sysInfo.BinaryStorePath)
489+
if err != nil {
490+
return "", err
458491
}
459-
storeDir := strings.Join(storeDirParts, "-")
460-
return filepath.Join("/nix/store", storeDir), nil
492+
return localPath, err
461493
}

internal/impl/update.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99

10+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
1011
"go.jetpack.io/devbox/internal/devpkg"
1112
"go.jetpack.io/devbox/internal/nix"
1213
"go.jetpack.io/devbox/internal/nix/nixprofile"
@@ -90,21 +91,52 @@ func (d *Devbox) updateDevboxPackage(
9091
if err != nil {
9192
return err
9293
}
93-
if existing != nil && existing.Version != newEntry.Version {
94+
if existing == nil {
95+
ux.Finfo(d.writer, "Resolved %s to %[1]s %[2]s\n", pkg, newEntry.Resolved)
96+
d.lockfile.Packages[pkg.Raw] = newEntry
97+
return nil
98+
}
99+
100+
if existing.Version != newEntry.Version {
94101
ux.Finfo(d.writer, "Updating %s %s -> %s\n", pkg, existing.Version, newEntry.Version)
95102
if err := d.removePackagesFromProfile(ctx, []string{pkg.Raw}); err != nil {
96103
// Warn but continue. TODO(landau): ensurePackagesAreInstalled should
97104
// sync the profile so we don't need to do this manually.
98105
ux.Fwarning(d.writer, "Failed to remove %s from profile: %s\n", pkg, err)
99106
}
100107
d.lockfile.Packages[pkg.Raw] = newEntry
101-
} else if existing == nil {
102-
ux.Finfo(d.writer, "Resolved %s to %[1]s %[2]s\n", pkg, newEntry.Resolved)
108+
return nil
109+
}
110+
111+
// Check if the package's system info is missing, or not complete.
112+
userSystem, err := nix.System()
113+
if err != nil {
114+
return err
115+
}
116+
var needsSysInfo bool
117+
var needsLocalStorePath bool
118+
if featureflag.RemoveNixpkgs.Enabled() {
119+
userSysInfo := d.lockfile.Packages[pkg.Raw].Systems[userSystem]
120+
needsSysInfo = userSysInfo == nil
121+
if !needsSysInfo {
122+
// Check if the LocalStorePath is missing for the user's system.
123+
// Since any one user cannot add this field for all systems,
124+
// we'll need to progressively add it to a project's lockfile.
125+
needsLocalStorePath = userSysInfo.LocalStorePath == ""
126+
}
127+
}
128+
if needsSysInfo {
103129
d.lockfile.Packages[pkg.Raw] = newEntry
104-
} else {
105-
ux.Finfo(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
130+
ux.Finfo(d.writer, "Updated system information for %s\n", pkg)
131+
return nil
132+
} else if needsLocalStorePath {
133+
// Update the LocalStorePath for the user's system
134+
d.lockfile.Packages[pkg.Raw].Systems[userSystem].LocalStorePath = newEntry.Systems[userSystem].LocalStorePath
135+
ux.Finfo(d.writer, "Updated system information for %s\n", pkg)
136+
return nil
106137
}
107138

139+
ux.Finfo(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
108140
return nil
109141
}
110142

internal/lock/lockfile.go

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111

1212
"github.com/pkg/errors"
1313
"github.com/samber/lo"
14-
"go.jetpack.io/devbox/internal/boxcli/featureflag"
15-
"go.jetpack.io/devbox/internal/nix"
1614
"go.jetpack.io/devbox/internal/searcher"
1715

1816
"go.jetpack.io/devbox/internal/cuecfg"
@@ -40,12 +38,13 @@ type Package struct {
4038
}
4139

4240
type SystemInfo struct {
43-
System string // stored elsewhere in json: it's the key for the Package.Systems
44-
FromHash string `json:"from_hash,omitempty"`
45-
// StoreName may be different from the canonicalName so we store it separately
46-
StoreName string `json:"store_name,omitempty"`
47-
StoreVersion string `json:"store_version,omitempty"`
48-
ToHash string `json:"to_hash,omitempty"`
41+
// BinaryStorePath is the cache key in the Binary Cache Store (cache.nixos.org)
42+
// It is of the form <hash>-<name>-<version>
43+
// <name> may be different from the canonicalName so we store the full store path.
44+
BinaryStorePath string `json:"bin_store_path,omitempty"`
45+
// LocalStorePath is the content-addressed path for the nix package in /nix/store
46+
// It is of the form <hash>-<name>-<version>
47+
LocalStorePath string `json:"local_store_path,omitempty"`
4948
}
5049

5150
func GetFile(project devboxProject) (*File, error) {
@@ -86,17 +85,7 @@ func (l *File) Remove(pkgs ...string) error {
8685
func (l *File) Resolve(pkg string) (*Package, error) {
8786
entry, hasEntry := l.Packages[pkg]
8887

89-
// If the package's system info is missing, we need to resolve it again.
90-
needsSysInfo := false
91-
if hasEntry && featureflag.RemoveNixpkgs.Enabled() {
92-
userSystem, err := nix.System()
93-
if err != nil {
94-
return nil, err
95-
}
96-
needsSysInfo = entry.Systems[userSystem] == nil
97-
}
98-
99-
if !hasEntry || entry.Resolved == "" || needsSysInfo {
88+
if !hasEntry || entry.Resolved == "" {
10089
locked := &Package{}
10190
var err error
10291
if _, _, versioned := searcher.ParseVersionedPackage(pkg); versioned {
@@ -109,19 +98,6 @@ func (l *File) Resolve(pkg string) (*Package, error) {
10998
// whatever hash is in the devbox.json
11099
locked = &Package{Resolved: l.LegacyNixpkgsPath(pkg)}
111100
}
112-
113-
// Merge the system info from the lockfile with the queried system info.
114-
// This is necessary because a different system's info may previously have
115-
// been added. For example: `aarch64-darwin` was already added, but
116-
// current user is on `x86_64-linux`.
117-
if hasEntry && featureflag.RemoveNixpkgs.Enabled() {
118-
for _, sysInfo := range entry.Systems {
119-
if _, ok := locked.Systems[sysInfo.System]; !ok {
120-
locked.Systems[sysInfo.System] = sysInfo
121-
}
122-
}
123-
}
124-
125101
l.Packages[pkg] = locked
126102
}
127103

internal/lock/resolve.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ func (l *File) FetchResolvedPackage(pkg string) (*Package, error) {
3434

3535
sysInfos := map[string]*SystemInfo{}
3636
if featureflag.RemoveNixpkgs.Enabled() {
37-
sysInfos = buildLockSystemInfos(packageVersion)
37+
sysInfos, err = buildLockSystemInfos(packageVersion)
38+
if err != nil {
39+
return nil, err
40+
}
3841
}
3942
packageInfo, err := selectForSystem(packageVersion)
4043
if err != nil {
@@ -76,15 +79,32 @@ func selectForSystem(pkg *searcher.PackageVersion) (searcher.PackageInfo, error)
7679
return maps.Values(pkg.Systems)[0], nil
7780
}
7881

79-
func buildLockSystemInfos(pkg *searcher.PackageVersion) map[string]*SystemInfo {
82+
func buildLockSystemInfos(pkg *searcher.PackageVersion) (map[string]*SystemInfo, error) {
83+
userSystem, err := nix.System()
84+
if err != nil {
85+
return nil, err
86+
}
87+
8088
sysInfos := map[string]*SystemInfo{}
8189
for sysName, sysInfo := range pkg.Systems {
90+
91+
// guard against missing search data
92+
if sysInfo.StoreHash == "" || sysInfo.StoreName == "" {
93+
continue
94+
}
95+
96+
localStorePath := ""
97+
if sysName == userSystem {
98+
binaryStorePath := nix.StorePath(sysInfo.StoreHash, sysInfo.StoreName, sysInfo.StoreVersion)
99+
localStorePath, err = nix.ContentAddressedStorePath(binaryStorePath)
100+
if err != nil {
101+
return nil, err
102+
}
103+
}
82104
sysInfos[sysName] = &SystemInfo{
83-
System: sysName,
84-
FromHash: sysInfo.StoreHash,
85-
StoreName: sysInfo.StoreName,
86-
StoreVersion: sysInfo.StoreVersion,
105+
BinaryStorePath: nix.StorePath(sysInfo.StoreHash, sysInfo.StoreName, sysInfo.StoreVersion),
106+
LocalStorePath: localStorePath,
87107
}
88108
}
89-
return sysInfos
109+
return sysInfos, nil
90110
}

internal/nix/nix.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ func (*Nix) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*PrintDevEn
6666
args.FlakesFilePath,
6767
)
6868
cmd.Args = append(cmd.Args, ExperimentalFlags()...)
69-
if featureflag.RemoveNixpkgs.Enabled() {
70-
cmd.Args = append(cmd.Args, "--impure")
71-
}
7269
cmd.Args = append(cmd.Args, "--json")
7370
debug.Log("Running print-dev-env cmd: %s\n", cmd)
7471
data, err = cmd.Output()

internal/nix/store.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package nix
2+
3+
import (
4+
"path/filepath"
5+
"regexp"
6+
"strings"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
func StorePath(hash, name, version string) string {
12+
storeDirParts := []string{hash, name}
13+
if version != "" {
14+
storeDirParts = append(storeDirParts, version)
15+
}
16+
storeDir := strings.Join(storeDirParts, "-")
17+
return filepath.Join("/nix/store", storeDir)
18+
}
19+
20+
// contentAddressedRegex matches the output of `nix store make-content-addressed`.
21+
// It is used to select the content-addressed store path (the second one in the example below).
22+
//
23+
// Example:
24+
// > nix store make-content-addressed /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
25+
// rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
26+
var contentAddressedRegex = regexp.MustCompile(`rewrote\s'[\/a-z0-9-\.]+'\sto\s'([a-z0-9-\/\.]+)'`)
27+
28+
// ContentAddressedStorePath takes a store path and returns the content-addressed store path.
29+
func ContentAddressedStorePath(storePath string) (string, error) {
30+
cmd := command("store", "make-content-addressed", storePath)
31+
out, err := cmd.CombinedOutput()
32+
if err != nil {
33+
return "", errors.WithStack(err)
34+
}
35+
36+
matches := contentAddressedRegex.FindStringSubmatch(string(out))
37+
if len(matches) < 2 {
38+
return "", errors.Errorf("could not parse output of nix store make-content-addressed: %s", string(out))
39+
}
40+
41+
return matches[1], nil
42+
}

internal/nix/store_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package nix
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestContentAddressedPath(t *testing.T) {
9+
10+
testCases := []struct {
11+
storePath string
12+
expected string
13+
}{
14+
{
15+
"/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1",
16+
"/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1",
17+
},
18+
}
19+
20+
for index, testCase := range testCases {
21+
t.Run(fmt.Sprintf("%d", index), func(t *testing.T) {
22+
out, err := ContentAddressedStorePath(testCase.storePath)
23+
if err != nil {
24+
t.Errorf("got error: %v", err)
25+
}
26+
if out != testCase.expected {
27+
t.Errorf("got %s, want %s", out, testCase.expected)
28+
}
29+
})
30+
31+
}
32+
}

internal/shellgen/tmpl/flake_remove_nixpkgs.nix.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
(builtins.fetchClosure{
2727
fromStore = "{{ $.BinaryCacheStore }}";
2828
fromPath = "{{ .PathInBinaryStore }}";
29+
toPath = "{{ .PathInLocalStore }}";
2930
})
3031
{{- end }}
3132
{{- end }}

0 commit comments

Comments
 (0)