Skip to content

Commit d564fe2

Browse files
mikeland73savil
andauthored
[global] global shellenv should not recompute env (#1534)
## Summary This causes the default behavior for `devbox global shellenv` to no longer recompute environment. It adds a new flag: `devbox global shellenv -r` which causes the recompute if needed (i.e. not cached) Recomputing the environment entails two steps: * Ensuring everything is installed * Running print-dev-env It also removed a bunch of `ensurePackagesAreInstalled` calls that are now handled by `nixEnv()`. Note that in all cases, we were calling `ensurePackagesAreInstalled(ensure)` followed by `nixEnv()` ## How was it tested? * Modified devbox.json manually * Opened new tab * Saw message: ```bash Info: Your devbox environment may be out of date. Please run eval "$(devbox global shellenv -r)" ``` * Ran `eval "$(devbox global shellenv -r)"` * Opened another tab and did not see message. --------- Co-authored-by: Savil Srivastava <[email protected]>
1 parent b2a8835 commit d564fe2

File tree

7 files changed

+76
-44
lines changed

7 files changed

+76
-44
lines changed

devbox.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type Devbox interface {
2323
Install(ctx context.Context) error
2424
IsEnvEnabled() bool
2525
ListScripts() []string
26-
NixEnv(ctx context.Context, includeHooks bool) (string, error)
26+
NixEnv(ctx context.Context, opts devopt.NixEnvOpts) (string, error)
2727
PackageNames() []string
2828
ProjectDir() string
2929
Pull(ctx context.Context, opts devopt.PullboxOpts) error

internal/boxcli/global.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import (
1414
"go.jetpack.io/devbox/internal/ux"
1515
)
1616

17+
type globalShellEnvCmdFlags struct {
18+
recompute bool
19+
}
20+
1721
func globalCmd() *cobra.Command {
22+
globalShellEnvCmdFlags := globalShellEnvCmdFlags{}
1823
globalCmd := &cobra.Command{}
1924
persistentPreRunE := setGlobalConfigForDelegatedCommands(globalCmd)
2025
*globalCmd = cobra.Command{
@@ -28,6 +33,12 @@ func globalCmd() *cobra.Command {
2833
PersistentPostRunE: ensureGlobalEnvEnabled,
2934
}
3035

36+
shellEnv := shellEnvCmd(&globalShellEnvCmdFlags.recompute)
37+
shellEnv.Flags().BoolVar(
38+
&globalShellEnvCmdFlags.recompute, "recompute", false,
39+
"Recompute environment if needed",
40+
)
41+
3142
addCommandAndHideConfigFlag(globalCmd, addCmd())
3243
addCommandAndHideConfigFlag(globalCmd, installCmd())
3344
addCommandAndHideConfigFlag(globalCmd, pathCmd())
@@ -36,7 +47,7 @@ func globalCmd() *cobra.Command {
3647
addCommandAndHideConfigFlag(globalCmd, removeCmd())
3748
addCommandAndHideConfigFlag(globalCmd, runCmd())
3849
addCommandAndHideConfigFlag(globalCmd, servicesCmd(persistentPreRunE))
39-
addCommandAndHideConfigFlag(globalCmd, shellEnvCmd())
50+
addCommandAndHideConfigFlag(globalCmd, shellEnv)
4051
addCommandAndHideConfigFlag(globalCmd, updateCmd())
4152

4253
// Create list for non-global? Mike: I want it :)

internal/boxcli/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212
"time"
1313

14+
"github.com/samber/lo"
1415
"github.com/spf13/cobra"
1516

1617
"go.jetpack.io/devbox/internal/boxcli/featureflag"
@@ -72,7 +73,8 @@ func RootCmd() *cobra.Command {
7273
command.AddCommand(servicesCmd())
7374
command.AddCommand(setupCmd())
7475
command.AddCommand(shellCmd())
75-
command.AddCommand(shellEnvCmd())
76+
// False to avoid recomputing the env in global shellenv:
77+
command.AddCommand(shellEnvCmd(lo.ToPtr(false)))
7678
command.AddCommand(updateCmd())
7779
command.AddCommand(versionCmd())
7880
// Preview commands

internal/boxcli/shell.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
6666
if flags.printEnv {
6767
// false for includeHooks is because init hooks is not compatible with .envrc files generated
6868
// by versions older than 0.4.6
69-
script, err := box.NixEnv(cmd.Context(), false /*includeHooks*/)
69+
script, err := box.NixEnv(cmd.Context(), devopt.NixEnvOpts{})
7070
if err != nil {
7171
return err
7272
}

internal/boxcli/shellenv.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ type shellEnvCmdFlags struct {
2222
preservePathStack bool
2323
}
2424

25-
func shellEnvCmd() *cobra.Command {
25+
func shellEnvCmd(recomputeEnvIfNeeded *bool) *cobra.Command {
2626
flags := shellEnvCmdFlags{}
2727
command := &cobra.Command{
2828
Use: "shellenv",
2929
Short: "Print shell commands that add Devbox packages to your PATH",
3030
Args: cobra.ExactArgs(0),
3131
PreRunE: ensureNixInstalled,
3232
RunE: func(cmd *cobra.Command, args []string) error {
33-
s, err := shellEnvFunc(cmd, flags)
33+
s, err := shellEnvFunc(cmd, flags, *recomputeEnvIfNeeded)
3434
if err != nil {
3535
return err
3636
}
@@ -63,7 +63,11 @@ func shellEnvCmd() *cobra.Command {
6363
return command
6464
}
6565

66-
func shellEnvFunc(cmd *cobra.Command, flags shellEnvCmdFlags) (string, error) {
66+
func shellEnvFunc(
67+
cmd *cobra.Command,
68+
flags shellEnvCmdFlags,
69+
recomputeEnvIfNeeded bool,
70+
) (string, error) {
6771
env, err := flags.Env(flags.config.path)
6872
if err != nil {
6973
return "", err
@@ -85,7 +89,10 @@ func shellEnvFunc(cmd *cobra.Command, flags shellEnvCmdFlags) (string, error) {
8589
}
8690
}
8791

88-
envStr, err := box.NixEnv(cmd.Context(), flags.runInitHook)
92+
envStr, err := box.NixEnv(cmd.Context(), devopt.NixEnvOpts{
93+
DontRecomputeEnvironment: !recomputeEnvIfNeeded,
94+
RunHooks: flags.runInitHook,
95+
})
8996
if err != nil {
9097
return "", err
9198
}

internal/impl/devbox.go

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,22 @@ func (d *Devbox) Shell(ctx context.Context) error {
168168
ctx, task := trace.NewTask(ctx, "devboxShell")
169169
defer task.End()
170170

171-
if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
172-
return err
173-
}
174-
fmt.Fprintln(d.stderr, "Starting a devbox shell...")
175-
176171
profileDir, err := d.profilePath()
177172
if err != nil {
178173
return err
179174
}
180175

181-
envs, err := d.nixEnv(ctx)
176+
envs, err := d.ensurePackagesAreInstalledAndComputeEnv(ctx)
182177
if err != nil {
183178
return err
184179
}
180+
181+
fmt.Fprintln(d.stderr, "Starting a devbox shell...")
182+
185183
// Used to determine whether we're inside a shell (e.g. to prevent shell inception)
184+
// TODO: This is likely obsolete but we need to decide what happens when
185+
// the user does shell-ception. One option is to leave the current shell and
186+
// join a new one (that way they are not in nested shells.)
186187
envs[envir.DevboxShellEnabled] = "1"
187188

188189
if err = createDevboxSymlink(d); err != nil {
@@ -210,15 +211,11 @@ func (d *Devbox) RunScript(ctx context.Context, cmdName string, cmdArgs []string
210211
ctx, task := trace.NewTask(ctx, "devboxRun")
211212
defer task.End()
212213

213-
if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
214-
return err
215-
}
216-
217214
if err := shellgen.WriteScriptsToFiles(d); err != nil {
218215
return err
219216
}
220217

221-
env, err := d.nixEnv(ctx)
218+
env, err := d.ensurePackagesAreInstalledAndComputeEnv(ctx)
222219
if err != nil {
223220
return err
224221
}
@@ -274,22 +271,39 @@ func (d *Devbox) ListScripts() []string {
274271
return keys
275272
}
276273

277-
func (d *Devbox) NixEnv(ctx context.Context, includeHooks bool) (string, error) {
274+
func (d *Devbox) NixEnv(ctx context.Context, opts devopt.NixEnvOpts) (string, error) {
278275
ctx, task := trace.NewTask(ctx, "devboxNixEnv")
279276
defer task.End()
280277

281-
if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
282-
return "", err
278+
var envs map[string]string
279+
var err error
280+
281+
if opts.DontRecomputeEnvironment {
282+
upToDate, _ := d.lockfile.IsUpToDateAndInstalled()
283+
if !upToDate {
284+
cmd := `eval "$(devbox global shellenv --recompute)"`
285+
if strings.HasSuffix(os.Getenv("SHELL"), "fish") {
286+
cmd = `devbox global shellenv --recompute | source`
287+
}
288+
ux.Finfo(
289+
d.stderr,
290+
"Your devbox environment may be out of date. Please run \n\n%s\n\n",
291+
cmd,
292+
)
293+
}
294+
295+
envs, err = d.computeNixEnv(ctx, true /*usePrintDevEnvCache*/)
296+
} else {
297+
envs, err = d.ensurePackagesAreInstalledAndComputeEnv(ctx)
283298
}
284299

285-
envs, err := d.nixEnv(ctx)
286300
if err != nil {
287301
return "", err
288302
}
289303

290304
envStr := exportify(envs)
291305

292-
if includeHooks {
306+
if opts.RunHooks {
293307
hooksStr := ". " + shellgen.ScriptPath(d.ProjectDir(), shellgen.HooksFilename)
294308
envStr = fmt.Sprintf("%s\n%s;\n", envStr, hooksStr)
295309
}
@@ -301,12 +315,7 @@ func (d *Devbox) EnvVars(ctx context.Context) ([]string, error) {
301315
ctx, task := trace.NewTask(ctx, "devboxEnvVars")
302316
defer task.End()
303317
// this only returns env variables for the shell environment excluding hooks
304-
// and excluding "export " prefix in "export key=value" format
305-
if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
306-
return nil, err
307-
}
308-
309-
envs, err := d.nixEnv(ctx)
318+
envs, err := d.ensurePackagesAreInstalledAndComputeEnv(ctx)
310319
if err != nil {
311320
return nil, err
312321
}
@@ -903,25 +912,23 @@ func (d *Devbox) computeNixEnv(ctx context.Context, usePrintDevEnvCache bool) (m
903912
return env, d.addHashToEnv(env)
904913
}
905914

906-
// nixEnv is a wrapper around computeNixEnv that returns a cached result if
907-
// it has previously computed and the local lock file is up to date.
908-
// Note that this is in-memory cache of the final environment, and not the same
909-
// as the nix print-dev-env cache which is stored in a file.
910-
func (d *Devbox) nixEnv(ctx context.Context) (map[string]string, error) {
915+
// ensurePackagesAreInstalledAndComputeEnv does what it says :P
916+
func (d *Devbox) ensurePackagesAreInstalledAndComputeEnv(
917+
ctx context.Context,
918+
) (map[string]string, error) {
911919
defer debug.FunctionTimer().End()
912920

913-
usePrintDevEnvCache := false
914-
915-
// If lockfile is up-to-date, we can use the print-dev-env cache.
916-
upToDate, err := d.lockfile.IsUpToDateAndInstalled()
917-
if err != nil {
921+
// When ensurePackagesAreInstalled is called with ensure=true, it always
922+
// returns early if the lockfile is up to date. So we don't need to check here
923+
if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
918924
return nil, err
919925
}
920-
if upToDate {
921-
usePrintDevEnvCache = true
922-
}
923926

924-
return d.computeNixEnv(ctx, usePrintDevEnvCache)
927+
// Since ensurePackagesAreInstalled calls computeNixEnv when not up do date,
928+
// it's ok to use usePrintDevEnvCache=true here always. This does end up
929+
// doing some non-nix work twice if lockfile is not up to date.
930+
// TODO: Improve this to avoid extra work.
931+
return d.computeNixEnv(ctx, true)
925932
}
926933

927934
func (d *Devbox) nixPrintDevEnvCachePath() string {

internal/impl/devopt/devboxopts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ type UpdateOpts struct {
4343
Pkgs []string
4444
IgnoreMissingPackages bool
4545
}
46+
47+
type NixEnvOpts struct {
48+
DontRecomputeEnvironment bool
49+
RunHooks bool
50+
}

0 commit comments

Comments
 (0)