Skip to content

Commit ad6725d

Browse files
authored
[remove nixpkgs] add toPath, and edit devbox.lock only on update command (#1256)
## Summary This PR rolls in a few fixes: 1. Add `toPath` to `builtins.fetchClosure` - lets us remove `--impure` flag in `nix print-dev-env` 2. Add `ca_store_path` to `devbox.lock` - Because this is system-dependent, _and_ is calculated locally, a `devbox update` will only update the user's system's `ca_store_path`. - This is good for team-mates on the same system, and to avoid the slow `nix store make-content-addressed <path>` call on each `devbox shell/run/shellenv`. 3. Only update `devbox.lock` during `devbox update`. - Previously, it would update system info on `devbox shell/run/shellenv`. 4. Simplify `devbox.lock` schema's `SystemInfo` to have `StorePath` and `CAStorePath`. These are used as `fromPath` and `toPath` in `builtins.fetchClosure`. Miscellaneous changes: 1. I remove `actionlint` from the `devbox.json`. It is not core to our workflows, and I am working with very minimal space on devbox cloud VM. This reduces "out of space errors" marginally. ## How was it tested? In `examples/development/go/hello-world`: `devbox update` and `devbox shell` The `devbox.lock` diff is now ``` ❯ git diff diff --git a/examples/development/go/hello-world/devbox.lock b/examples/development/go/hello-world/ devbox.lock index 34556d3..168874db 100644 --- a/examples/development/go/hello-world/devbox.lock +++ b/examples/development/go/hello-world/devbox.lock @@ -2,9 +2,18 @@ "lockfile_version": "1", "packages": { "[email protected]": { - "last_modified": "2023-05-01T16:53:22Z", - "resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#go_1_19", - "version": "1.19.8" + "last_modified": "2023-05-03T02:55:54Z", + "resolved": "github:NixOS/nixpkgs/0d373d5af960504dd60c3d06c65e553b36ef29d8#go_1_19", + "version": "1.19.8", + "systems": { + "aarch64-darwin": { + "store_path": "/nix/store/7m99ip3616l3z670y83p34r3plwd4iq1-go-1.19.8" + }, + "x86_64-linux": { + "store_path": "/nix/store/r0x0agq0vwn0p6z99vkkvn8l8a8idzsb-go-1.19.8", + "ca_store_path": "/nix/store/ns557l24yf1f16f9jvbmxv57qdkkn2mj-go-1.19.8" + } + } } } -} \ No newline at end of file +} ```
1 parent 9ccf82a commit ad6725d

File tree

10 files changed

+190
-60
lines changed

10 files changed

+190
-60
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: 42 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"
@@ -20,6 +21,7 @@ import (
2021
"go.jetpack.io/devbox/internal/cuecfg"
2122
"go.jetpack.io/devbox/internal/lock"
2223
"go.jetpack.io/devbox/internal/nix"
24+
"go.jetpack.io/devbox/internal/ux"
2325
)
2426

2527
// Package represents a "package" added to the devbox.json config.
@@ -426,8 +428,10 @@ func (p *Package) HashFromNixPkgsURL() string {
426428

427429
// BinaryCacheStore is the store from which to fetch this package's binaries.
428430
// It is used as FromStore in builtins.fetchClosure.
431+
// TODO savil: rename to BinaryCache
429432
const BinaryCacheStore = "https://cache.nixos.org"
430433

434+
// TODO savil: rename to IsInBinaryCache
431435
func (p *Package) IsInBinaryStore() (bool, error) {
432436
if !featureflag.RemoveNixpkgs.Enabled() {
433437
return false, nil
@@ -457,7 +461,8 @@ func (p *Package) IsInBinaryStore() (bool, error) {
457461
}
458462

459463
// PathInBinaryStore is the key in the BinaryCacheStore for this package
460-
// This is used as FromPath in builtins.fetchClosure
464+
// This is used as StorePath in builtins.fetchClosure
465+
// TODO savil: rename to PathInBinaryCache
461466
func (p *Package) PathInBinaryStore() (string, error) {
462467
if isInStore, err := p.IsInBinaryStore(); err != nil {
463468
return "", err
@@ -477,10 +482,41 @@ func (p *Package) PathInBinaryStore() (string, error) {
477482
}
478483

479484
sysInfo := entry.Systems[userSystem]
480-
storeDirParts := []string{sysInfo.FromHash, sysInfo.StoreName}
481-
if sysInfo.StoreVersion != "" {
482-
storeDirParts = append(storeDirParts, sysInfo.StoreVersion)
485+
return sysInfo.StorePath, nil
486+
}
487+
488+
func (p *Package) ContentAddressedStorePath() (string, error) {
489+
490+
if isInStore, err := p.IsInBinaryStore(); err != nil {
491+
return "", err
492+
} else if !isInStore {
493+
return "",
494+
errors.Errorf("Package %q cannot be fetched from binary cache store", p.Raw)
495+
}
496+
497+
entry, err := p.lockfile.Resolve(p.Raw)
498+
if err != nil {
499+
return "", err
500+
}
501+
502+
userSystem, err := nix.System()
503+
if err != nil {
504+
return "", err
505+
}
506+
507+
sysInfo := entry.Systems[userSystem]
508+
if sysInfo.CAStorePath != "" {
509+
return sysInfo.CAStorePath, nil
510+
}
511+
512+
ux.Fwarning(
513+
os.Stderr,
514+
"calculating local_store_path. This may be slow.\n"+
515+
"Run `devbox update` to speed this up for next time.",
516+
)
517+
localPath, err := nix.ContentAddressedStorePath(sysInfo.StorePath)
518+
if err != nil {
519+
return "", err
483520
}
484-
storeDir := strings.Join(storeDirParts, "-")
485-
return filepath.Join("/nix/store", storeDir), nil
521+
return localPath, err
486522
}

internal/impl/update.go

Lines changed: 36 additions & 6 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,50 @@ 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)
103-
d.lockfile.Packages[pkg.Raw] = newEntry
104-
} else {
105-
ux.Finfo(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
108+
return nil
109+
}
110+
111+
// Check if the package's system info is missing, or not complete.
112+
if featureflag.RemoveNixpkgs.Enabled() {
113+
userSystem, err := nix.System()
114+
if err != nil {
115+
return err
116+
}
117+
118+
// Check if the system info is missing for the user's system.
119+
sysInfo := d.lockfile.Packages[pkg.Raw].Systems[userSystem]
120+
if sysInfo == nil {
121+
d.lockfile.Packages[pkg.Raw] = newEntry
122+
ux.Finfo(d.writer, "Updated system information for %s\n", pkg)
123+
return nil
124+
}
125+
126+
// Check if the CAStorePath is missing for the user's system.
127+
// Since any one user cannot add this field for all systems,
128+
// we'll need to progressively add it to a project's lockfile.
129+
if sysInfo.CAStorePath == "" {
130+
// Update the CAStorePath for the user's system
131+
d.lockfile.Packages[pkg.Raw].Systems[userSystem].CAStorePath = newEntry.Systems[userSystem].CAStorePath
132+
ux.Finfo(d.writer, "Updated system information for %s\n", pkg)
133+
return nil
134+
}
106135
}
107136

137+
ux.Finfo(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
108138
return nil
109139
}
110140

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+
// StorePath 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+
StorePath string `json:"store_path,omitempty"`
45+
// CAStorePath is the content-addressed path for the nix package in /nix/store
46+
// It is of the form <hash>-<name>-<version>
47+
CAStorePath string `json:"ca_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+
storePath := nix.StorePath(sysInfo.StoreHash, sysInfo.StoreName, sysInfo.StoreVersion)
97+
caStorePath := ""
98+
if sysName == userSystem {
99+
caStorePath, err = nix.ContentAddressedStorePath(storePath)
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+
StorePath: storePath,
106+
CAStorePath: caStorePath,
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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package nix
2+
3+
import (
4+
"encoding/json"
5+
"path/filepath"
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+
// ContentAddressedStorePath takes a store path and returns the content-addressed store path.
21+
func ContentAddressedStorePath(storePath string) (string, error) {
22+
cmd := command("store", "make-content-addressed", storePath, "--json")
23+
out, err := cmd.Output()
24+
if err != nil {
25+
return "", errors.WithStack(err)
26+
}
27+
// Example Output:
28+
// > nix store make-content-addressed /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1 --json
29+
// {"rewrites":{"/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1":"/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1"}}
30+
31+
type ContentAddressed struct {
32+
Rewrites map[string]string `json:"rewrites"`
33+
}
34+
caOutput := ContentAddressed{}
35+
if err := json.Unmarshal(out, &caOutput); err != nil {
36+
return "", errors.WithStack(err)
37+
}
38+
39+
caStorePath, ok := caOutput.Rewrites[storePath]
40+
if !ok {
41+
return "", errors.Errorf("could not find content-addressed store path for %s", storePath)
42+
}
43+
return caStorePath, nil
44+
}

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
@@ -34,6 +34,7 @@
3434
(builtins.fetchClosure{
3535
fromStore = "{{ $.BinaryCacheStore }}";
3636
fromPath = "{{ .PathInBinaryStore }}";
37+
toPath = "{{ .ContentAddressedStorePath }}";
3738
})
3839
{{- end }}
3940
{{- end }}

0 commit comments

Comments
 (0)