Skip to content

Commit f0a562e

Browse files
authored
[devbox global] add --omit-nix-env flag for shellenv/shell/run commands (#2150)
…vars ## Summary In this PR, we change `devbox global` shell environment to omit the env-vars from `nix print-dev-env`. Instead, we rely on the `nix profile` that Devbox manages to introduce the global packages into `PATH`. As before, `nix profile` continues to be generated from the `buildInputs` from `nix print-dev-env <devbox-generated-flake>`. The motivation is: 1. `devbox global` adds a bunch of nix stdenv packages to the top of your `$PATH` variable, which can cause conflicts when trying to compile or build projects on your machine. 2. `devbox global` also sets a global `PYTHONPATH` variable that interferes with other packages, as well as python scripts that are installed/running on the host. This global `PYTHONPATH` is unnecessary because any python binaries installed by the user are already wrapped with the `PYTHONPATH` Implementation notes: - Sets `Devbox.envForPackageBins` boolean setting that controls whether the Devbox Environment is optimized for executing package binaries, or for developing software using the packages. - [x] see if this can be passed down as a function parameter (see #2159) - Adds `flagDefaultOptions` to `shellEnvCmd`. This is a nicer way of overriding the defaults in global, even for the `--recompute` flag that we had done before. - Refactors code into a `devbox.execPrintDevEnv` function to remove some of the complexity from the `devbox.computeEnv` function (thanks to finger-wagging by our linter tool ;) ). TODO: - [x] Verify `omitNixEnv` is still properly handling the useNixPrintDevEnvCache call paths ## How was it tested? CICD tests should pass TODO: - [x] Ensure the python global scenario works - [x] Ensure we can use common packages (fzf, ripgrep, etc.) in `devbox global` - [x] Thanks to @gcurtis vetted this PR helps resolve issues with a couple of repos: (rust: https://github.com/zed-industries/zed.git) and (golang with cgo: [github.com/Code-Hex/vz/example/macOS](http://github.com/Code-Hex/vz/example/macOS))
1 parent f44b799 commit f0a562e

File tree

8 files changed

+130
-80
lines changed

8 files changed

+130
-80
lines changed

internal/boxcli/global.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,19 @@ func globalCmd() *cobra.Command {
2828
PersistentPostRunE: ensureGlobalEnvEnabled,
2929
}
3030

31-
shellEnv := shellEnvCmd()
32-
// For `devbox shellenv` the default value of recompute is true.
33-
// Change the default value to false for `devbox global shellenv` only.
34-
shellEnv.Flag("recompute").DefValue = "false" // Needed for help text
35-
if err := shellEnv.Flag("recompute").Value.Set("false"); err != nil {
36-
// This will never panic because internally it just does
37-
// `strconv.ParseBool("false")` which is always valid.
38-
// If this were to change, we'll immediately detect this during development
39-
// since this code always runs on any devbox command (and will fix it).
40-
panic(errors.WithStack(err))
41-
}
42-
4331
addCommandAndHideConfigFlag(globalCmd, addCmd())
4432
addCommandAndHideConfigFlag(globalCmd, installCmd())
4533
addCommandAndHideConfigFlag(globalCmd, pathCmd())
4634
addCommandAndHideConfigFlag(globalCmd, pullCmd())
4735
addCommandAndHideConfigFlag(globalCmd, pushCmd())
4836
addCommandAndHideConfigFlag(globalCmd, removeCmd())
49-
addCommandAndHideConfigFlag(globalCmd, runCmd())
37+
addCommandAndHideConfigFlag(globalCmd, runCmd(runFlagDefaults{
38+
omitNixEnv: true,
39+
}))
5040
addCommandAndHideConfigFlag(globalCmd, servicesCmd(persistentPreRunE))
51-
addCommandAndHideConfigFlag(globalCmd, shellEnv)
41+
addCommandAndHideConfigFlag(globalCmd, shellEnvCmd(shellenvFlagDefaults{
42+
omitNixEnv: true,
43+
}))
5244
addCommandAndHideConfigFlag(globalCmd, updateCmd())
5345
addCommandAndHideConfigFlag(globalCmd, listCmd())
5446

internal/boxcli/root.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,14 @@ func RootCmd() *cobra.Command {
7171
command.AddCommand(listCmd())
7272
command.AddCommand(logCmd())
7373
command.AddCommand(removeCmd())
74-
command.AddCommand(runCmd())
74+
command.AddCommand(runCmd(runFlagDefaults{}))
7575
command.AddCommand(searchCmd())
7676
command.AddCommand(servicesCmd())
7777
command.AddCommand(setupCmd())
78-
command.AddCommand(shellCmd())
79-
command.AddCommand(shellEnvCmd())
78+
command.AddCommand(shellCmd(shellFlagDefaults{}))
79+
command.AddCommand(shellEnvCmd(shellenvFlagDefaults{
80+
recomputeEnv: true,
81+
}))
8082
command.AddCommand(updateCmd())
8183
command.AddCommand(versionCmd())
8284
// Preview commands

internal/boxcli/run.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,18 @@ import (
2222
type runCmdFlags struct {
2323
envFlag
2424
config configFlags
25+
omitNixEnv bool
2526
pure bool
2627
listScripts bool
2728
}
2829

29-
func runCmd() *cobra.Command {
30+
// runFlagDefaults are the flag default values that differ
31+
// from the `devbox` command versus `devbox global` command.
32+
type runFlagDefaults struct {
33+
omitNixEnv bool
34+
}
35+
36+
func runCmd(defaults runFlagDefaults) *cobra.Command {
3037
flags := runCmdFlags{}
3138
command := &cobra.Command{
3239
Use: "run [<script> | <cmd>]",
@@ -50,6 +57,11 @@ func runCmd() *cobra.Command {
5057
&flags.pure, "pure", false, "if this flag is specified, devbox runs the script in an isolated environment inheriting almost no variables from the current environment. A few variables, in particular HOME, USER and DISPLAY, are retained.")
5158
command.Flags().BoolVarP(
5259
&flags.listScripts, "list", "l", false, "list all scripts defined in devbox.json")
60+
command.Flags().BoolVar(
61+
&flags.omitNixEnv, "omit-nix-env", defaults.omitNixEnv,
62+
"shell environment will omit the env-vars from print-dev-env",
63+
)
64+
_ = command.Flags().MarkHidden("omit-nix-env")
5365

5466
command.ValidArgs = listScripts(command, flags)
5567

@@ -102,6 +114,7 @@ func runScriptCmd(cmd *cobra.Command, args []string, flags runCmdFlags) error {
102114
Dir: path,
103115
Environment: flags.config.environment,
104116
Stderr: cmd.ErrOrStderr(),
117+
OmitNixEnv: flags.omitNixEnv,
105118
Pure: flags.pure,
106119
Env: env,
107120
})

internal/boxcli/shell.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@ import (
1717

1818
type shellCmdFlags struct {
1919
envFlag
20-
config configFlags
21-
printEnv bool
22-
pure bool
20+
config configFlags
21+
omitNixEnv bool
22+
printEnv bool
23+
pure bool
2324
}
2425

25-
func shellCmd() *cobra.Command {
26+
// shellFlagDefaults are the flag default values that differ
27+
// from the `devbox` command versus `devbox global` command.
28+
type shellFlagDefaults struct {
29+
omitNixEnv bool
30+
}
31+
32+
func shellCmd(defaults shellFlagDefaults) *cobra.Command {
2633
flags := shellCmdFlags{}
2734
command := &cobra.Command{
2835
Use: "shell",
@@ -41,6 +48,11 @@ func shellCmd() *cobra.Command {
4148
&flags.printEnv, "print-env", false, "print script to setup shell environment")
4249
command.Flags().BoolVar(
4350
&flags.pure, "pure", false, "if this flag is specified, devbox creates an isolated shell inheriting almost no variables from the current environment. A few variables, in particular HOME, USER and DISPLAY, are retained.")
51+
command.Flags().BoolVar(
52+
&flags.omitNixEnv, "omit-nix-env", defaults.omitNixEnv,
53+
"shell environment will omit the env-vars from print-dev-env",
54+
)
55+
_ = command.Flags().MarkHidden("omit-nix-env")
4456

4557
flags.config.register(command)
4658
flags.envFlag.register(command)
@@ -57,6 +69,7 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
5769
Dir: flags.config.path,
5870
Env: env,
5971
Environment: flags.config.environment,
72+
OmitNixEnv: flags.omitNixEnv,
6073
Pure: flags.pure,
6174
Stderr: cmd.ErrOrStderr(),
6275
})

internal/boxcli/shellenv.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
type shellEnvCmdFlags struct {
1717
envFlag
1818
config configFlags
19+
omitNixEnv bool
1920
install bool
2021
noRefreshAlias bool
2122
preservePathStack bool
@@ -24,11 +25,18 @@ type shellEnvCmdFlags struct {
2425
runInitHook bool
2526
}
2627

27-
func shellEnvCmd() *cobra.Command {
28+
// shellenvFlagDefaults are the flag default values that differ
29+
// from the `devbox` command versus `devbox global` command.
30+
type shellenvFlagDefaults struct {
31+
omitNixEnv bool
32+
recomputeEnv bool
33+
}
34+
35+
func shellEnvCmd(defaults shellenvFlagDefaults) *cobra.Command {
2836
flags := shellEnvCmdFlags{}
2937
command := &cobra.Command{
3038
Use: "shellenv",
31-
Short: "Print shell commands that add Devbox packages to your PATH",
39+
Short: "Print shell commands that create a Devbox Environment in the shell",
3240
Args: cobra.ExactArgs(0),
3341
PreRunE: ensureNixInstalled,
3442
RunE: func(cmd *cobra.Command, args []string) error {
@@ -61,10 +69,14 @@ func shellEnvCmd() *cobra.Command {
6169
"by default, devbox will add refresh alias to the environment"+
6270
"Use this flag to disable this behavior.")
6371
_ = command.Flags().MarkHidden("no-refresh-alias")
72+
command.Flags().BoolVar(
73+
&flags.omitNixEnv, "omit-nix-env", defaults.omitNixEnv,
74+
"shell environment will omit the env-vars from print-dev-env",
75+
)
76+
_ = command.Flags().MarkHidden("omit-nix-env")
6477

65-
// Note, `devbox global shellenv` will override the default value to be false
6678
command.Flags().BoolVarP(
67-
&flags.recomputeEnv, "recompute", "r", true,
79+
&flags.recomputeEnv, "recompute", "r", defaults.recomputeEnv,
6880
"Recompute environment if needed",
6981
)
7082

@@ -85,6 +97,7 @@ func shellEnvFunc(
8597
box, err := devbox.Open(&devopt.Opts{
8698
Dir: flags.config.path,
8799
Environment: flags.config.environment,
100+
OmitNixEnv: flags.omitNixEnv,
88101
Stderr: cmd.ErrOrStderr(),
89102
PreservePathStack: flags.preservePathStack,
90103
Pure: flags.pure,

internal/devbox/devbox.go

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const (
5959
type Devbox struct {
6060
cfg *devconfig.Config
6161
env map[string]string
62+
omitNixEnv bool
6263
environment string
6364
lockfile *lock.File
6465
nix nix.Nixer
@@ -97,6 +98,7 @@ func Open(opts *devopt.Opts) (*Devbox, error) {
9798
box := &Devbox{
9899
cfg: cfg,
99100
env: opts.Env,
101+
omitNixEnv: opts.OmitNixEnv,
100102
environment: environment,
101103
nix: &nix.Nix{},
102104
projectDir: projectDir,
@@ -803,6 +805,61 @@ func (d *Devbox) StartProcessManager(
803805
)
804806
}
805807

808+
func (d *Devbox) execPrintDevEnv(ctx context.Context, usePrintDevEnvCache bool) (map[string]string, error) {
809+
var spinny *spinner.Spinner
810+
if !usePrintDevEnvCache {
811+
spinny = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(d.stderr))
812+
spinny.FinalMSG = "✓ Computed the Devbox environment.\n"
813+
spinny.Suffix = " Computing the Devbox environment...\n"
814+
spinny.Start()
815+
}
816+
817+
vaf, err := d.nix.PrintDevEnv(ctx, &nix.PrintDevEnvArgs{
818+
FlakeDir: d.flakeDir(),
819+
PrintDevEnvCachePath: d.nixPrintDevEnvCachePath(),
820+
UsePrintDevEnvCache: usePrintDevEnvCache,
821+
})
822+
if spinny != nil {
823+
spinny.Stop()
824+
}
825+
if err != nil {
826+
return nil, err
827+
}
828+
829+
// Add environment variables from "nix print-dev-env" except for a few
830+
// special ones we need to ignore.
831+
env := map[string]string{}
832+
for key, val := range vaf.Variables {
833+
// We only care about "exported" because the var and array types seem to only be used by nix-defined
834+
// functions that we don't need (like genericBuild). For reference, each type translates to bash as follows:
835+
// var: export VAR=VAL
836+
// exported: export VAR=VAL
837+
// array: declare -a VAR=('VAL1' 'VAL2' )
838+
if val.Type != "exported" {
839+
continue
840+
}
841+
842+
// SSL_CERT_FILE is a special-case. We only ignore it if it's
843+
// set to a specific value. This emulates the behavior of
844+
// "nix develop".
845+
if key == "SSL_CERT_FILE" && val.Value.(string) == "/no-cert-file.crt" {
846+
continue
847+
}
848+
849+
// Certain variables get set to invalid values after Nix builds
850+
// the shell environment. For example, HOME=/homeless-shelter
851+
// and TMPDIR points to a missing directory. We want to ignore
852+
// those values and just use the values from the current
853+
// environment instead.
854+
if ignoreDevEnvVar[key] {
855+
continue
856+
}
857+
858+
env[key] = val.Value.(string)
859+
}
860+
return env, nil
861+
}
862+
806863
// computeEnv computes the set of environment variables that define a Devbox
807864
// environment. The "devbox run" and "devbox shell" commands source these
808865
// variables into a shell before executing a command or showing an interactive
@@ -853,58 +910,17 @@ func (d *Devbox) computeEnv(ctx context.Context, usePrintDevEnvCache bool) (map[
853910
originalEnv := make(map[string]string, len(env))
854911
maps.Copy(originalEnv, env)
855912

856-
var spinny *spinner.Spinner
857-
if !usePrintDevEnvCache {
858-
spinny = spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(d.stderr))
859-
spinny.FinalMSG = "✓ Computed the Devbox environment.\n"
860-
spinny.Suffix = " Computing the Devbox environment...\n"
861-
spinny.Start()
862-
}
863-
864-
vaf, err := d.nix.PrintDevEnv(ctx, &nix.PrintDevEnvArgs{
865-
FlakeDir: d.flakeDir(),
866-
PrintDevEnvCachePath: d.nixPrintDevEnvCachePath(),
867-
UsePrintDevEnvCache: usePrintDevEnvCache,
868-
})
869-
if spinny != nil {
870-
spinny.Stop()
871-
}
872-
if err != nil {
873-
return nil, err
874-
}
875-
876-
// Add environment variables from "nix print-dev-env" except for a few
877-
// special ones we need to ignore.
878-
for key, val := range vaf.Variables {
879-
// We only care about "exported" because the var and array types seem to only be used by nix-defined
880-
// functions that we don't need (like genericBuild). For reference, each type translates to bash as follows:
881-
// var: export VAR=VAL
882-
// exported: export VAR=VAL
883-
// array: declare -a VAR=('VAL1' 'VAL2' )
884-
if val.Type != "exported" {
885-
continue
886-
}
887-
888-
// SSL_CERT_FILE is a special-case. We only ignore it if it's
889-
// set to a specific value. This emulates the behavior of
890-
// "nix develop".
891-
if key == "SSL_CERT_FILE" && val.Value.(string) == "/no-cert-file.crt" {
892-
continue
913+
if !d.omitNixEnv {
914+
nixEnv, err := d.execPrintDevEnv(ctx, usePrintDevEnvCache)
915+
if err != nil {
916+
return nil, err
893917
}
894918

895-
// Certain variables get set to invalid values after Nix builds
896-
// the shell environment. For example, HOME=/homeless-shelter
897-
// and TMPDIR points to a missing directory. We want to ignore
898-
// those values and just use the values from the current
899-
// environment instead.
900-
if ignoreDevEnvVar[key] {
901-
continue
919+
for k, v := range nixEnv {
920+
env[k] = v
902921
}
903-
904-
env[key] = val.Value.(string)
905922
}
906-
907-
slog.Debug("nix environment PATH", "path", env)
923+
slog.Debug("nix environment PATH", "path", env["PATH"])
908924

909925
env["PATH"] = envpath.JoinPathLists(
910926
nix.ProfileBinPath(d.projectDir),
@@ -991,9 +1007,8 @@ func (d *Devbox) computeEnv(ctx context.Context, usePrintDevEnvCache bool) (map[
9911007
return env, d.addHashToEnv(env)
9921008
}
9931009

994-
// ensureStateIsUpToDateAndComputeEnv will return a map of the env-vars for the Devbox Environment
1010+
// ensureStateIsUpToDateAndComputeEnv will return a map of the env-vars for the Devbox Environment
9951011
// while ensuring these reflect the current (up to date) state of the project.
996-
// TODO: find a better name for this function.
9971012
func (d *Devbox) ensureStateIsUpToDateAndComputeEnv(
9981013
ctx context.Context,
9991014
) (map[string]string, error) {

internal/devbox/devopt/devboxopts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Opts struct {
1212
Dir string
1313
Env map[string]string
1414
Environment string
15+
OmitNixEnv bool
1516
PreservePathStack bool
1617
Pure bool
1718
IgnoreWarnings bool

internal/devbox/nixprofile.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ import (
1919
// It also removes any packages from the nix profile that are no longer in the buildInputs.
2020
func (d *Devbox) syncNixProfileFromFlake(ctx context.Context) error {
2121
defer debug.FunctionTimer().End()
22-
// Get the computed Devbox environment from the generated flake
23-
env, err := d.computeEnv(ctx, false /*usePrintDevEnvCache*/)
22+
// Get the buildInputs from the generated flake
23+
env, err := d.execPrintDevEnv(ctx, false /*usePrintDevEnvCache*/)
2424
if err != nil {
2525
return err
2626
}
27+
buildInputs := env["buildInputs"]
2728

2829
// Get the store-paths of the packages we want installed in the nix profile
2930
wantStorePaths := []string{}
30-
if env["buildInputs"] != "" {
31+
if buildInputs != "" {
3132
// env["buildInputs"] can be empty string if there are no packages in the project
3233
// if buildInputs is empty, then we don't want wantStorePaths to be an array with a single "" entry
33-
wantStorePaths = strings.Split(env["buildInputs"], " ")
34+
wantStorePaths = strings.Split(buildInputs, " ")
3435
}
3536

3637
profilePath, err := d.profilePath()

0 commit comments

Comments
 (0)