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 [ ]) ++