Skip to content

Commit 2f38449

Browse files
authored
shellgen: delete flake.lock if flake.nix changes (#2388)
Fix a bug where `.devbox/gen/flake` gets locked on old versions of `.devbox/gen/flake/glibc-patch` by deleting the `flake.lock` file. We only delete the lock file when the generated flake changes so that Nix isn't forced to re-evaluate it every time. The repro steps are: 1. Add a package that gets auto-patched (`devbox add [email protected]`). 2. `.devbox/gen/flake` gets locked on the patch flake. 3. Change the patched package (`devbox add [email protected]`). 4. The new patch flake isn't used because of `.devbox/gen/flake/flake.lock`. Instead, the old version is used (from the Nix store). Fixes #2316. Fixes #2370.
1 parent 399b7f3 commit 2f38449

File tree

3 files changed

+33
-21
lines changed

3 files changed

+33
-21
lines changed

internal/shellgen/flake_plan.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"os"
78
"path/filepath"
89
"runtime/trace"
910
"slices"
@@ -317,7 +318,14 @@ func (g *glibcPatchFlake) writeTo(dir string) error {
317318
slog.Debug("error copying system libcuda.so to flake", "dir", dir)
318319
}
319320
}
320-
return writeFromTemplate(dir, g, "glibc-patch.nix", "flake.nix")
321+
changed, err := writeFromTemplate(dir, g, "glibc-patch.nix", "flake.nix")
322+
if err != nil {
323+
return err
324+
}
325+
if changed {
326+
_ = os.Remove(filepath.Join(dir, "flake.lock"))
327+
}
328+
return nil
321329
}
322330

323331
func (g *glibcPatchFlake) LogValue() slog.Value {

internal/shellgen/generate.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ func GenerateForPrintEnv(ctx context.Context, devbox devboxer) error {
3737
outPath := genPath(devbox)
3838

3939
// Preserving shell.nix to avoid breaking old-style .envrc users
40-
err = writeFromTemplate(outPath, plan, "shell.nix", "shell.nix")
40+
_, err = writeFromTemplate(outPath, plan, "shell.nix", "shell.nix")
4141
if err != nil {
4242
return errors.WithStack(err)
4343
}
4444

4545
// Gitignore file is added to the .devbox directory
46-
err = writeFromTemplate(filepath.Join(devbox.ProjectDir(), ".devbox"), plan, ".gitignore", ".gitignore")
46+
_, err = writeFromTemplate(filepath.Join(devbox.ProjectDir(), ".devbox"), plan, ".gitignore", ".gitignore")
4747
if err != nil {
4848
return errors.WithStack(err)
4949
}
@@ -70,7 +70,7 @@ var (
7070
tmplBuf bytes.Buffer
7171
)
7272

73-
func writeFromTemplate(path string, plan any, tmplName, generatedName string) error {
73+
func writeFromTemplate(path string, plan any, tmplName, generatedName string) (changed bool, err error) {
7474
tmplKey := tmplName + ".tmpl"
7575
tmpl := tmplCache[tmplKey]
7676
if tmpl == nil {
@@ -81,64 +81,64 @@ func writeFromTemplate(path string, plan any, tmplName, generatedName string) er
8181
glob := "tmpl/" + tmplKey
8282
tmpl, err = tmpl.ParseFS(tmplFS, glob)
8383
if err != nil {
84-
return redact.Errorf("parse embedded tmplFS glob %q: %v", redact.Safe(glob), redact.Safe(err))
84+
return false, redact.Errorf("parse embedded tmplFS glob %q: %v", redact.Safe(glob), redact.Safe(err))
8585
}
8686
tmplCache[tmplKey] = tmpl
8787
}
8888
tmplBuf.Reset()
8989
if err := tmpl.Execute(&tmplBuf, plan); err != nil {
90-
return redact.Errorf("execute template %s: %v", redact.Safe(tmplKey), err)
90+
return false, redact.Errorf("execute template %s: %v", redact.Safe(tmplKey), err)
9191
}
9292

9393
// In some circumstances, Nix looks at the mod time of a file when
9494
// caching, so we only want to update the file if something has
9595
// changed. Blindly overwriting the file could invalidate Nix's cache
9696
// every time, slowing down evaluation considerably.
97-
err := overwriteFileIfChanged(filepath.Join(path, generatedName), tmplBuf.Bytes(), 0o644)
97+
changed, err = overwriteFileIfChanged(filepath.Join(path, generatedName), tmplBuf.Bytes(), 0o644)
9898
if err != nil {
99-
return redact.Errorf("write %s to file: %v", redact.Safe(tmplName), err)
99+
return changed, redact.Errorf("write %s to file: %v", redact.Safe(tmplName), err)
100100
}
101-
return nil
101+
return changed, nil
102102
}
103103

104104
// overwriteFileIfChanged checks that the contents of f == data, and overwrites
105105
// f if they differ. It also ensures that f's permissions are set to perm.
106-
func overwriteFileIfChanged(path string, data []byte, perm os.FileMode) error {
106+
func overwriteFileIfChanged(path string, data []byte, perm os.FileMode) (changed bool, err error) {
107107
flag := os.O_RDWR | os.O_CREATE
108108
file, err := os.OpenFile(path, flag, perm)
109109
if errors.Is(err, os.ErrNotExist) {
110110
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
111-
return err
111+
return false, err
112112
}
113113

114114
// Definitely a new file if we had to make the directory.
115-
return os.WriteFile(path, data, perm)
115+
return true, os.WriteFile(path, data, perm)
116116
}
117117
if err != nil {
118-
return err
118+
return false, err
119119
}
120120
defer file.Close()
121121

122122
fi, err := file.Stat()
123123
if err != nil || fi.Mode().Perm() != perm {
124124
if err := file.Chmod(perm); err != nil {
125-
return err
125+
return false, err
126126
}
127127
}
128128

129129
// Fast path - check if the lengths differ.
130130
if err == nil && fi.Size() != int64(len(data)) {
131-
return overwriteFile(file, data, 0)
131+
return true, overwriteFile(file, data, 0)
132132
}
133133

134134
r := bufio.NewReader(file)
135135
for offset := range data {
136136
b, err := r.ReadByte()
137137
if err != nil || b != data[offset] {
138-
return overwriteFile(file, data, offset)
138+
return true, overwriteFile(file, data, offset)
139139
}
140140
}
141-
return nil
141+
return false, nil
142142
}
143143

144144
// overwriteFile truncates f to len(data) and writes data[offset:] beginning at
@@ -168,5 +168,9 @@ var templateFuncs = template.FuncMap{
168168

169169
func makeFlakeFile(d devboxer, plan *flakePlan) error {
170170
flakeDir := FlakePath(d)
171-
return writeFromTemplate(flakeDir, plan, "flake.nix", "flake.nix")
171+
changed, err := writeFromTemplate(flakeDir, plan, "flake.nix", "flake.nix")
172+
if changed {
173+
_ = os.Remove(filepath.Join(flakeDir, "flake.lock"))
174+
}
175+
return err
172176
}

internal/shellgen/generate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ func TestWriteFromTemplate(t *testing.T) {
2626
t.Setenv("__DEVBOX_NIX_SYSTEM", "x86_64-linux")
2727
dir := filepath.Join(t.TempDir(), "makeme")
2828
outPath := filepath.Join(dir, "flake.nix")
29-
err := writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix", "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
}
3333
cmpGoldenFile(t, outPath, "testdata/flake.nix.golden")
3434

3535
t.Run("WriteUnmodified", func(t *testing.T) {
36-
err = writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix", "flake.nix")
36+
_, err = writeFromTemplate(dir, testFlakeTmplPlan, "flake.nix", "flake.nix")
3737
if err != nil {
3838
t.Fatal("got error writing flake template:", err)
3939
}
@@ -49,7 +49,7 @@ func TestWriteFromTemplate(t *testing.T) {
4949
FlakeInputs: []flakeInput{},
5050
System: "x86_64-linux",
5151
}
52-
err = writeFromTemplate(dir, emptyPlan, "flake.nix", "flake.nix")
52+
_, err = writeFromTemplate(dir, emptyPlan, "flake.nix", "flake.nix")
5353
if err != nil {
5454
t.Fatal("got error writing flake template:", err)
5555
}

0 commit comments

Comments
 (0)