Skip to content

Commit 066944e

Browse files
authored
[rm nixpkgs] use path-from-hash-part command when updating lock file (#1442)
## Summary **Motivation** With Remove Nixpkgs feature on, installing `[email protected]` was failing with: ``` [3/7] [email protected] don't know how to build these paths: /nix/store/0xyi7w3cm3g7gzwfpwqzx0n7xks2sdc6-curl-8.0.1 asked 'https://cache.nixos.org/' for '/nix/store/0xyi7w3cm3g7gzwfpwqzx0n7xks2sdc6-curl-8.0.1' but got '/nix/store/0xyi7w3cm3g7gzwfpwqzx0n7xks2sdc6-curl-8.0.1-bin' error: path '/nix/store/0xyi7w3cm3g7gzwfpwqzx0n7xks2sdc6-curl-8.0.1' is required, but there is no substituter that can build it ``` **Explanation** Thanks to @gcurtis. curl’s first (and therefore default) output is `bin`, not `out`, so Nix is appending `-bin` to the path. From `man nix-build`: > If a derivation has multiple outputs, nix-build will build the default (first) output. You can also build all outputs: $ nix-build '<nixpkgs>' --attr openssl.all This will create a symlink for each output named result-outputname. The suffix is omitted if the output name is out. So if openssl has outputs out, bin and man, nix-build will create symlinks result, result-bin and result-man. **Fix** Observe: ``` > nix store path-from-hash-part --store https://cache.nixos.org/ bbkz21vmx16wl0h3xk36g7gxpbymnm6d --extra-experimental-features "nix-command flakes" /nix/store/bbkz21vmx16wl0h3xk36g7gxpbymnm6d-curl-8.0.1-bin ``` So: - During `devbox update`, we derive the store-path from the search API and write to `devbox.lock` - Previously, we would manually construct store-path from `hash-name-version` from search API response - Now, we invoke `nix store path-from-hash-part <hash>` and write the response store-path in the `devbox.lock`. - if this fails (it shouldn't!), we skip writing the store-path to the `devbox.lock`, and print a warning. Maybe we should track it in sentry? ## How was it tested? ``` > cat devbox.json { "packages": [ "[email protected]" ] } > devbox update Info: Updated system information for [email protected] Ensuring packages are installed. Installing package: [email protected]. [1/1] [email protected] [1/1] [email protected]: Success Info: Running "nix flake update" > cat devbox.lock { "lockfile_version": "1", "packages": { "[email protected]": { "last_modified": "2023-05-31T02:09:55Z", "resolved": "github:NixOS/nixpkgs/9cfaa8a1a00830d17487cb60a19bb86f96f09b27#curl", "source": "devbox-search", "version": "8.0.1", "systems": { "aarch64-darwin": { "store_path": "/nix/store/7qwsslwdrjyg8hs654nsqpq7lly6dhq5-curl-8.0.1-bin" }, "aarch64-linux": { "store_path": "/nix/store/g9g1p4l9146kclnx4lv0hfbzhr9hk7n7-curl-8.0.1-bin" } } } } } ```
1 parent 49ddee7 commit 066944e

File tree

3 files changed

+69
-19
lines changed

3 files changed

+69
-19
lines changed

internal/lock/resolve.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@
44
package lock
55

66
import (
7+
"context"
78
"fmt"
9+
"os"
10+
"sync"
811
"time"
912

1013
"github.com/pkg/errors"
14+
"github.com/samber/lo"
1115
"go.jetpack.io/devbox/internal/boxcli/featureflag"
1216
"go.jetpack.io/devbox/internal/boxcli/usererr"
1317
"go.jetpack.io/devbox/internal/debug"
1418
"go.jetpack.io/devbox/internal/nix"
1519
"go.jetpack.io/devbox/internal/searcher"
20+
"go.jetpack.io/devbox/internal/ux"
1621
"golang.org/x/exp/maps"
22+
"golang.org/x/sync/errgroup"
1723
)
1824

1925
// FetchResolvedPackage fetches a resolution but does not write it to the lock
@@ -35,7 +41,10 @@ func (f *File) FetchResolvedPackage(pkg string) (*Package, error) {
3541

3642
sysInfos := map[string]*SystemInfo{}
3743
if featureflag.RemoveNixpkgs.Enabled() {
38-
sysInfos = buildLockSystemInfos(packageVersion)
44+
sysInfos, err = buildLockSystemInfos(packageVersion)
45+
if err != nil {
46+
return nil, err
47+
}
3948
}
4049
packageInfo, err := selectForSystem(packageVersion)
4150
if err != nil {
@@ -74,20 +83,57 @@ func selectForSystem(pkg *searcher.PackageVersion) (searcher.PackageInfo, error)
7483
return maps.Values(pkg.Systems)[0], nil
7584
}
7685

77-
func buildLockSystemInfos(pkg *searcher.PackageVersion) map[string]*SystemInfo {
78-
sysInfos := map[string]*SystemInfo{}
79-
for sysName, sysInfo := range pkg.Systems {
86+
func buildLockSystemInfos(pkg *searcher.PackageVersion) (map[string]*SystemInfo, error) {
87+
// guard against missing search data
88+
systems := lo.PickBy(pkg.Systems, func(sysName string, sysInfo searcher.PackageInfo) bool {
89+
return sysInfo.StoreHash != "" && sysInfo.StoreName != ""
90+
})
8091

81-
// guard against missing search data
82-
if sysInfo.StoreHash == "" || sysInfo.StoreName == "" {
83-
debug.Log("WARN: skipping %s in %s due to missing store name or hash", pkg.Name, sysName)
84-
continue
85-
}
92+
group, ctx := errgroup.WithContext(context.Background())
93+
94+
var storePathLock sync.RWMutex
95+
sysStorePaths := map[string]string{}
96+
for _sysName, _sysInfo := range systems {
97+
sysName := _sysName // capture range variable
98+
sysInfo := _sysInfo // capture range variable
99+
100+
group.Go(func() error {
101+
// We should use devpkg.BinaryCache here, but it'll cause a circular reference
102+
// Just hardcoding for now. Maybe we should move that to nix.DefaultBinaryCache?
103+
path, err := nix.StorePathFromHashPart(ctx, sysInfo.StoreHash, "https://cache.nixos.org")
104+
if err != nil {
105+
// Should we report this to sentry to collect data?
106+
ux.Fwarning(
107+
os.Stderr,
108+
"Failed to resolve store path for %s with storeHash %s. Installing will be a bit slower.\n",
109+
sysName,
110+
sysInfo.StoreHash,
111+
)
112+
debug.Log(
113+
"Failed to resolve store path for %s with storeHash %s. Error is %s.\n",
114+
sysName,
115+
sysInfo.StoreHash,
116+
err,
117+
)
118+
// Instead of erroring, we can just skip this package. It can install via the slow path.
119+
return nil
120+
}
121+
storePathLock.Lock()
122+
sysStorePaths[sysName] = path
123+
storePathLock.Unlock()
124+
return nil
125+
})
126+
}
127+
err := group.Wait()
128+
if err != nil {
129+
return nil, err
130+
}
86131

87-
storePath := nix.StorePath(sysInfo.StoreHash, sysInfo.StoreName, sysInfo.StoreVersion)
132+
sysInfos := map[string]*SystemInfo{}
133+
for sysName, storePath := range sysStorePaths {
88134
sysInfos[sysName] = &SystemInfo{
89135
StorePath: storePath,
90136
}
91137
}
92-
return sysInfos
138+
return sysInfos, nil
93139
}

internal/nix/command.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package nix
22

33
import (
4+
"context"
45
"os/exec"
56
)
67

78
func command(args ...string) *exec.Cmd {
9+
return commandContext(context.Background(), args...)
10+
}
811

9-
cmd := exec.Command("nix", args...)
12+
func commandContext(ctx context.Context, args ...string) *exec.Cmd {
13+
cmd := exec.CommandContext(ctx, "nix", args...)
1014
cmd.Args = append(cmd.Args, ExperimentalFlags()...)
1115
return cmd
1216
}

internal/nix/store.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package nix
22

33
import (
4-
"path/filepath"
4+
"context"
55
"strings"
66
)
77

8-
func StorePath(hash, name, version string) string {
9-
storeDirParts := []string{hash, name}
10-
if version != "" {
11-
storeDirParts = append(storeDirParts, version)
8+
func StorePathFromHashPart(ctx context.Context, hash, storeAddr string) (string, error) {
9+
cmd := commandContext(ctx, "store", "path-from-hash-part", "--store", storeAddr, hash)
10+
resultBytes, err := cmd.Output()
11+
if err != nil {
12+
return "", err
1213
}
13-
storeDir := strings.Join(storeDirParts, "-")
14-
return filepath.Join("/nix/store", storeDir)
14+
return strings.TrimSpace(string(resultBytes)), nil
1515
}

0 commit comments

Comments
 (0)