Skip to content

Commit 928c344

Browse files
More cleanup following review; config is no longer relative to .envrc
1 parent 8b57442 commit 928c344

23 files changed

+347
-107
lines changed

internal/boxcli/generate.go

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package boxcli
66
import (
77
"cmp"
88
"fmt"
9-
"path/filepath"
109
"regexp"
1110

1211
"github.com/pkg/errors"
@@ -163,8 +162,7 @@ func direnvCmd() *cobra.Command {
163162
&flags.envrcDir, "envrc-dir", "",
164163
"path to directory where the .envrc file should be generated.\n"+
165164
"If not specified, the .envrc file will be generated in the same directory as\n"+
166-
"the devbox.json config file. Also, when specified along with --config, the config file\n"+
167-
"location will be relative to the .envrc file location.")
165+
"the devbox.json.")
168166

169167
flags.config.register(command)
170168
return command
@@ -294,59 +292,21 @@ func runGenerateDirenvCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
294292
cmd.OutOrStdout(), devopt.EnvFlags(flags.envFlag), flags.config.path)
295293
}
296294

297-
// Determine the directories for .envrc and config
298-
configDir, envrcDir, err := determineDirenvDirs(flags.config.path, flags.envrcDir)
299-
if err != nil {
300-
return errors.WithStack(err)
301-
}
302-
303-
generateOpts := devopt.EnvrcOpts{
304-
EnvrcDir: envrcDir,
305-
ConfigDir: configDir,
306-
EnvFlags: devopt.EnvFlags(flags.envFlag),
307-
}
308-
309295
box, err := devbox.Open(&devopt.Opts{
310-
Dir: filepath.Join(envrcDir, configDir),
296+
Dir: flags.config.path,
311297
Environment: flags.config.environment,
312298
Stderr: cmd.ErrOrStderr(),
313299
})
314300
if err != nil {
315301
return errors.WithStack(err)
316302
}
317303

318-
return box.GenerateEnvrcFile(
319-
cmd.Context(), flags.force, generateOpts)
320-
}
321-
322-
// Returns canonical paths for configDir and envrcDir. Both locations are relative to the current
323-
// working directory when provided to this function. However, since the config file will ultimately
324-
// be relative to the .envrc file, we need to determine the relative path from envrcDir to configDir.
325-
func determineDirenvDirs(configDir, envrcDir string) (string, string, error) {
326-
// If envrcDir is not specified, we will use the configDir as the location for .envrc. This is
327-
// for backward compatibility (prior to the --envrc-dir flag being introduced).
328-
if envrcDir == "" {
329-
return "", configDir, nil
330-
}
331-
332-
// If no configDir is specified, it will be assumed to be in the same directory as the .envrc file
333-
// which means we can just return an empty configDir.
334-
if configDir == "" {
335-
return "", envrcDir, nil
336-
}
337-
338-
relativeConfigDir, err := filepath.Rel(envrcDir, configDir)
339-
if err != nil {
340-
return "", "", errors.Wrapf(err, "failed to determine relative path from %s to %s", envrcDir, configDir)
341-
}
342-
343-
// If the relative path is ".", it means configDir is the same as envrcDir. Leaving it as "."
344-
// will result in the .envrc containing "--config .", which is fine, but unnecessary and also
345-
// a change from the previous behavior. So we will return an empty string for relativeConfigDir
346-
// which will result in the .envrc file not containing the "--config" flag at all.
347-
if relativeConfigDir == "." {
348-
relativeConfigDir = ""
304+
generateEnvrcOpts := devopt.EnvrcOpts{
305+
EnvFlags: devopt.EnvFlags(flags.envFlag),
306+
Force: flags.force,
307+
EnvrcDir: flags.envrcDir,
308+
ConfigDir: flags.config.path,
349309
}
350310

351-
return relativeConfigDir, envrcDir, nil
311+
return box.GenerateEnvrcFile(cmd.Context(), generateEnvrcOpts)
352312
}

internal/devbox/devbox.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -527,18 +527,25 @@ func (d *Devbox) GenerateDockerfile(ctx context.Context, generateOpts devopt.Gen
527527
}))
528528
}
529529

530-
func PrintEnvrcContent(w io.Writer, opts devopt.EnvFlags, configDir string) error {
531-
return generate.EnvrcContent(w, opts, configDir)
530+
func PrintEnvrcContent(w io.Writer, envFlags devopt.EnvFlags, configDir string) error {
531+
return generate.EnvrcContent(w, envFlags, configDir)
532532
}
533533

534534
// GenerateEnvrcFile generates a .envrc file that makes direnv integration convenient
535-
func (d *Devbox) GenerateEnvrcFile(ctx context.Context, force bool, opts devopt.EnvrcOpts) error {
535+
func (d *Devbox) GenerateEnvrcFile(ctx context.Context, opts devopt.EnvrcOpts) error {
536536
ctx, task := trace.NewTask(ctx, "devboxGenerateEnvrc")
537537
defer task.End()
538538

539-
envrcfilePath := filepath.Join(opts.EnvrcDir, ".envrc")
540-
filesExist := fileutil.Exists(envrcfilePath)
541-
if !force && filesExist {
539+
// If no envrcDir was specified, use the configDir. This is for backward compatibility
540+
// where the .envrc was placed in the same location as specified by --config. Note that
541+
// if that is also blank, the .envrc will be generated in the current working directory.
542+
if opts.EnvrcDir == "" {
543+
opts.EnvrcDir = opts.ConfigDir
544+
}
545+
546+
envrcFilePath := filepath.Join(opts.EnvrcDir, ".envrc")
547+
filesExist := fileutil.Exists(envrcFilePath)
548+
if !opts.Force && filesExist {
542549
return usererr.New(
543550
"A .envrc is already present in %q. Remove it or use --force to overwrite it.",
544551
opts.EnvrcDir,

internal/devbox/devopt/devboxopts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type EnvFlags struct {
3636

3737
type EnvrcOpts struct {
3838
EnvFlags
39+
Force bool
3940
EnvrcDir string
4041
ConfigDir string
4142
}

internal/devbox/generate/devcontainer_util.go

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,47 @@ func CreateEnvrc(ctx context.Context, opts devopt.EnvrcOpts) error {
161161
flags = append(flags, fmt.Sprintf("--env-file %s", opts.EnvFile))
162162
}
163163

164+
configDir, err := getRelativePathToConfig(opts.EnvrcDir, opts.ConfigDir)
165+
if err != nil {
166+
return err
167+
}
168+
164169
t := template.Must(template.ParseFS(tmplFS, "tmpl/envrc.tmpl"))
165170

166171
// write content into file
167172
return t.Execute(file, map[string]string{
168173
"EnvFlag": strings.Join(flags, " "),
169-
"ConfigDir": formatConfigDirArg(opts.ConfigDir),
174+
"ConfigDir": formatConfigDirArg(configDir),
170175
})
171176
}
172177

178+
// Returns the relative path from sourceDir to configDir, or an error if it cannot be determined.
179+
func getRelativePathToConfig(sourceDir string, configDir string) (string, error) {
180+
absConfigDir, err := filepath.Abs(configDir)
181+
if err != nil {
182+
return "", fmt.Errorf("failed to get absolute path for config dir: %w", err)
183+
}
184+
185+
absSourceDir, err := filepath.Abs(sourceDir)
186+
if err != nil {
187+
return "", fmt.Errorf("failed to get absolute path for source dir: %w", err)
188+
}
189+
190+
// We don't want the path if the config dir is a parent of the envrc dir. This way
191+
// the config will be found when it recursively searches for it through the parent tree.
192+
if strings.HasPrefix(absSourceDir, absConfigDir) {
193+
return "", nil
194+
}
195+
196+
relPath, err := filepath.Rel(absSourceDir, absConfigDir)
197+
if err != nil {
198+
// If a relative path cannot be computed, return the absolute path of configDir
199+
return absConfigDir, nil
200+
}
201+
202+
return relPath, nil
203+
}
204+
173205
func (g *Options) getDevcontainerContent() *devcontainerObject {
174206
// object that gets written in devcontainer.json
175207
devcontainerContent := &devcontainerObject{
@@ -221,8 +253,7 @@ func (g *Options) getDevcontainerContent() *devcontainerObject {
221253
}
222254

223255
func EnvrcContent(w io.Writer, envFlags devopt.EnvFlags, configDir string) error {
224-
tmplName := "envrcContent.tmpl"
225-
t := template.Must(template.ParseFS(tmplFS, "tmpl/"+tmplName))
256+
t := template.Must(template.ParseFS(tmplFS, "tmpl/envrcContent.tmpl"))
226257
envFlag := ""
227258
if len(envFlags.EnvMap) > 0 {
228259
for k, v := range envFlags.EnvMap {
@@ -231,19 +262,16 @@ func EnvrcContent(w io.Writer, envFlags devopt.EnvFlags, configDir string) error
231262
}
232263

233264
return t.Execute(w, map[string]string{
234-
"JSONPath": filepath.Join(configDir, "devbox.json"),
235-
"LockPath": filepath.Join(configDir, "devbox.lock"),
236265
"EnvFlag": envFlag,
237266
"EnvFile": envFlags.EnvFile,
238267
"ConfigDir": formatConfigDirArg(configDir),
239268
})
240269
}
241270

242271
func formatConfigDirArg(configDir string) string {
243-
configDirArg := ""
244-
if configDir != "" {
245-
configDirArg = "--config " + configDir
272+
if configDir == "" {
273+
return ""
246274
}
247275

248-
return configDirArg
276+
return "--config " + configDir
249277
}

internal/devbox/generate/tmpl/envrcContent.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use_devbox() {
2-
watch_file {{ .JSONPath }} {{ .LockPath }}
3-
eval "$(devbox shellenv --init-hook --install --no-refresh-alias{{ if .EnvFlag}} {{ .EnvFlag }}{{ end }}{{ if .ConfigDir }} {{ .ConfigDir }}{{ end }})"
2+
eval "$(devbox shellenv --init-hook --install --no-refresh-alias{{ if .EnvFlag }} {{ .EnvFlag }}{{ end }}{{ if .ConfigDir }} {{ .ConfigDir }}{{ end }})"
3+
watch_file $DEVBOX_PROJECT_ROOT/devbox.json $DEVBOX_PROJECT_ROOT/devbox.lock
44
}
55
use devbox
66
{{ if .EnvFile }}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Testscript to validate generating the contents of the .envrc file.
2+
# Note that since --envrc-dir was NOT specified, the .envrc will be in the `dir` directory and
3+
# the config will be found there, which means the `--print-env` doesn't need to specify the dir.
4+
# This matches the mode of operation prior to the addition of the --envrc-dir flag.
5+
6+
mkdir dir
7+
exec devbox init dir
8+
exists dir/devbox.json
9+
10+
exec devbox generate direnv --env x=y --config dir
11+
grep 'eval "\$\(devbox generate direnv --print-envrc --env x=y\)"' dir/.envrc
12+
13+
cd dir
14+
exec devbox generate direnv --print-envrc --env x=y
15+
16+
cmp stdout ../expected-results.txt
17+
18+
# Note: The contents of the following file ends with two blank lines. This is
19+
# necessary to match the blank line that follows "use devbox" in the actual output.
20+
-- expected-results.txt --
21+
use_devbox() {
22+
eval "$(devbox shellenv --init-hook --install --no-refresh-alias --env x=y )"
23+
watch_file $DEVBOX_PROJECT_ROOT/devbox.json $DEVBOX_PROJECT_ROOT/devbox.lock
24+
}
25+
use devbox
26+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Testscript to validate generating the contents of the .envrc file.
2+
# Note that since --envrc-dir was NOT specified, the .envrc will be in the `dir` directory and
3+
# the config will be found there, which means the `--print-env` doesn't need to specify the dir.
4+
# This matches the mode of operation prior to the addition of the --envrc-dir flag.
5+
6+
mkdir dir
7+
exec devbox init dir
8+
exists dir/devbox.json
9+
10+
exec devbox generate direnv --config dir
11+
grep 'eval "\$\(devbox generate direnv --print-envrc\)"' dir/.envrc
12+
13+
cd dir
14+
exec devbox generate direnv --print-envrc
15+
16+
cmp stdout ../expected-results.txt
17+
18+
# Note: The contents of the following file ends with two blank lines. This is
19+
# necessary to match the blank line that follows "use devbox" in the actual output.
20+
-- expected-results.txt --
21+
use_devbox() {
22+
eval "$(devbox shellenv --init-hook --install --no-refresh-alias)"
23+
watch_file $DEVBOX_PROJECT_ROOT/devbox.json $DEVBOX_PROJECT_ROOT/devbox.lock
24+
}
25+
use devbox
26+

testscripts/generate/direnv-envflag-config.test.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.

testscripts/generate/direnv-envflag-envrcdir-config-subdir.test.txt

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
# Testscript to validate generating the contents of the .envrc file.
2-
# Note that since --envrc-dir was NOT specified, the .envrc will be in the `dir` directory and
3-
# the config will be found there, which means the `--print-env` doesn't need to specify the dir.
4-
5-
mkdir dir
6-
exec devbox init dir
7-
exec devbox generate direnv --env x=y --config dir
8-
exists dir/.envrc
9-
exists dir/devbox.json
10-
grep 'eval "\$\(devbox generate direnv --print-envrc --env x=y\)"' dir/.envrc
2+
3+
exec devbox init
4+
exists devbox.json
5+
6+
exec devbox generate direnv --env x=y
7+
grep 'eval "\$\(devbox generate direnv --print-envrc --env x=y\)"' .envrc
8+
9+
exec devbox generate direnv --print-envrc --env x=y
10+
11+
cmp stdout expected-results.txt
12+
13+
# Note: The contents of the following file ends with two blank lines. This is
14+
# necessary to match the blank line that follows "use devbox" in the actual output.
15+
-- expected-results.txt --
16+
use_devbox() {
17+
eval "$(devbox shellenv --init-hook --install --no-refresh-alias --env x=y )"
18+
watch_file $DEVBOX_PROJECT_ROOT/devbox.json $DEVBOX_PROJECT_ROOT/devbox.lock
19+
}
20+
use devbox
21+

0 commit comments

Comments
 (0)