Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 60 additions & 11 deletions internal/impl/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,64 @@ package impl

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/samber/lo"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/planner/plansdk"
)

func (d *Devbox) AddGlobal(pkgs ...string) error {
profilePath, err := globalProfilePath()
if err != nil {
return err
}
// validate all packages exist. Don't install anything if any are missing
for _, pkg := range pkgs {
if !nix.FlakesPkgExists(plansdk.DefaultNixpkgsCommit, pkg) {
return nix.ErrPackageNotFound
}
}
var added []string
for _, pkg := range pkgs {
if err := nix.ProfileInstall(plansdk.DefaultNixpkgsCommit, pkg); err != nil {
// TODO: we should only add packages to devbox.json if we actually
// installed them in the nix profile.
return err
if err := nix.ProfileInstall(profilePath, plansdk.DefaultNixpkgsCommit, pkg); err != nil {
fmt.Fprintf(d.writer, "Error installing %s: %s", pkg, err)
} else {
fmt.Fprintf(d.writer, "%s is now installed\n", pkg)
added = append(added, pkg)
}
}
d.cfg.Packages = lo.Uniq(append(d.cfg.Packages, pkgs...))
d.cfg.Packages = lo.Uniq(append(d.cfg.Packages, added...))
return d.saveCfg()
}

func (d *Devbox) RemoveGlobal(pkgs ...string) error {
for _, pkg := range pkgs {
if err := nix.ProfileRemove(plansdk.DefaultNixpkgsCommit, pkg); err != nil {
// TODO: we should only remove packages from devbox.json if we actually
// removed them from the nix profile.
return err
profilePath, err := globalProfilePath()
if err != nil {
return err
}
if _, missing := lo.Difference(d.cfg.Packages, pkgs); len(missing) > 0 {
fmt.Fprintf(
d.writer,
"%s the following packages were not found in your global devbox.json: %s\n",
color.HiYellowString("Warning:"),
strings.Join(missing, ", "),
)
}
var removed []string
for _, pkg := range lo.Intersect(d.cfg.Packages, pkgs) {
if err := nix.ProfileRemove(profilePath, plansdk.DefaultNixpkgsCommit, pkg); err != nil {
fmt.Fprintf(d.writer, "Error removing %s: %s", pkg, err)
} else {
fmt.Fprintf(d.writer, "%s was removed\n", pkg)
removed = append(removed, pkg)
}
}
d.cfg.Packages, _ = lo.Difference(d.cfg.Packages, pkgs)
d.cfg.Packages, _ = lo.Difference(d.cfg.Packages, removed)
return d.saveCfg()
}

Expand All @@ -41,3 +72,21 @@ func (d *Devbox) PrintGlobalList() error {
}
return nil
}

func globalProfilePath() (string, error) {
configPath, err := GlobalConfigPath()
if err != nil {
return "", err
}
nixDirPath := filepath.Join(configPath, "nix")
_ = os.MkdirAll(nixDirPath, 0755)
return filepath.Join(nixDirPath, "profile"), nil
}

func GlobalConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", errors.WithStack(err)
}
return filepath.Join(home, "/.config/devbox/"), nil
}
42 changes: 7 additions & 35 deletions internal/nix/nix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package nix

import (
"bytes"
"encoding/json"
"fmt"
"os"
Expand All @@ -31,6 +30,13 @@ func PkgExists(nixpkgsCommit, pkg string) bool {
return found
}

// FlakesPkgExists returns true if the package exists in the nixpkgs commit
// using flakes. This can be removed once flakes are the default.
func FlakesPkgExists(nixpkgsCommit, pkg string) bool {
_, found := flakesPkgInfo(nixpkgsCommit, pkg)
return found
}

type Info struct {
// attribute key is different in flakes vs legacy so we should only use it
// if we know exactly which version we are using
Expand Down Expand Up @@ -158,40 +164,6 @@ func PrintDevEnv(nixShellFilePath, nixFlakesFilePath string) (*varsAndFuncs, err
return &vaf, nil
}

// ProfileInstall calls nix profile install with default profile
func ProfileInstall(nixpkgsCommit, pkg string) error {
cmd := exec.Command("nix", "profile", "install",
"nixpkgs/"+nixpkgsCommit+"#"+pkg,
"--extra-experimental-features", "nix-command flakes",
)
cmd.Env = DefaultEnv()
out, err := cmd.CombinedOutput()
if bytes.Contains(out, []byte("does not provide attribute")) {
return ErrPackageNotFound
}

return errors.WithStack(err)
}

func ProfileRemove(nixpkgsCommit, pkg string) error {
info, found := flakesPkgInfo(nixpkgsCommit, pkg)
if !found {
return ErrPackageNotFound
}
cmd := exec.Command(
"nix", "profile", "remove",
info.attributeKey,
"--extra-experimental-features", "nix-command flakes",
)
cmd.Env = DefaultEnv()
out, err := cmd.CombinedOutput()
if bytes.Contains(out, []byte("does not match any packages")) {
return ErrPackageNotInstalled
}

return errors.WithStack(err)
}

// FlakeNixpkgs returns a flakes-compatible reference to the nixpkgs registry.
// TODO savil. Ensure this works with the nixed cache service.
func FlakeNixpkgs(commit string) string {
Expand Down
36 changes: 36 additions & 0 deletions internal/nix/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nix

import (
"bufio"
"bytes"
"fmt"
"io"
"os/exec"
Expand Down Expand Up @@ -181,3 +182,38 @@ func (item *NixProfileListItem) String() string {
item.nixStorePath,
)
}

// ProfileInstall calls nix profile install with default profile
func ProfileInstall(profilePath, nixpkgsCommit, pkg string) error {
cmd := exec.Command("nix", "profile", "install",
"--profile", profilePath,
"nixpkgs/"+nixpkgsCommit+"#"+pkg,
"--extra-experimental-features", "nix-command flakes",
)
cmd.Env = DefaultEnv()
out, err := cmd.CombinedOutput()
if bytes.Contains(out, []byte("does not provide attribute")) {
return ErrPackageNotFound
}

return errors.Wrap(err, string(out))
}

func ProfileRemove(profilePath, nixpkgsCommit, pkg string) error {
info, found := flakesPkgInfo(nixpkgsCommit, pkg)
if !found {
return ErrPackageNotFound
}
cmd := exec.Command("nix", "profile", "remove",
"--profile", profilePath,
info.attributeKey,
"--extra-experimental-features", "nix-command flakes",
)
cmd.Env = DefaultEnv()
out, err := cmd.CombinedOutput()
if bytes.Contains(out, []byte("does not match any packages")) {
return ErrPackageNotInstalled
}

return errors.Wrap(err, string(out))
}