Skip to content

Commit f7b01fc

Browse files
authored
Add --recompute flag for commands: shell and run (#2391)
## Summary This PR continues the work from #2013 to also add the `--recompute` flag option for `devbox run` and `devbox shell`. For some users on bad networks, this can save them annoyance and time for when they _know_ their devbox environment is up-to-date. Fixes #2315 ## How was it tested? This PR affects 3 commands: `run`, `shell` and `shellenv`. 1. For `run`: Added `"hello": "latest",` to devbox.json of this project. ``` devbox run --recompute=false -- echo "hello world" Warning: Your devbox environment may be out of date. Run with --recompute=true to update it. hello world ``` then ``` devbox run -- echo "hello world" Info: Ensuring packages are installed. ✓ Computed the Devbox environment. hello world ``` 2. For `shell`. Ran similar commands as above. 3. For `shellenv`. Followed test plan of #1963. Changed the `.envrc` to be: ``` .envrc @@ -1,7 +1,13 @@ # Automatically sets up your devbox environment whenever you cd into this # directory via our direnv integration: -eval "$(devbox generate direnv --print-envrc)" +#eval "$(devbox generate direnv --print-envrc)" + # output of `devbox generate direnv --print-envrc` to modify it +use_devbox() { + watch_file devbox.json devbox.lock + # eval "$(devbox shellenv --init-hook --install --no-refresh-alias)" + eval "$(devbox shellenv --init-hook --no-refresh-alias --recompute=false)" +} +use devbox # check out https://www.jetify.com/devbox/docs/ide_configuration/direnv/ # for more details ``` Then modified devbox.json and saw the warning get printed. --------- Co-authored-by: savil <>
1 parent cba645a commit f7b01fc

File tree

7 files changed

+101
-51
lines changed

7 files changed

+101
-51
lines changed

internal/boxcli/run.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ import (
1717
"go.jetpack.io/devbox/internal/devbox"
1818
"go.jetpack.io/devbox/internal/devbox/devopt"
1919
"go.jetpack.io/devbox/internal/redact"
20+
"go.jetpack.io/devbox/internal/ux"
2021
)
2122

2223
type runCmdFlags struct {
2324
envFlag
24-
config configFlags
25-
omitNixEnv bool
26-
pure bool
27-
listScripts bool
25+
config configFlags
26+
omitNixEnv bool
27+
pure bool
28+
listScripts bool
29+
recomputeEnv bool
2830
}
2931

3032
// runFlagDefaults are the flag default values that differ
@@ -62,6 +64,7 @@ func runCmd(defaults runFlagDefaults) *cobra.Command {
6264
"shell environment will omit the env-vars from print-dev-env",
6365
)
6466
_ = command.Flags().MarkHidden("omit-nix-env")
67+
command.Flags().BoolVar(&flags.recomputeEnv, "recompute", true, "recompute environment if needed")
6568

6669
command.ValidArgs = listScripts(command, flags)
6770

@@ -84,6 +87,7 @@ func listScripts(cmd *cobra.Command, flags runCmdFlags) []string {
8487
}
8588

8689
func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error {
90+
ctx := cmd.Context()
8791
if len(args) == 0 || flags.listScripts {
8892
scripts := listScripts(cmd, flags)
8993
if len(scripts) == 0 {
@@ -111,19 +115,32 @@ func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error {
111115
// Check the directory exists.
112116
box, err := devbox.Open(&devopt.Opts{
113117
Dir: path,
118+
Env: env,
114119
Environment: flags.config.environment,
115120
Stderr: cmd.ErrOrStderr(),
116-
Env: env,
117121
})
118122
if err != nil {
119123
return redact.Errorf("error reading devbox.json: %w", err)
120124
}
121125

122126
envOpts := devopt.EnvOptions{
123-
OmitNixEnv: flags.omitNixEnv,
124-
Pure: flags.pure,
127+
Hooks: devopt.LifecycleHooks{
128+
OnStaleState: func() {
129+
if !flags.recomputeEnv {
130+
ux.FHidableWarning(
131+
ctx,
132+
cmd.ErrOrStderr(),
133+
devbox.StateOutOfDateMessage,
134+
"with --recompute=true",
135+
)
136+
}
137+
},
138+
},
139+
OmitNixEnv: flags.omitNixEnv,
140+
Pure: flags.pure,
141+
SkipRecompute: !flags.recomputeEnv,
125142
}
126-
if err := box.RunScript(cmd.Context(), envOpts, script, scriptArgs); err != nil {
143+
if err := box.RunScript(ctx, envOpts, script, scriptArgs); err != nil {
127144
return redact.Errorf("error running script %q in Devbox: %w", script, err)
128145
}
129146
return nil

internal/boxcli/shell.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ import (
1313
"go.jetpack.io/devbox/internal/devbox"
1414
"go.jetpack.io/devbox/internal/devbox/devopt"
1515
"go.jetpack.io/devbox/internal/envir"
16+
"go.jetpack.io/devbox/internal/ux"
1617
)
1718

1819
type shellCmdFlags struct {
1920
envFlag
20-
config configFlags
21-
omitNixEnv bool
22-
printEnv bool
23-
pure bool
21+
config configFlags
22+
omitNixEnv bool
23+
printEnv bool
24+
pure bool
25+
recomputeEnv bool
2426
}
2527

2628
// shellFlagDefaults are the flag default values that differ
@@ -53,17 +55,20 @@ func shellCmd(defaults shellFlagDefaults) *cobra.Command {
5355
"shell environment will omit the env-vars from print-dev-env",
5456
)
5557
_ = command.Flags().MarkHidden("omit-nix-env")
58+
command.Flags().BoolVar(&flags.recomputeEnv, "recompute", true, "recompute environment if needed")
5659

5760
flags.config.register(command)
5861
flags.envFlag.register(command)
5962
return command
6063
}
6164

6265
func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
66+
ctx := cmd.Context()
6367
env, err := flags.Env(flags.config.path)
6468
if err != nil {
6569
return err
6670
}
71+
6772
// Check the directory exists.
6873
box, err := devbox.Open(&devopt.Opts{
6974
Dir: flags.config.path,
@@ -91,9 +96,22 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
9196
return shellInceptionErrorMsg("devbox shell")
9297
}
9398

94-
return box.Shell(cmd.Context(), devopt.EnvOptions{
95-
OmitNixEnv: flags.omitNixEnv,
96-
Pure: flags.pure,
99+
return box.Shell(ctx, devopt.EnvOptions{
100+
Hooks: devopt.LifecycleHooks{
101+
OnStaleState: func() {
102+
if !flags.recomputeEnv {
103+
ux.FHidableWarning(
104+
ctx,
105+
cmd.ErrOrStderr(),
106+
devbox.StateOutOfDateMessage,
107+
"with --recompute=true",
108+
)
109+
}
110+
},
111+
},
112+
OmitNixEnv: flags.omitNixEnv,
113+
Pure: flags.pure,
114+
SkipRecompute: !flags.recomputeEnv,
97115
})
98116
}
99117

internal/boxcli/shellenv.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,23 @@ func shellEnvFunc(
116116
}
117117

118118
envStr, err := box.EnvExports(ctx, devopt.EnvExportsOpts{
119-
DontRecomputeEnvironment: !flags.recomputeEnv,
120119
EnvOptions: devopt.EnvOptions{
120+
Hooks: devopt.LifecycleHooks{
121+
OnStaleState: func() {
122+
if !flags.recomputeEnv {
123+
ux.FHidableWarning(
124+
ctx,
125+
cmd.ErrOrStderr(),
126+
devbox.StateOutOfDateMessage,
127+
box.RefreshAliasOrCommand(),
128+
)
129+
}
130+
},
131+
},
121132
OmitNixEnv: flags.omitNixEnv,
122133
PreservePathStack: flags.preservePathStack,
123134
Pure: flags.pure,
135+
SkipRecompute: !flags.recomputeEnv,
124136
},
125137
NoRefreshAlias: flags.noRefreshAlias,
126138
RunHooks: flags.runInitHook,

internal/devbox/devbox.go

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -351,22 +351,7 @@ func (d *Devbox) EnvExports(ctx context.Context, opts devopt.EnvExportsOpts) (st
351351
var envs map[string]string
352352
var err error
353353

354-
if opts.DontRecomputeEnvironment {
355-
upToDate, _ := d.lockfile.IsUpToDateAndInstalled(isFishShell())
356-
if !upToDate {
357-
ux.FHidableWarning(
358-
ctx,
359-
d.stderr,
360-
StateOutOfDateMessage,
361-
d.refreshAliasOrCommand(),
362-
)
363-
}
364-
365-
envs, err = d.computeEnv(ctx, true /*usePrintDevEnvCache*/, opts.EnvOptions)
366-
} else {
367-
envs, err = d.ensureStateIsUpToDateAndComputeEnv(ctx, opts.EnvOptions)
368-
}
369-
354+
envs, err = d.ensureStateIsUpToDateAndComputeEnv(ctx, opts.EnvOptions)
370355
if err != nil {
371356
return "", err
372357
}
@@ -819,23 +804,35 @@ func (d *Devbox) ensureStateIsUpToDateAndComputeEnv(
819804
) (map[string]string, error) {
820805
defer debug.FunctionTimer().End()
821806

822-
// When ensureStateIsUpToDate is called with ensure=true, it always
823-
// returns early if the lockfile is up to date. So we don't need to check here
824-
if err := d.ensureStateIsUpToDate(ctx, ensure); isConnectionError(err) {
825-
if !fileutil.Exists(d.nixPrintDevEnvCachePath()) {
826-
ux.Ferrorf(
807+
upToDate, err := d.lockfile.IsUpToDateAndInstalled(isFishShell())
808+
if err != nil {
809+
return nil, err
810+
}
811+
if !upToDate {
812+
if envOpts.Hooks.OnStaleState != nil {
813+
envOpts.Hooks.OnStaleState()
814+
}
815+
}
816+
817+
if !envOpts.SkipRecompute {
818+
// When ensureStateIsUpToDate is called with ensure=true, it always
819+
// returns early if the lockfile is up to date. So we don't need to check here
820+
if err := d.ensureStateIsUpToDate(ctx, ensure); isConnectionError(err) {
821+
if !fileutil.Exists(d.nixPrintDevEnvCachePath()) {
822+
ux.Ferrorf(
823+
d.stderr,
824+
"Error connecting to the internet and no cached environment found. Aborting.\n",
825+
)
826+
return nil, err
827+
}
828+
ux.Fwarningf(
827829
d.stderr,
828-
"Error connecting to the internet and no cached environment found. Aborting.\n",
830+
"Error connecting to the internet. Will attempt to use cached environment.\n",
829831
)
832+
} else if err != nil {
833+
// Some other non connection error, just return it.
830834
return nil, err
831835
}
832-
ux.Fwarningf(
833-
d.stderr,
834-
"Error connecting to the internet. Will attempt to use cached environment.\n",
835-
)
836-
} else if err != nil {
837-
// Some other non connection error, just return it.
838-
return nil, err
839836
}
840837

841838
// Since ensureStateIsUpToDate calls computeEnv when not up do date,

internal/devbox/devopt/devboxopts.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,24 @@ type UpdateOpts struct {
6262
}
6363

6464
type EnvExportsOpts struct {
65-
DontRecomputeEnvironment bool
66-
EnvOptions EnvOptions
67-
NoRefreshAlias bool
68-
RunHooks bool
65+
EnvOptions EnvOptions
66+
NoRefreshAlias bool
67+
RunHooks bool
6968
}
7069

7170
// EnvOptions configure the Devbox Environment in the `computeEnv` function.
7271
// - These options are commonly set by flags in some Devbox commands
7372
// like `shellenv`, `shell` and `run`.
7473
// - The struct is designed for the "common case" to be zero-initialized as `EnvOptions{}`.
7574
type EnvOptions struct {
75+
Hooks LifecycleHooks
7676
OmitNixEnv bool
7777
PreservePathStack bool
7878
Pure bool
79+
SkipRecompute bool
80+
}
81+
82+
type LifecycleHooks struct {
83+
// OnStaleState is called when the Devbox state is out of date, AND it is not being recomputed.
84+
OnStaleState func()
7985
}

internal/devbox/packages.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
303303
ctx,
304304
d.stderr,
305305
StateOutOfDateMessage,
306-
d.refreshAliasOrCommand(),
306+
d.RefreshAliasOrCommand(),
307307
)
308308
}
309309

internal/devbox/refresh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (d *Devbox) isGlobal() bool {
2626
// In some cases (e.g. 2 non-global projects somehow active at the same time),
2727
// refresh might not match. This is a tiny edge case, so no need to make UX
2828
// great, we just print out the entire command.
29-
func (d *Devbox) refreshAliasOrCommand() string {
29+
func (d *Devbox) RefreshAliasOrCommand() string {
3030
if !d.isRefreshAliasSet() {
3131
// even if alias is not set, it might still be set by the end of this process
3232
return fmt.Sprintf("`%s` or `%s`", d.refreshAliasName(), d.refreshCmd())

0 commit comments

Comments
 (0)