Skip to content

Commit 18bbfe9

Browse files
authored
[remove nixpkgs] part 2: generate flake.nix using builtins.fetchClosure (#1209)
1 parent 27fc351 commit 18bbfe9

File tree

8 files changed

+180
-35
lines changed

8 files changed

+180
-35
lines changed

internal/lock/lockfile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
package lock
55

66
import (
7-
"errors"
87
"fmt"
98
"io/fs"
109
"path/filepath"
1110
"strings"
1211

12+
"github.com/pkg/errors"
1313
"github.com/samber/lo"
1414
"go.jetpack.io/devbox/internal/boxcli/featureflag"
1515

internal/nix/input.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
"regexp"
1414
"strings"
1515

16+
"github.com/pkg/errors"
1617
"github.com/samber/lo"
17-
1818
"go.jetpack.io/devbox/internal/boxcli/usererr"
1919
"go.jetpack.io/devbox/internal/cuecfg"
2020
"go.jetpack.io/devbox/internal/lock"
@@ -73,6 +73,7 @@ func PackageFromString(raw string, locker lock.Locker) *Package {
7373
}
7474
pkgURL, _ = url.Parse(normalizedURL)
7575
}
76+
7677
return &Package{*pkgURL, locker, raw, ""}
7778
}
7879

@@ -407,6 +408,62 @@ func (p *Package) hashFromNixPkgsURL() string {
407408
return HashFromNixPkgsURL(p.URLForFlakeInput())
408409
}
409410

411+
// BinaryCacheStore is the store from which to fetch this package's binaries.
412+
// It is used as FromStore in builtins.fetchClosure.
413+
const BinaryCacheStore = "https://cache.nixos.org"
414+
415+
func (p *Package) IsInBinaryStore() (bool, error) {
416+
if !p.isVersioned() {
417+
return false, nil
418+
}
419+
420+
entry, err := p.lockfile.Resolve(p.Raw)
421+
if err != nil {
422+
return false, err
423+
}
424+
425+
userSystem, err := System()
426+
if err != nil {
427+
return false, err
428+
}
429+
430+
if entry.Systems == nil {
431+
return false, nil
432+
}
433+
434+
// Check if the user's system's info is present in the lockfile
435+
_, ok := entry.Systems[userSystem]
436+
return ok, nil
437+
}
438+
439+
// PathInBinaryStore is the key in the BinaryCacheStore for this package
440+
// This is used as FromPath in builtins.fetchClosure
441+
func (p *Package) PathInBinaryStore() (string, error) {
442+
if isInStore, err := p.IsInBinaryStore(); err != nil {
443+
return "", err
444+
} else if !isInStore {
445+
return "", errors.Errorf("Package %q cannot be fetched from binary cache store", p.Raw)
446+
}
447+
448+
entry, err := p.lockfile.Resolve(p.Raw)
449+
if err != nil {
450+
return "", err
451+
}
452+
453+
userSystem, err := System()
454+
if err != nil {
455+
return "", err
456+
}
457+
458+
sysInfo := entry.Systems[userSystem]
459+
storeDirParts := []string{sysInfo.FromHash, sysInfo.StoreName}
460+
if sysInfo.StoreVersion != "" {
461+
storeDirParts = append(storeDirParts, sysInfo.StoreVersion)
462+
}
463+
storeDir := strings.Join(storeDirParts, "-")
464+
return filepath.Join("/nix/store", storeDir), nil
465+
}
466+
410467
// IsGithubNixpkgsURL returns true if the package is a flake of the form:
411468
// github:NixOS/nixpkgs/...
412469
//

internal/nix/nix.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"os/exec"
1212
"path/filepath"
1313
"runtime/trace"
14+
"strings"
1415

1516
"github.com/pkg/errors"
17+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
1618

1719
"go.jetpack.io/devbox/internal/debug"
1820
)
@@ -64,6 +66,9 @@ func (*Nix) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*PrintDevEn
6466
args.FlakesFilePath,
6567
)
6668
cmd.Args = append(cmd.Args, ExperimentalFlags()...)
69+
if featureflag.RemoveNixpkgs.Enabled() {
70+
cmd.Args = append(cmd.Args, "--impure")
71+
}
6772
cmd.Args = append(cmd.Args, "--json")
6873
debug.Log("Running print-dev-env cmd: %s\n", cmd)
6974
data, err = cmd.Output()
@@ -102,15 +107,27 @@ func FlakeNixpkgs(commit string) string {
102107
}
103108

104109
func ExperimentalFlags() []string {
110+
options := []string{"nix-command", "flakes"}
111+
if featureflag.RemoveNixpkgs.Enabled() {
112+
options = append(options, "fetch-closure")
113+
}
105114
return []string{
106115
"--extra-experimental-features", "ca-derivations",
107-
"--option", "experimental-features", "nix-command flakes",
116+
"--option", "experimental-features", strings.Join(options, " "),
108117
}
109118
}
110119

111120
var cachedSystem string
112121

113122
func System() (string, error) {
123+
// For Savil to debug "remove nixpkgs" feature. The search api lacks x86-darwin info.
124+
// So, I need to fake that I am x86-linux and inspect the output in generated devbox.lock
125+
// and flake.nix files.
126+
override := os.Getenv("__DEVBOX_NIX_SYSTEM")
127+
if override != "" {
128+
return override, nil
129+
}
130+
114131
if cachedSystem == "" {
115132
cmd := exec.Command(
116133
"nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem",

internal/shellgen/flake_input.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/samber/lo"
9+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
910
"go.jetpack.io/devbox/internal/goutil"
1011
"go.jetpack.io/devbox/internal/nix"
1112
)
@@ -64,24 +65,30 @@ func (f *flakeInput) BuildInputs() []string {
6465
// i.e. have a commit hash and always resolve to the same package/version.
6566
// Note: inputs returned by this function include plugin packages. (php only for now)
6667
// It's not entirely clear we always want to add plugin packages to the top level
67-
func flakeInputs(ctx context.Context, devbox devboxer) ([]*flakeInput, error) {
68+
func flakeInputs(ctx context.Context, packages []*nix.Package) ([]*flakeInput, error) {
6869
defer trace.StartRegion(ctx, "flakeInputs").End()
6970

7071
// Use the verbose name flakeInputs to distinguish from `inputs`
7172
// which refer to `nix.Input` in most of the codebase.
7273
flakeInputs := map[string]*flakeInput{}
7374

74-
userInputs := devbox.PackagesAsInputs()
75-
pluginInputs, err := devbox.PluginManager().PluginInputs(userInputs)
76-
if err != nil {
77-
return nil, err
78-
}
75+
packages = lo.Filter(packages, func(item *nix.Package, _ int) bool {
76+
// Include packages (like local or remote flakes) that cannot be
77+
// fetched from a Binary Cache Store.
78+
if !featureflag.RemoveNixpkgs.Enabled() {
79+
return true
80+
}
81+
82+
inStore, err := item.IsInBinaryStore()
83+
if err != nil {
84+
// Ignore this error for now. TODO savil: return error?
85+
return true
86+
}
87+
return !inStore
88+
})
7989

8090
order := []string{}
81-
// We prioritize plugin packages so that the php plugin works. Not sure
82-
// if this is behavior we want for user plugins. We may need to add an optional
83-
// priority field to the config.
84-
for _, input := range append(pluginInputs, userInputs...) {
91+
for _, input := range packages {
8592
AttributePath, err := input.FullPackageAttributePath()
8693
if err != nil {
8794
return nil, err

internal/shellgen/flake_plan.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ package shellgen
33
import (
44
"context"
55
"runtime/trace"
6+
7+
"go.jetpack.io/devbox/internal/nix"
68
)
79

810
// flakePlan contains the data to populate the top level flake.nix file
911
// that builds the devbox environment
1012
type flakePlan struct {
11-
NixpkgsInfo *NixpkgsInfo
12-
FlakeInputs []*flakeInput
13+
BinaryCacheStore string
14+
NixpkgsInfo *NixpkgsInfo
15+
FlakeInputs []*flakeInput
16+
Packages []*nix.Package
17+
System string
1318
}
1419

1520
func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
@@ -35,9 +40,17 @@ func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
3540
}
3641
}
3742

38-
shellPlan := &flakePlan{}
39-
var err error
40-
shellPlan.FlakeInputs, err = flakeInputs(ctx, devbox)
43+
userPackages := devbox.PackagesAsInputs()
44+
pluginPackages, err := devbox.PluginManager().PluginInputs(userPackages)
45+
if err != nil {
46+
return nil, err
47+
}
48+
// We prioritize plugin packages so that the php plugin works. Not sure
49+
// if this is behavior we want for user plugins. We may need to add an optional
50+
// priority field to the config.
51+
packages := append(pluginPackages, userPackages...)
52+
53+
flakeInputs, err := flakeInputs(ctx, packages)
4154
if err != nil {
4255
return nil, err
4356
}
@@ -46,14 +59,23 @@ func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
4659

4760
// This is an optimization. Try to reuse the nixpkgs info from the flake
4861
// inputs to avoid introducing a new one.
49-
for _, input := range shellPlan.FlakeInputs {
62+
for _, input := range flakeInputs {
5063
if input.IsNixpkgs() {
5164
nixpkgsInfo = getNixpkgsInfo(input.HashFromNixPkgsURL())
5265
break
5366
}
5467
}
5568

56-
shellPlan.NixpkgsInfo = nixpkgsInfo
69+
system, err := nix.System()
70+
if err != nil {
71+
return nil, err
72+
}
5773

58-
return shellPlan, nil
74+
return &flakePlan{
75+
BinaryCacheStore: nix.BinaryCacheStore,
76+
FlakeInputs: flakeInputs,
77+
NixpkgsInfo: nixpkgsInfo,
78+
Packages: packages,
79+
System: system,
80+
}, nil
5981
}

internal/shellgen/generate.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ import (
1717
"text/template"
1818

1919
"github.com/pkg/errors"
20+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
2021
"go.jetpack.io/devbox/internal/cuecfg"
2122
"go.jetpack.io/devbox/internal/debug"
2223
)
2324

2425
//go:embed tmpl/*
2526
var tmplFS embed.FS
2627

27-
var shellFiles = []string{"shell.nix"}
28-
2928
// GenerateForPrintEnv will create all the files necessary for processing
3029
// devbox.PrintEnv, which is the core function from which devbox shell/run/direnv
3130
// functionality is derived.
@@ -39,15 +38,14 @@ func GenerateForPrintEnv(ctx context.Context, devbox devboxer) error {
3938

4039
outPath := genPath(devbox)
4140

42-
for _, file := range shellFiles {
43-
err := writeFromTemplate(outPath, plan, file)
44-
if err != nil {
45-
return errors.WithStack(err)
46-
}
41+
// Preserving shell.nix to avoid breaking old-style .envrc users
42+
err = writeFromTemplate(outPath, plan, "shell.nix", "shell.nix")
43+
if err != nil {
44+
return errors.WithStack(err)
4745
}
4846

4947
// Gitignore file is added to the .devbox directory
50-
err = writeFromTemplate(filepath.Join(devbox.ProjectDir(), ".devbox"), plan, ".gitignore")
48+
err = writeFromTemplate(filepath.Join(devbox.ProjectDir(), ".devbox"), plan, ".gitignore", ".gitignore")
5149
if err != nil {
5250
return errors.WithStack(err)
5351
}
@@ -69,7 +67,7 @@ var (
6967
tmplOldBuf = bytes.NewBuffer(make([]byte, 0, 4096))
7068
)
7169

72-
func writeFromTemplate(path string, plan any, tmplName string) error {
70+
func writeFromTemplate(path string, plan any, tmplName string, generatedName string) error {
7371
tmplKey := tmplName + ".tmpl"
7472
tmpl := tmplCache[tmplKey]
7573
if tmpl == nil {
@@ -93,7 +91,7 @@ func writeFromTemplate(path string, plan any, tmplName string) error {
9391
// changed. Blindly overwriting the file could invalidate Nix's cache
9492
// every time, slowing down evaluation considerably.
9593
var (
96-
outPath = filepath.Join(path, tmplName)
94+
outPath = filepath.Join(path, generatedName)
9795
flag = os.O_RDWR | os.O_CREATE
9896
perm = fs.FileMode(0644)
9997
)
@@ -150,7 +148,11 @@ var templateFuncs = template.FuncMap{
150148

151149
func makeFlakeFile(d devboxer, outPath string, plan *flakePlan) error {
152150
flakeDir := FlakePath(d)
153-
err := writeFromTemplate(flakeDir, plan, "flake.nix")
151+
templateName := "flake.nix"
152+
if featureflag.RemoveNixpkgs.Enabled() {
153+
templateName = "flake_remove_nixpkgs.nix"
154+
}
155+
err := writeFromTemplate(flakeDir, plan, templateName, "flake.nix")
154156
if err != nil {
155157
return errors.WithStack(err)
156158
}

internal/shellgen/generate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ var update = flag.Bool("update", false, "update the golden files with the test r
1919
func TestWriteFromTemplate(t *testing.T) {
2020
dir := filepath.Join(t.TempDir(), "makeme")
2121
outPath := filepath.Join(dir, "flake.nix")
22-
err := writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix")
22+
err := writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix", "flake.nix")
2323
if err != nil {
2424
t.Fatal("got error writing flake template:", err)
2525
}
2626
cmpGoldenFile(t, outPath, "testdata/flake.nix.golden")
2727

2828
t.Run("WriteUnmodified", func(t *testing.T) {
29-
err = writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix")
29+
err = writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix", "flake.nix")
3030
if err != nil {
3131
t.Fatal("got error writing flake template:", err)
3232
}
@@ -39,7 +39,7 @@ func TestWriteFromTemplate(t *testing.T) {
3939
}
4040
FlakeInputs []flakeInput
4141
}{}
42-
err = writeFromTemplate(dir, emptyPlan, "flake.nix")
42+
err = writeFromTemplate(dir, emptyPlan, "flake.nix", "flake.nix")
4343
if err != nil {
4444
t.Fatal("got error writing flake template:", err)
4545
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
description = "A devbox shell";
3+
4+
inputs = {
5+
nixpkgs.url = "{{ .NixpkgsInfo.URL }}";
6+
{{- range .FlakeInputs }}
7+
{{.Name}}.url = "{{.URLWithCaching}}";
8+
{{- end }}
9+
};
10+
11+
outputs = {
12+
self,
13+
nixpkgs,
14+
{{- range .FlakeInputs }}
15+
{{.Name}},
16+
{{- end }}
17+
}:
18+
let
19+
pkgs = nixpkgs.legacyPackages.{{ .System }};
20+
in
21+
{
22+
devShells.{{ .System }}.default = pkgs.mkShell {
23+
buildInputs = [
24+
{{- range .Packages }}
25+
{{- if .IsInBinaryStore }}
26+
(builtins.fetchClosure{
27+
fromStore = "{{ $.BinaryCacheStore }}";
28+
fromPath = "{{ .PathInBinaryStore }}";
29+
})
30+
{{- end }}
31+
{{- end }}
32+
{{- range .FlakeInputs }}
33+
{{- range .BuildInputs }}
34+
{{.}}
35+
{{- end }}
36+
{{- end }}
37+
];
38+
};
39+
};
40+
}

0 commit comments

Comments
 (0)