Skip to content

Commit

Permalink
patchpkg: use devbox flake instead of local binary (#2378)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gcurtis authored Oct 24, 2024
1 parent 034a486 commit cab9ca6
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 43 deletions.
33 changes: 33 additions & 0 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package build

import (
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
Expand Down Expand Up @@ -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)
}
}
78 changes: 58 additions & 20 deletions internal/shellgen/flake_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -100,43 +102,57 @@ 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
}
}

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.
Expand Down Expand Up @@ -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)},
)
}
27 changes: 4 additions & 23 deletions internal/shellgen/tmpl/glibc-patch.nix.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@
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 }}
{{ $name }}.url = "{{ $flakeref }}";
{{- 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.<input>.<system>.<package>".
Expand Down Expand Up @@ -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 "<hash>-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 [ ]) ++
Expand Down

0 comments on commit cab9ca6

Please sign in to comment.