Skip to content

Commit

Permalink
Add --recompute flag for commands: shell and run (#2391)
Browse files Browse the repository at this point in the history
## 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 <>
  • Loading branch information
savil authored Nov 5, 2024
1 parent cba645a commit f7b01fc
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 51 deletions.
33 changes: 25 additions & 8 deletions internal/boxcli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ import (
"go.jetpack.io/devbox/internal/devbox"
"go.jetpack.io/devbox/internal/devbox/devopt"
"go.jetpack.io/devbox/internal/redact"
"go.jetpack.io/devbox/internal/ux"
)

type runCmdFlags struct {
envFlag
config configFlags
omitNixEnv bool
pure bool
listScripts bool
config configFlags
omitNixEnv bool
pure bool
listScripts bool
recomputeEnv bool
}

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

command.ValidArgs = listScripts(command, flags)

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

func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error {
ctx := cmd.Context()
if len(args) == 0 || flags.listScripts {
scripts := listScripts(cmd, flags)
if len(scripts) == 0 {
Expand Down Expand Up @@ -111,19 +115,32 @@ func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error {
// Check the directory exists.
box, err := devbox.Open(&devopt.Opts{
Dir: path,
Env: env,
Environment: flags.config.environment,
Stderr: cmd.ErrOrStderr(),
Env: env,
})
if err != nil {
return redact.Errorf("error reading devbox.json: %w", err)
}

envOpts := devopt.EnvOptions{
OmitNixEnv: flags.omitNixEnv,
Pure: flags.pure,
Hooks: devopt.LifecycleHooks{
OnStaleState: func() {
if !flags.recomputeEnv {
ux.FHidableWarning(
ctx,
cmd.ErrOrStderr(),
devbox.StateOutOfDateMessage,
"with --recompute=true",
)
}
},
},
OmitNixEnv: flags.omitNixEnv,
Pure: flags.pure,
SkipRecompute: !flags.recomputeEnv,
}
if err := box.RunScript(cmd.Context(), envOpts, script, scriptArgs); err != nil {
if err := box.RunScript(ctx, envOpts, script, scriptArgs); err != nil {
return redact.Errorf("error running script %q in Devbox: %w", script, err)
}
return nil
Expand Down
32 changes: 25 additions & 7 deletions internal/boxcli/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import (
"go.jetpack.io/devbox/internal/devbox"
"go.jetpack.io/devbox/internal/devbox/devopt"
"go.jetpack.io/devbox/internal/envir"
"go.jetpack.io/devbox/internal/ux"
)

type shellCmdFlags struct {
envFlag
config configFlags
omitNixEnv bool
printEnv bool
pure bool
config configFlags
omitNixEnv bool
printEnv bool
pure bool
recomputeEnv bool
}

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

flags.config.register(command)
flags.envFlag.register(command)
return command
}

func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
ctx := cmd.Context()
env, err := flags.Env(flags.config.path)
if err != nil {
return err
}

// Check the directory exists.
box, err := devbox.Open(&devopt.Opts{
Dir: flags.config.path,
Expand Down Expand Up @@ -91,9 +96,22 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
return shellInceptionErrorMsg("devbox shell")
}

return box.Shell(cmd.Context(), devopt.EnvOptions{
OmitNixEnv: flags.omitNixEnv,
Pure: flags.pure,
return box.Shell(ctx, devopt.EnvOptions{
Hooks: devopt.LifecycleHooks{
OnStaleState: func() {
if !flags.recomputeEnv {
ux.FHidableWarning(
ctx,
cmd.ErrOrStderr(),
devbox.StateOutOfDateMessage,
"with --recompute=true",
)
}
},
},
OmitNixEnv: flags.omitNixEnv,
Pure: flags.pure,
SkipRecompute: !flags.recomputeEnv,
})
}

Expand Down
14 changes: 13 additions & 1 deletion internal/boxcli/shellenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,23 @@ func shellEnvFunc(
}

envStr, err := box.EnvExports(ctx, devopt.EnvExportsOpts{
DontRecomputeEnvironment: !flags.recomputeEnv,
EnvOptions: devopt.EnvOptions{
Hooks: devopt.LifecycleHooks{
OnStaleState: func() {
if !flags.recomputeEnv {
ux.FHidableWarning(
ctx,
cmd.ErrOrStderr(),
devbox.StateOutOfDateMessage,
box.RefreshAliasOrCommand(),
)
}
},
},
OmitNixEnv: flags.omitNixEnv,
PreservePathStack: flags.preservePathStack,
Pure: flags.pure,
SkipRecompute: !flags.recomputeEnv,
},
NoRefreshAlias: flags.noRefreshAlias,
RunHooks: flags.runInitHook,
Expand Down
55 changes: 26 additions & 29 deletions internal/devbox/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,22 +351,7 @@ func (d *Devbox) EnvExports(ctx context.Context, opts devopt.EnvExportsOpts) (st
var envs map[string]string
var err error

if opts.DontRecomputeEnvironment {
upToDate, _ := d.lockfile.IsUpToDateAndInstalled(isFishShell())
if !upToDate {
ux.FHidableWarning(
ctx,
d.stderr,
StateOutOfDateMessage,
d.refreshAliasOrCommand(),
)
}

envs, err = d.computeEnv(ctx, true /*usePrintDevEnvCache*/, opts.EnvOptions)
} else {
envs, err = d.ensureStateIsUpToDateAndComputeEnv(ctx, opts.EnvOptions)
}

envs, err = d.ensureStateIsUpToDateAndComputeEnv(ctx, opts.EnvOptions)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -819,23 +804,35 @@ func (d *Devbox) ensureStateIsUpToDateAndComputeEnv(
) (map[string]string, error) {
defer debug.FunctionTimer().End()

// When ensureStateIsUpToDate is called with ensure=true, it always
// returns early if the lockfile is up to date. So we don't need to check here
if err := d.ensureStateIsUpToDate(ctx, ensure); isConnectionError(err) {
if !fileutil.Exists(d.nixPrintDevEnvCachePath()) {
ux.Ferrorf(
upToDate, err := d.lockfile.IsUpToDateAndInstalled(isFishShell())
if err != nil {
return nil, err
}
if !upToDate {
if envOpts.Hooks.OnStaleState != nil {
envOpts.Hooks.OnStaleState()
}
}

if !envOpts.SkipRecompute {
// When ensureStateIsUpToDate is called with ensure=true, it always
// returns early if the lockfile is up to date. So we don't need to check here
if err := d.ensureStateIsUpToDate(ctx, ensure); isConnectionError(err) {
if !fileutil.Exists(d.nixPrintDevEnvCachePath()) {
ux.Ferrorf(
d.stderr,
"Error connecting to the internet and no cached environment found. Aborting.\n",
)
return nil, err
}
ux.Fwarningf(
d.stderr,
"Error connecting to the internet and no cached environment found. Aborting.\n",
"Error connecting to the internet. Will attempt to use cached environment.\n",
)
} else if err != nil {
// Some other non connection error, just return it.
return nil, err
}
ux.Fwarningf(
d.stderr,
"Error connecting to the internet. Will attempt to use cached environment.\n",
)
} else if err != nil {
// Some other non connection error, just return it.
return nil, err
}

// Since ensureStateIsUpToDate calls computeEnv when not up do date,
Expand Down
14 changes: 10 additions & 4 deletions internal/devbox/devopt/devboxopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,24 @@ type UpdateOpts struct {
}

type EnvExportsOpts struct {
DontRecomputeEnvironment bool
EnvOptions EnvOptions
NoRefreshAlias bool
RunHooks bool
EnvOptions EnvOptions
NoRefreshAlias bool
RunHooks bool
}

// EnvOptions configure the Devbox Environment in the `computeEnv` function.
// - These options are commonly set by flags in some Devbox commands
// like `shellenv`, `shell` and `run`.
// - The struct is designed for the "common case" to be zero-initialized as `EnvOptions{}`.
type EnvOptions struct {
Hooks LifecycleHooks
OmitNixEnv bool
PreservePathStack bool
Pure bool
SkipRecompute bool
}

type LifecycleHooks struct {
// OnStaleState is called when the Devbox state is out of date, AND it is not being recomputed.
OnStaleState func()
}
2 changes: 1 addition & 1 deletion internal/devbox/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er
ctx,
d.stderr,
StateOutOfDateMessage,
d.refreshAliasOrCommand(),
d.RefreshAliasOrCommand(),
)
}

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

0 comments on commit f7b01fc

Please sign in to comment.