Skip to content

Commit cab9ca6

Browse files
authored
patchpkg: use devbox flake instead of local binary (#2378)
Update the patching flake to use the devbox flake (added in 37c36a1) instead of copying the binary manually. - It uses the flakeref `"github:jetify-com/devbox/" + build.Version` to get the same version of Devbox that's currently running. - For dev builds, it attempts to find the devbox source and use the local flake instead. When Devbox needs to patch a package, it generates a flake that calls `devbox patch` as its builder. Because flake builds are sandboxed, we need a way of getting devbox into the Nix store. Previously, we were just copying the currently running devbox executable into the Nix store and using that. However, Devbox isn't actually a static binary (we don't build with CGO_ENABLED=0). This causes `devbox patch` to fail because the flake is isolated from the system's linker.
1 parent 034a486 commit cab9ca6

File tree

3 files changed

+95
-43
lines changed

3 files changed

+95
-43
lines changed

internal/build/build.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
package build
55

66
import (
7+
"fmt"
8+
"log/slog"
79
"os"
10+
"path/filepath"
811
"runtime"
912
"strconv"
1013
"sync"
@@ -98,3 +101,33 @@ func DashboardHostname() string {
98101
}
99102
return "https://cloud.jetify.com"
100103
}
104+
105+
// SourceDir searches for the source code directory that built the current
106+
// binary.
107+
func SourceDir() (string, error) {
108+
_, file, _, ok := runtime.Caller(0)
109+
if !ok || file == "" {
110+
return "", fmt.Errorf("build.SourceDir: binary is missing path info")
111+
}
112+
slog.Debug("trying to determine path to devbox source using runtime.Caller", "path", file)
113+
114+
dir := filepath.Dir(file)
115+
if _, err := os.Stat(dir); err != nil {
116+
if filepath.IsAbs(file) {
117+
return "", fmt.Errorf("build.SourceDir: path to binary source doesn't exist: %v", err)
118+
}
119+
return "", fmt.Errorf("build.SourceDir: binary was built with -trimpath")
120+
}
121+
122+
for {
123+
_, err := os.Stat(filepath.Join(dir, "go.mod"))
124+
if err == nil {
125+
slog.Debug("found devbox source directory", "path", dir)
126+
return dir, nil
127+
}
128+
if dir == "/" || dir == "." {
129+
return "", fmt.Errorf("build.SourceDir: can't find go.mod in any parent directories of %s", file)
130+
}
131+
dir = filepath.Dir(dir)
132+
}
133+
}

internal/shellgen/flake_plan.go

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7-
"os"
87
"path/filepath"
98
"runtime/trace"
109
"slices"
1110
"strings"
1211

12+
"go.jetpack.io/devbox/internal/build"
1313
"go.jetpack.io/devbox/internal/devpkg"
1414
"go.jetpack.io/devbox/internal/nix"
1515
"go.jetpack.io/devbox/internal/patchpkg"
@@ -73,9 +73,11 @@ func (f *flakePlan) needsGlibcPatch() bool {
7373
}
7474

7575
type glibcPatchFlake struct {
76-
// DevboxExecutable is the absolute path to the Devbox binary to use as
77-
// the flake's builder. It must not be the wrapper script.
78-
DevboxExecutable string
76+
// DevboxFlake provides the devbox binary that will act as the patch
77+
// flake's builder. By default it's set to "github:jetify-com/devbox/" +
78+
// [build.Version]. For dev builds, it's set to the local path to the
79+
// Devbox source code (this Go module) if it's available.
80+
DevboxFlake flake.Ref
7981

8082
// NixpkgsGlibcFlakeRef is a flake reference to the nixpkgs flake
8183
// containing the new glibc package.
@@ -100,43 +102,57 @@ type glibcPatchFlake struct {
100102
}
101103

102104
func newGlibcPatchFlake(nixpkgsGlibcRev string, packages []*devpkg.Package) (glibcPatchFlake, error) {
103-
// Get the path to the actual devbox binary (not the /usr/bin/devbox
104-
// wrapper) so the flake build can use it.
105-
exe, err := os.Executable()
106-
if err != nil {
107-
return glibcPatchFlake{}, err
108-
}
109-
exe, err = filepath.EvalSymlinks(exe)
110-
if err != nil {
111-
return glibcPatchFlake{}, err
105+
patchFlake := glibcPatchFlake{
106+
DevboxFlake: flake.Ref{
107+
Type: flake.TypeGitHub,
108+
Owner: "jetify-com",
109+
Repo: "devbox",
110+
Ref: build.Version,
111+
},
112+
NixpkgsGlibcFlakeRef: "flake:nixpkgs/" + nixpkgsGlibcRev,
112113
}
113114

114-
flake := glibcPatchFlake{
115-
DevboxExecutable: exe,
116-
NixpkgsGlibcFlakeRef: "flake:nixpkgs/" + nixpkgsGlibcRev,
115+
// In dev builds, use the local Devbox flake for patching packages
116+
// instead of the one on GitHub. Using build.IsDev doesn't work because
117+
// DEVBOX_PROD=1 will attempt to download 0.0.0-dev from GitHub.
118+
if strings.HasPrefix(build.Version, "0.0.0") {
119+
src, err := build.SourceDir()
120+
if err != nil {
121+
slog.Error("can't find the local devbox flake for patching, falling back to the latest github release", "err", err)
122+
patchFlake.DevboxFlake = flake.Ref{
123+
Type: flake.TypeGitHub,
124+
Owner: "jetify-com",
125+
Repo: "devbox",
126+
}
127+
} else {
128+
patchFlake.DevboxFlake = flake.Ref{Type: flake.TypePath, Path: src}
129+
}
117130
}
131+
118132
for _, pkg := range packages {
119133
// Check to see if this is a CUDA package. If so, we need to add
120134
// it to the flake dependencies so that we can patch other
121135
// packages to reference it (like Python).
122-
relAttrPath, err := flake.systemRelativeAttrPath(pkg)
136+
relAttrPath, err := patchFlake.systemRelativeAttrPath(pkg)
123137
if err != nil {
124138
return glibcPatchFlake{}, err
125139
}
126140
if strings.HasPrefix(relAttrPath, "cudaPackages") {
127-
if err := flake.addDependency(pkg); err != nil {
141+
if err := patchFlake.addDependency(pkg); err != nil {
128142
return glibcPatchFlake{}, err
129143
}
130144
}
131145

132146
if !pkg.Patch {
133147
continue
134148
}
135-
if err := flake.addOutput(pkg); err != nil {
149+
if err := patchFlake.addOutput(pkg); err != nil {
136150
return glibcPatchFlake{}, err
137151
}
138152
}
139-
return flake, nil
153+
154+
slog.Debug("creating new patch flake", "flake", &patchFlake)
155+
return patchFlake, nil
140156
}
141157

142158
// addInput adds a flake input that provides pkg.
@@ -303,3 +319,25 @@ func (g *glibcPatchFlake) writeTo(dir string) error {
303319
}
304320
return writeFromTemplate(dir, g, "glibc-patch.nix", "flake.nix")
305321
}
322+
323+
func (g *glibcPatchFlake) LogValue() slog.Value {
324+
inputs := make([]slog.Attr, 0, 2+len(g.Inputs))
325+
inputs = append(inputs,
326+
slog.String("devbox", g.DevboxFlake.String()),
327+
slog.String("nixpkgs-glibc", g.NixpkgsGlibcFlakeRef),
328+
)
329+
for k, v := range g.Inputs {
330+
inputs = append(inputs, slog.String(k, v))
331+
}
332+
333+
var outputs []string
334+
for _, pkg := range g.Outputs.Packages {
335+
for attrPath := range pkg {
336+
outputs = append(outputs, attrPath)
337+
}
338+
}
339+
return slog.GroupValue(
340+
slog.Attr{Key: "inputs", Value: slog.GroupValue(inputs...)},
341+
slog.Attr{Key: "outputs", Value: slog.AnyValue(outputs)},
342+
)
343+
}

internal/shellgen/tmpl/glibc-patch.nix.tmpl

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@
22
description = "Patches packages to use a newer version of glibc";
33

44
inputs = {
5-
local-devbox = {
6-
url = "path://{{ .DevboxExecutable }}";
7-
flake = false;
8-
};
9-
5+
devbox.url = "{{ .DevboxFlake }}";
106
nixpkgs-glibc.url = "{{ .NixpkgsGlibcFlakeRef }}";
117

128
{{- range $name, $flakeref := .Inputs }}
139
{{ $name }}.url = "{{ $flakeref }}";
1410
{{- end }}
1511
};
1612

17-
outputs = args@{ self, local-devbox, nixpkgs-glibc {{- range $name, $_ := .Inputs -}}, {{ $name }} {{- end }} }:
13+
outputs = args@{ self, devbox, nixpkgs-glibc {{- range $name, $_ := .Inputs -}}, {{ $name }} {{- end }} }:
1814
let
1915
# Initialize each nixpkgs input into a new attribute set with the
2016
# schema "pkgs.<input>.<system>.<package>".
@@ -97,24 +93,9 @@
9793
glibc = if isLinux then nixpkgs-glibc.legacyPackages."${system}".glibc else null;
9894
gcc = if isLinux then nixpkgs-glibc.legacyPackages."${system}".stdenv.cc.cc.lib else null;
9995

100-
# Create a package that puts the local devbox binary in the conventional
101-
# bin subdirectory. This also ensures that the executable is named
102-
# "devbox" and not "<hash>-source" (which is how Nix names the flake
103-
# input). Invoking it as anything other than "devbox" will break
104-
# testscripts which look at os.Args[0] to decide to run the real
105-
# entrypoint or the test entrypoint.
106-
devbox = derivation {
107-
name = "devbox";
108-
system = pkg.system;
109-
builder = "${bash}/bin/bash";
110-
111-
# exit 0 to work around https://github.com/NixOS/nix/issues/2176
112-
args = [ "-c" "${coreutils}/bin/mkdir -p $out/bin && ${coreutils}/bin/cp ${local-devbox} $out/bin/devbox && exit 0" ];
113-
};
114-
11596
DEVBOX_DEBUG = 1;
116-
src = self;
117-
builder = "${devbox}/bin/devbox";
97+
98+
builder = "${devbox.packages.${system}.default}/bin/devbox";
11899
args = [ "patch" "--restore-refs" ] ++
119100
(if glibc != null then [ "--glibc" "${glibc}" ] else [ ]) ++
120101
(if gcc != null then [ "--gcc" "${gcc}" ] else [ ]) ++

0 commit comments

Comments
 (0)