Skip to content

Commit 31453b6

Browse files
committed
install packages via nix build
1 parent 2ac8350 commit 31453b6

File tree

5 files changed

+119
-17
lines changed

5 files changed

+119
-17
lines changed

internal/devbox/packages.go

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,23 @@ const (
248248
// The `mode` is used for:
249249
// 1. Skipping certain operations that may not apply.
250250
// 2. User messaging to explain what operations are happening, because this function may take time to execute.
251-
func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) error {
251+
//
252+
// nolint:revive to turn off linter complaining about function complexity
253+
func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) error { //nolint:revive
252254
defer trace.StartRegion(ctx, "devboxEnsureStateIsUpToDate").End()
253255
defer debug.FunctionTimer().End()
254256

257+
if mode != ensure && !d.IsEnvEnabled() {
258+
// if mode is install/uninstall/update and we are not in a devbox environment,
259+
// then we skip some operations below for speed.
260+
// Remove the local.lock file so that we re-compute the project state when
261+
// we are in the devbox environment again.
262+
fmt.Printf("mode is not ensure and env is not enabled, so will remove local.lock on exit\n")
263+
264+
defer func() { _ = d.lockfile.RemoveLocal() }()
265+
// return nil
266+
}
267+
255268
// if mode is install or uninstall, then we need to update the nix-profile
256269
// and lockfile, so we must continue below.
257270
upToDate, err := d.lockfile.IsUpToDateAndInstalled()
@@ -262,10 +275,12 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
262275
if mode == ensure {
263276
// if mode is ensure and we are up to date, then we can skip the rest
264277
if upToDate {
278+
fmt.Printf("state is up to date. Returning early\n")
265279
return nil
266280
}
267281
fmt.Fprintln(d.stderr, "Ensuring packages are installed.")
268282
}
283+
fmt.Printf("state is not up to date. Continuing...\n")
269284

270285
// Validate packages. Must be run up-front and definitely prior to computeEnv
271286
// and syncNixProfile below that will evaluate the flake and may give
@@ -285,6 +300,9 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
285300
return err
286301
}
287302

303+
fmt.Printf("mode is %s\n", mode)
304+
fmt.Printf("env is enabled: %v\n", d.IsEnvEnabled())
305+
288306
if err := shellgen.GenerateForPrintEnv(ctx, d); err != nil {
289307
return err
290308
}
@@ -293,24 +311,37 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
293311
return err
294312
}
295313

296-
// Always re-compute print-dev-env to ensure all packages are installed, and
297-
// the correct set of packages are reflected in the nix-profile below.
298-
env, err := d.computeEnv(ctx, false /*usePrintDevEnvCache*/)
299-
if err != nil {
300-
return err
301-
}
314+
// The steps contained in this if-block of computeEnv and syncNixProfile are a tad
315+
// slow. So, we only do it if we are in a devbox environment, or if mode is ensure.
316+
if mode == ensure || d.IsEnvEnabled() {
317+
// Re-compute print-dev-env to ensure all packages are installed, and
318+
// the correct set of packages are reflected in the nix-profile below.
319+
env, err := d.computeEnv(ctx, false /*usePrintDevEnvCache*/)
320+
if err != nil {
321+
return err
322+
}
302323

303-
// Ensure the nix profile has the packages from the flake.
304-
buildInputs := []string{}
305-
if env["buildInputs"] != "" {
306-
// env["buildInputs"] can be empty string if there are no packages in the project
307-
// if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry
308-
buildInputs = strings.Split(env["buildInputs"], " ")
309-
}
310-
if err := d.syncNixProfile(ctx, buildInputs); err != nil {
311-
return err
324+
// Ensure the nix profile has the packages from the flake.
325+
buildInputs := []string{}
326+
if env["buildInputs"] != "" {
327+
// env["buildInputs"] can be empty string if there are no packages in the project
328+
// if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry
329+
buildInputs = strings.Split(env["buildInputs"], " ")
330+
}
331+
if err := d.syncNixProfile(ctx, buildInputs); err != nil {
332+
return err
333+
}
334+
335+
} else if mode == install || mode == update {
336+
// Else: if we are not in a devbox environment, and we are installing or updating
337+
// then we must ensure the new nix packages are in the nix store. This way, the
338+
// next time we enter a devbox environment, we will have the packages available locally.
339+
if err := d.installNixPackagesToStore(ctx); err != nil {
340+
return err
341+
}
312342
}
313343

344+
fmt.Printf("calling lockfile.tidy\n")
314345
// Ensure we clean out packages that are no longer needed.
315346
d.lockfile.Tidy()
316347

@@ -395,6 +426,33 @@ func (d *Devbox) InstallRunXPackages(ctx context.Context) error {
395426
return nil
396427
}
397428

429+
// installNixPackagesToStore will install all the packages in the nix store, if
430+
// mode is install or update, and we're not in a devbox environment.
431+
// This is done by running `nix build` on the flake
432+
func (d *Devbox) installNixPackagesToStore(ctx context.Context) error {
433+
packages, err := d.AllInstallablePackages()
434+
if err != nil {
435+
return err
436+
}
437+
packages = lo.Filter(packages, devpkg.IsNix) // Remove non-nix packages from the list
438+
439+
installables := []string{}
440+
for _, pkg := range packages {
441+
i, err := pkg.Installable()
442+
if err != nil {
443+
return err
444+
}
445+
installables = append(installables, i)
446+
}
447+
448+
if len(installables) == 0 {
449+
return nil
450+
}
451+
452+
// --no-link to avoid generating the result objects
453+
return nix.Build(ctx, []string{"--no-link"}, installables...)
454+
}
455+
398456
// validatePackagesToBeInstalled will ensure that packages are available to be installed
399457
// in the user's current system.
400458
func (d *Devbox) validatePackagesToBeInstalled(ctx context.Context) error {

internal/lock/local.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package lock
55

66
import (
77
"errors"
8+
"fmt"
89
"io/fs"
10+
"os"
911
"path/filepath"
1012

1113
"go.jetpack.io/devbox/internal/build"
@@ -49,6 +51,8 @@ func isLocalUpToDate(project devboxProject) (bool, error) {
4951
}
5052

5153
func updateLocal(project devboxProject) error {
54+
fmt.Printf("updating local.lock\n")
55+
// debug.PrintStack()
5256
l, err := readLocal(project)
5357
if err != nil {
5458
return err
@@ -74,6 +78,11 @@ func readLocal(project devboxProject) (*localLockFile, error) {
7478
return lockFile, nil
7579
}
7680

81+
func removeLocal(project devboxProject) error {
82+
// RemoveAll to avoid error in case file does not exist.
83+
return os.RemoveAll(localLockFilePath(project))
84+
}
85+
7786
func forProject(project devboxProject) (*localLockFile, error) {
7887
configHash, err := project.ConfigHash()
7988
if err != nil {

internal/lock/lockfile.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (f *File) Add(pkgs ...string) error {
5555
return err
5656
}
5757
}
58+
fmt.Printf("calling lockfile.Add with pkgs: %v\n", pkgs)
5859
return f.Save()
5960
}
6061

@@ -176,6 +177,11 @@ func (f *File) isDirty() (bool, error) {
176177
return currentHash != filesystemHash, nil
177178
}
178179

180+
// RemoveLocal removes the local.lock file
181+
func (f *File) RemoveLocal() error {
182+
return removeLocal(f.devboxProject)
183+
}
184+
179185
func lockFilePath(project devboxProject) string {
180186
return filepath.Join(project.ProjectDir(), "devbox.lock")
181187
}

internal/nix/build.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package nix
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os/exec"
7+
8+
"github.com/pkg/errors"
9+
)
10+
11+
func Build(ctx context.Context, flags []string, installables ...string) error {
12+
// --impure is required for allowUnfreeEnv to work.
13+
cmd := commandContext(ctx, "build", "--impure")
14+
cmd.Args = append(cmd.Args, flags...)
15+
cmd.Args = append(cmd.Args, installables...)
16+
// We need to allow Unfree packages to be installed. We choose to not also add os.Environ() to the environment
17+
// to keep the command as pure as possible, even though we must pass --impure to nix build.
18+
cmd.Env = allowUnfreeEnv([]string{}) // allowUnfreeEnv(os.Environ())
19+
20+
out, err := cmd.Output()
21+
if err != nil {
22+
if exitErr := (&exec.ExitError{}); errors.As(err, &exitErr) {
23+
fmt.Printf("Exit code: %d, output: %s\n", exitErr.ExitCode(), exitErr.Stderr)
24+
}
25+
return err
26+
}
27+
fmt.Printf("Ran nix build, output: %s\n", out)
28+
return nil
29+
}

testscripts/add/global_add.test.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
! exec vim --version
55
exec devbox global add ripgrep vim
66

7-
exec devbox global shellenv
7+
exec devbox global shellenv --recompute
88
source.path
99
exec rg --version
1010
exec vim --version

0 commit comments

Comments
 (0)