Skip to content

Commit d1f41ea

Browse files
authored
[config] look for the devbox.json file in parent directories as well (#200)
## Summary **Motivation** Previously, a user would need to specify the directory of the devbox.json when invoking `devbox shell` or `devbox build` and similar commands. In the absence, of an explicit directory argument, we would assume the user is saying that the devbox.json file is in the current directory. This PR changes this to look for the devbox.json file in the specified-directory, or current directory if none specified, AND any parent directory. This is desirable for a few reasons: 1. Convenience: it is nice especially for mono-repos to be located in a sub-directory and be able to invoke `devbox shell` or `devbox build`, in a manner similar to how `git` works. 2. Enable `devbox add` and `devbox rm` to run from any sub-directory. These commands currently _require_ the user to be located in the "directory of the devbox.json". With this PR, this requirement is relaxed so that the commands can be run more naturally in any sub-directory as well. Fixes #185 ## How was it tested? - `devbox shell` and `devbox build` inside a sub-directory ``` # this directory has a devbox.json > cd testdata/rust/rust-stable > mkdir fake-dir > cd fake-dir # verify shell is using the rustc from the nix store > devbox shell (devbox) > which rustc > devbox build > docker run devbox > devbox add openssl > git diff ../devbox.json diff --git a/testdata/rust/rust-stable/devbox.json b/testdata/rust/rust-stable/devbox.json index 288dade..66982f9 100644 --- a/testdata/rust/rust-stable/devbox.json +++ b/testdata/rust/rust-stable/devbox.json @@ -1,3 +1,9 @@ { - "packages": ["rust-bin.stable.latest.default"] -} + "packages": [ + "rust-bin.stable.latest.default", + "openssl" + ], + "shell": { + "init_hook": null + } +} \ No newline at end of file > devbox rm openssl > git diff # none! as expected (not quite, I'm lying, there's been some unrelated changes which still show up) ``` - `devbox shell` and `devbox build` in a directory containing a devbox.json - did these commands in `testdata/rust/rust-stable` - explicitly specify the folder of the devbox.json ``` > cd testdata/rust > devbox shell rust-stable ``` - Error scenario ``` > cd testdata/rust/ > devbox shell Error: No devbox.json found in this directory, or any parent directories. Did you run `devbox init` yet? > devbox build Error: No devbox.json found in this directory, or any parent directories. Did you run `devbox init` yet? ```
1 parent 36835e9 commit d1f41ea

File tree

2 files changed

+43
-7
lines changed

2 files changed

+43
-7
lines changed

devbox.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/pkg/errors"
1313
"go.jetpack.io/devbox/boxcli/usererr"
1414
"go.jetpack.io/devbox/cuecfg"
15+
"go.jetpack.io/devbox/debug"
1516
"go.jetpack.io/devbox/docker"
1617
"go.jetpack.io/devbox/nix"
1718
"go.jetpack.io/devbox/pkgslice"
@@ -33,17 +34,19 @@ func InitConfig(dir string) (created bool, err error) {
3334
// Devbox provides an isolated development environment that contains a set of
3435
// Nix packages.
3536
type Devbox struct {
36-
cfg *Config
37+
cfg *Config
38+
// srcDir is the directory where the config file (devbox.json) resides
3739
srcDir string
3840
}
3941

4042
// Open opens a devbox by reading the config file in dir.
4143
func Open(dir string) (*Devbox, error) {
42-
cfgPath := filepath.Join(dir, configFilename)
4344

44-
if !plansdk.FileExists(cfgPath) {
45-
return nil, missingDevboxJSONError(dir)
45+
cfgDir, err := findConfigDir(dir)
46+
if err != nil {
47+
return nil, err
4648
}
49+
cfgPath := filepath.Join(cfgDir, configFilename)
4750

4851
cfg, err := ReadConfig(cfgPath)
4952
if err != nil {
@@ -52,7 +55,7 @@ func Open(dir string) (*Devbox, error) {
5255

5356
box := &Devbox{
5457
cfg: cfg,
55-
srcDir: dir,
58+
srcDir: cfgDir,
5659
}
5760
return box, nil
5861
}
@@ -226,7 +229,7 @@ func (d *Devbox) generateBuildFiles() error {
226229
func missingDevboxJSONError(dir string) error {
227230

228231
// We try to prettify the `dir` before printing
229-
if dir == "." {
232+
if dir == "." || dir == "" {
230233
dir = "this directory"
231234
} else {
232235
// Instead of a long absolute directory, print the relative directory
@@ -240,5 +243,26 @@ func missingDevboxJSONError(dir string) error {
240243
}
241244
}
242245
}
243-
return usererr.New("No devbox.json found in %s. Did you run `devbox init` yet?", dir)
246+
return usererr.New("No devbox.json found in %s, or any parent directories. Did you run `devbox init` yet?", dir)
247+
}
248+
249+
func findConfigDir(dir string) (string, error) {
250+
251+
// Sanitize the directory and use the absolute path as canonical form
252+
cur, err := filepath.Abs(dir)
253+
if err != nil {
254+
return "", errors.WithStack(err)
255+
}
256+
257+
for cur != "/" {
258+
debug.Log("finding %s in dir: %s\n", configFilename, cur)
259+
if plansdk.FileExists(filepath.Join(cur, configFilename)) {
260+
return cur, nil
261+
}
262+
cur = filepath.Dir(cur)
263+
}
264+
if plansdk.FileExists(filepath.Join(cur, configFilename)) {
265+
return cur, nil
266+
}
267+
return "", missingDevboxJSONError(dir)
244268
}

devbox_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/bmatcuk/doublestar/v4"
1010
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1112
"go.jetpack.io/devbox/planner/plansdk"
1213
)
1314

@@ -28,6 +29,10 @@ func TestDevbox(t *testing.T) {
2829
}
2930

3031
func testExample(t *testing.T, testPath string) {
32+
33+
currentDir, err := os.Getwd()
34+
require.New(t).NoError(err)
35+
3136
baseDir := filepath.Dir(testPath)
3237
t.Run(baseDir, func(t *testing.T) {
3338
assert := assert.New(t)
@@ -36,6 +41,13 @@ func testExample(t *testing.T, testPath string) {
3641

3742
box, err := Open(baseDir)
3843
assert.NoErrorf(err, "%s should be a valid devbox project", baseDir)
44+
45+
// Just for tests, we make srcDir be a relative path so that the paths in plan.json
46+
// of various test cases have relative paths. Absolute paths are a no-go because they'd
47+
// be of the form `/Users/savil/...`, which are not generalized and cannot be checked in.
48+
box.srcDir, err = filepath.Rel(currentDir, box.srcDir)
49+
assert.NoErrorf(err, "expect to construct relative path from %s relative to base %s", box.srcDir, currentDir)
50+
3951
plan, err := box.ShellPlan()
4052
assert.NoError(err, "devbox plan should not fail")
4153

0 commit comments

Comments
 (0)