diff --git a/internal/build/build.go b/internal/build/build.go
index 9d102c8ca81..dee0ac00db3 100644
--- a/internal/build/build.go
+++ b/internal/build/build.go
@@ -4,7 +4,10 @@
package build
import (
+ "fmt"
+ "log/slog"
"os"
+ "path/filepath"
"runtime"
"strconv"
"sync"
@@ -98,3 +101,33 @@ func DashboardHostname() string {
}
return "https://cloud.jetify.com"
}
+
+// SourceDir searches for the source code directory that built the current
+// binary.
+func SourceDir() (string, error) {
+ _, file, _, ok := runtime.Caller(0)
+ if !ok || file == "" {
+ return "", fmt.Errorf("build.SourceDir: binary is missing path info")
+ }
+ slog.Debug("trying to determine path to devbox source using runtime.Caller", "path", file)
+
+ dir := filepath.Dir(file)
+ if _, err := os.Stat(dir); err != nil {
+ if filepath.IsAbs(file) {
+ return "", fmt.Errorf("build.SourceDir: path to binary source doesn't exist: %v", err)
+ }
+ return "", fmt.Errorf("build.SourceDir: binary was built with -trimpath")
+ }
+
+ for {
+ _, err := os.Stat(filepath.Join(dir, "go.mod"))
+ if err == nil {
+ slog.Debug("found devbox source directory", "path", dir)
+ return dir, nil
+ }
+ if dir == "/" || dir == "." {
+ return "", fmt.Errorf("build.SourceDir: can't find go.mod in any parent directories of %s", file)
+ }
+ dir = filepath.Dir(dir)
+ }
+}
diff --git a/internal/shellgen/flake_plan.go b/internal/shellgen/flake_plan.go
index 82072e18d10..7044e97d632 100644
--- a/internal/shellgen/flake_plan.go
+++ b/internal/shellgen/flake_plan.go
@@ -4,12 +4,12 @@ import (
"context"
"fmt"
"log/slog"
- "os"
"path/filepath"
"runtime/trace"
"slices"
"strings"
+ "go.jetpack.io/devbox/internal/build"
"go.jetpack.io/devbox/internal/devpkg"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/patchpkg"
@@ -73,9 +73,11 @@ func (f *flakePlan) needsGlibcPatch() bool {
}
type glibcPatchFlake struct {
- // DevboxExecutable is the absolute path to the Devbox binary to use as
- // the flake's builder. It must not be the wrapper script.
- DevboxExecutable string
+ // DevboxFlake provides the devbox binary that will act as the patch
+ // flake's builder. By default it's set to "github:jetify-com/devbox/" +
+ // [build.Version]. For dev builds, it's set to the local path to the
+ // Devbox source code (this Go module) if it's available.
+ DevboxFlake flake.Ref
// NixpkgsGlibcFlakeRef is a flake reference to the nixpkgs flake
// containing the new glibc package.
@@ -100,31 +102,43 @@ type glibcPatchFlake struct {
}
func newGlibcPatchFlake(nixpkgsGlibcRev string, packages []*devpkg.Package) (glibcPatchFlake, error) {
- // Get the path to the actual devbox binary (not the /usr/bin/devbox
- // wrapper) so the flake build can use it.
- exe, err := os.Executable()
- if err != nil {
- return glibcPatchFlake{}, err
- }
- exe, err = filepath.EvalSymlinks(exe)
- if err != nil {
- return glibcPatchFlake{}, err
+ patchFlake := glibcPatchFlake{
+ DevboxFlake: flake.Ref{
+ Type: flake.TypeGitHub,
+ Owner: "jetify-com",
+ Repo: "devbox",
+ Ref: build.Version,
+ },
+ NixpkgsGlibcFlakeRef: "flake:nixpkgs/" + nixpkgsGlibcRev,
}
- flake := glibcPatchFlake{
- DevboxExecutable: exe,
- NixpkgsGlibcFlakeRef: "flake:nixpkgs/" + nixpkgsGlibcRev,
+ // In dev builds, use the local Devbox flake for patching packages
+ // instead of the one on GitHub. Using build.IsDev doesn't work because
+ // DEVBOX_PROD=1 will attempt to download 0.0.0-dev from GitHub.
+ if strings.HasPrefix(build.Version, "0.0.0") {
+ src, err := build.SourceDir()
+ if err != nil {
+ slog.Error("can't find the local devbox flake for patching, falling back to the latest github release", "err", err)
+ patchFlake.DevboxFlake = flake.Ref{
+ Type: flake.TypeGitHub,
+ Owner: "jetify-com",
+ Repo: "devbox",
+ }
+ } else {
+ patchFlake.DevboxFlake = flake.Ref{Type: flake.TypePath, Path: src}
+ }
}
+
for _, pkg := range packages {
// Check to see if this is a CUDA package. If so, we need to add
// it to the flake dependencies so that we can patch other
// packages to reference it (like Python).
- relAttrPath, err := flake.systemRelativeAttrPath(pkg)
+ relAttrPath, err := patchFlake.systemRelativeAttrPath(pkg)
if err != nil {
return glibcPatchFlake{}, err
}
if strings.HasPrefix(relAttrPath, "cudaPackages") {
- if err := flake.addDependency(pkg); err != nil {
+ if err := patchFlake.addDependency(pkg); err != nil {
return glibcPatchFlake{}, err
}
}
@@ -132,11 +146,13 @@ func newGlibcPatchFlake(nixpkgsGlibcRev string, packages []*devpkg.Package) (gli
if !pkg.Patch {
continue
}
- if err := flake.addOutput(pkg); err != nil {
+ if err := patchFlake.addOutput(pkg); err != nil {
return glibcPatchFlake{}, err
}
}
- return flake, nil
+
+ slog.Debug("creating new patch flake", "flake", &patchFlake)
+ return patchFlake, nil
}
// addInput adds a flake input that provides pkg.
@@ -303,3 +319,25 @@ func (g *glibcPatchFlake) writeTo(dir string) error {
}
return writeFromTemplate(dir, g, "glibc-patch.nix", "flake.nix")
}
+
+func (g *glibcPatchFlake) LogValue() slog.Value {
+ inputs := make([]slog.Attr, 0, 2+len(g.Inputs))
+ inputs = append(inputs,
+ slog.String("devbox", g.DevboxFlake.String()),
+ slog.String("nixpkgs-glibc", g.NixpkgsGlibcFlakeRef),
+ )
+ for k, v := range g.Inputs {
+ inputs = append(inputs, slog.String(k, v))
+ }
+
+ var outputs []string
+ for _, pkg := range g.Outputs.Packages {
+ for attrPath := range pkg {
+ outputs = append(outputs, attrPath)
+ }
+ }
+ return slog.GroupValue(
+ slog.Attr{Key: "inputs", Value: slog.GroupValue(inputs...)},
+ slog.Attr{Key: "outputs", Value: slog.AnyValue(outputs)},
+ )
+}
diff --git a/internal/shellgen/tmpl/glibc-patch.nix.tmpl b/internal/shellgen/tmpl/glibc-patch.nix.tmpl
index 4d0df7aa04e..b71799e43e5 100644
--- a/internal/shellgen/tmpl/glibc-patch.nix.tmpl
+++ b/internal/shellgen/tmpl/glibc-patch.nix.tmpl
@@ -2,11 +2,7 @@
description = "Patches packages to use a newer version of glibc";
inputs = {
- local-devbox = {
- url = "path://{{ .DevboxExecutable }}";
- flake = false;
- };
-
+ devbox.url = "{{ .DevboxFlake }}";
nixpkgs-glibc.url = "{{ .NixpkgsGlibcFlakeRef }}";
{{- range $name, $flakeref := .Inputs }}
@@ -14,7 +10,7 @@
{{- end }}
};
- outputs = args@{ self, local-devbox, nixpkgs-glibc {{- range $name, $_ := .Inputs -}}, {{ $name }} {{- end }} }:
+ outputs = args@{ self, devbox, nixpkgs-glibc {{- range $name, $_ := .Inputs -}}, {{ $name }} {{- end }} }:
let
# Initialize each nixpkgs input into a new attribute set with the
# schema "pkgs...".
@@ -97,24 +93,9 @@
glibc = if isLinux then nixpkgs-glibc.legacyPackages."${system}".glibc else null;
gcc = if isLinux then nixpkgs-glibc.legacyPackages."${system}".stdenv.cc.cc.lib else null;
- # Create a package that puts the local devbox binary in the conventional
- # bin subdirectory. This also ensures that the executable is named
- # "devbox" and not "-source" (which is how Nix names the flake
- # input). Invoking it as anything other than "devbox" will break
- # testscripts which look at os.Args[0] to decide to run the real
- # entrypoint or the test entrypoint.
- devbox = derivation {
- name = "devbox";
- system = pkg.system;
- builder = "${bash}/bin/bash";
-
- # exit 0 to work around https://github.com/NixOS/nix/issues/2176
- args = [ "-c" "${coreutils}/bin/mkdir -p $out/bin && ${coreutils}/bin/cp ${local-devbox} $out/bin/devbox && exit 0" ];
- };
-
DEVBOX_DEBUG = 1;
- src = self;
- builder = "${devbox}/bin/devbox";
+
+ builder = "${devbox.packages.${system}.default}/bin/devbox";
args = [ "patch" "--restore-refs" ] ++
(if glibc != null then [ "--glibc" "${glibc}" ] else [ ]) ++
(if gcc != null then [ "--gcc" "${gcc}" ] else [ ]) ++