diff --git a/internal/boxcli/run.go b/internal/boxcli/run.go index ac4f832b2af..123288b5039 100644 --- a/internal/boxcli/run.go +++ b/internal/boxcli/run.go @@ -21,10 +21,11 @@ import ( 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 @@ -62,6 +63,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) @@ -84,6 +86,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 { @@ -111,9 +114,9 @@ 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) @@ -122,8 +125,12 @@ func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error { envOpts := devopt.EnvOptions{ OmitNixEnv: flags.omitNixEnv, Pure: flags.pure, + RecomputeEnv: &devopt.RecomputeEnvOpts{ + Disabled: !flags.recomputeEnv, + StateOutOfDateMessage: fmt.Sprintf(devbox.StateOutOfDateMessage, "with --recompute=true"), + }, } - 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 diff --git a/internal/boxcli/shell.go b/internal/boxcli/shell.go index 48e0df4ed1f..60575549f9f 100644 --- a/internal/boxcli/shell.go +++ b/internal/boxcli/shell.go @@ -17,10 +17,11 @@ import ( 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 @@ -53,6 +54,7 @@ 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) @@ -60,10 +62,12 @@ func shellCmd(defaults shellFlagDefaults) *cobra.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, @@ -91,9 +95,13 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error { return shellInceptionErrorMsg("devbox shell") } - return box.Shell(cmd.Context(), devopt.EnvOptions{ + return box.Shell(ctx, devopt.EnvOptions{ OmitNixEnv: flags.omitNixEnv, Pure: flags.pure, + RecomputeEnv: &devopt.RecomputeEnvOpts{ + Disabled: !flags.recomputeEnv, + StateOutOfDateMessage: fmt.Sprintf(devbox.StateOutOfDateMessage, "with --recompute=true"), + }, }) } diff --git a/internal/boxcli/shellenv.go b/internal/boxcli/shellenv.go index a37472b7167..a783e091306 100644 --- a/internal/boxcli/shellenv.go +++ b/internal/boxcli/shellenv.go @@ -116,11 +116,14 @@ func shellEnvFunc( } envStr, err := box.EnvExports(ctx, devopt.EnvExportsOpts{ - DontRecomputeEnvironment: !flags.recomputeEnv, EnvOptions: devopt.EnvOptions{ OmitNixEnv: flags.omitNixEnv, PreservePathStack: flags.preservePathStack, Pure: flags.pure, + RecomputeEnv: &devopt.RecomputeEnvOpts{ + Disabled: !flags.recomputeEnv, + StateOutOfDateMessage: fmt.Sprintf(devbox.StateOutOfDateMessage, box.RefreshAliasOrCommand()), + }, }, NoRefreshAlias: flags.noRefreshAlias, RunHooks: flags.runInitHook, diff --git a/internal/devbox/devbox.go b/internal/devbox/devbox.go index a8a24a0b30b..d958fb83e71 100644 --- a/internal/devbox/devbox.go +++ b/internal/devbox/devbox.go @@ -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 } @@ -819,23 +804,34 @@ 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( + if envOpts.RecomputeEnv.Disabled { + upToDate, _ := d.lockfile.IsUpToDateAndInstalled(isFishShell()) + if !upToDate { + ux.FHidableWarning( + ctx, d.stderr, - "Error connecting to the internet and no cached environment found. Aborting.\n", + envOpts.RecomputeEnv.StateOutOfDateMessage, ) + } + } else { + // 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. 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, diff --git a/internal/devbox/devopt/devboxopts.go b/internal/devbox/devopt/devboxopts.go index df47b1c2174..4ee64f9dd40 100644 --- a/internal/devbox/devopt/devboxopts.go +++ b/internal/devbox/devopt/devboxopts.go @@ -62,10 +62,9 @@ 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. @@ -76,4 +75,10 @@ type EnvOptions struct { OmitNixEnv bool PreservePathStack bool Pure bool + RecomputeEnv *RecomputeEnvOpts +} + +type RecomputeEnvOpts struct { + Disabled bool // Disabled instead of Enabled, because zero-value is false + StateOutOfDateMessage string } diff --git a/internal/devbox/packages.go b/internal/devbox/packages.go index eb631e26211..8ed878e34fc 100644 --- a/internal/devbox/packages.go +++ b/internal/devbox/packages.go @@ -303,7 +303,7 @@ func (d *Devbox) ensureStateIsUpToDate(ctx context.Context, mode installMode) er ctx, d.stderr, StateOutOfDateMessage, - d.refreshAliasOrCommand(), + d.RefreshAliasOrCommand(), ) } diff --git a/internal/devbox/refresh.go b/internal/devbox/refresh.go index dd44aeabc9a..9f9f650670b 100644 --- a/internal/devbox/refresh.go +++ b/internal/devbox/refresh.go @@ -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())