Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[planners] Separate shell plan from build plan #227

Merged
merged 5 commits into from
Oct 17, 2022
Merged

Conversation

LucilleH
Copy link
Collaborator

@LucilleH LucilleH commented Oct 10, 2022

Summary

This PR separate shell plans from build plans. This PR looks big but really it only does the following:
For shell

  1. Separate planner struct into shellPlan and buildPlan
  2. Shell plan is applied when user defined packages include all of the packages the shell plan requires include one of the packages from the shell plan.
  3. If multiple shell plans are found and applicable, the fields are merged and appended
  4. If no shell plans are applicable, launch shell with user defined devbox.json
  5. If we detect that the user defined packages do not include all of shell plan suggested packages, we print a warning and suggest user to run devbox add

For build

  1. Build plan is only applied when user defined packages includes all of the dev packages the build plan requires
  2. Build plan runtimePackages are no longer merged with user defined packages
  3. If multiple buildable plans are found and applicable, only 1 build plan is selected
  4. If no buildable plans are found, return error.

How was it tested?

go test ./...
devbox shell
devbox build

@LucilleH LucilleH force-pushed the lucille--shell-struct branch from dad4793 to 1bc8439 Compare October 10, 2022 18:33
@LucilleH LucilleH requested review from loreto and savil and removed request for loreto October 10, 2022 18:37
savil added a commit that referenced this pull request Oct 11, 2022
Until we can land #227, I want to revert the `devbox.go` changes in #222 so that
shell plans can continue working, and we can release some other (unrelated) fixes.

In this PR, I revert (manually) the `devbox.go` changes in #222, while leaving the test
changes to minimize merge conflicts with #227.
savil added a commit that referenced this pull request Oct 11, 2022
…228)

## Summary

Until we can land #227, I want to revert the changes in #222 so that
shell plans can continue working, and we can release some other
(unrelated) fixes.

In this PR, I revert (manually) the `devbox.go` changes in #222, while
leaving the test changes to minimize merge conflicts with #227.

cc @loreto @LucilleH 

## How was it tested?

sanity check

```
> cd testdata/nodejs/nodejs-18
> devbox shell
> which node
# get nix store path
> exit

```

- [x] will find an example with init-hooks and test that.

Example 1: nginx

```
> devbox shell
Installing nix packages. This may take a while...done.
Starting a devbox shell...

##### WARNING: nginx planner is experimental #####

You may need to add

"include ./.devbox/gen/shell-helper-nginx.conf;"

to your shell-nginx.conf file to ensure the server can start in the nix shell.

Use "shell-nginx" to start the server
```

Example 2: python/pip-example

```
devbox/testdata/python/pip-example 
> devbox shell
Installing nix packages. This may take a while...done.
Starting a devbox shell...
Creating/Using virtual environment in /Users/savil/code/jetpack/devbox/testdata/python/pip-example/.venv

(devbox)
devbox/testdata/python/pip-example 
> which python3
/Users/savil/code/jetpack/devbox/testdata/python/pip-example/.venv/bin/python3
```
Copy link
Collaborator

@savil savil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Overall, I really like the separation of ShellPlan from BuildPlan. I think this will add clarity.

The parts that have me confused are:

Shell plan is only applied when user defined packages includes all of the packages the shell plan requires
Build plan is only applied when user defined packages includes all of the dev packages the build plan requires
Build plan runtimePackages are no longer merged with user defined packages

What is the overall story around how users will understand this behavior?
It feels a bit of a stretch still that users are expected to first add the package via devbox add for devbox's planners to kick in. cc @Lagoja @loreto curious about your thoughts.


plan, err := box.BuildPlan()
shellPlan := box.ShellPlan()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check shellPlan.Invalid() ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no invalid shell plan, as devbox shell should always succeeds?

devbox.go Outdated
return d.convertToPlan()
func (d *Devbox) ShellPlan() *plansdk.ShellPlan {
shellPlan := planner.GetShellPlan(d.srcDir, d.cfg.Packages)
shellPlan.DevPackages = d.cfg.Packages
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two thoughts when reading this. It's possible the answers await me below as I read on.

  • Should this override or append to DevPackages?
  • Also, which not do this inside planner.GetShellPlan?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since shell plans are only selected when users have all of the packages a shell plan expect, user defined packages will always include devPackages from shell plan.

I was thinking planner.GetShellPlan only strictly return the shell plan from planners.

if err != nil {
return nil, err
}
return plansdk.MergeUserPlan(userPlan, buildPlan)
return plansdk.MergeUserBuildPlan(userPlan, buildPlan)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thought as above. Check for buildPlan.Invalid() here?

Copy link
Collaborator Author

@LucilleH LucilleH Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we have Buildable() and Invalid(). I think maybe we can consolidate the 2. I'll refactor a bit 👍


currentDir, err := os.Getwd()
require.New(t).NoError(err)

baseDir := filepath.Dir(testPath)
t.Run(baseDir, func(t *testing.T) {
assert := assert.New(t)
goldenFile := filepath.Join(baseDir, "plan.json")
hasGoldenFile := fileExists(goldenFile)
shellPlanFile := filepath.Join(baseDir, "shell_plan.json")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the name of the test case above to t.Run(baseDir+"_testShell, ... ?

devbox_test.go Outdated
require.New(t).NoError(err)

baseDir := filepath.Dir(testPath)
t.Run(baseDir, func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change name to t.Run(baseDir+"_testBuild" + testPath, ... ?

@@ -30,3 +30,17 @@ func Exclude(s []string, elems []string) []string {
}
return filtered
}

// returns true when s includes all elements from elems.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is okay, but thought I'd mention lo.Contains as alternative way
https://github.com/samber/lo#contains

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw you importing this package lo in #230. If we need it in multiple places, I can switch to use that

plan := &plansdk.Plan{
return &plansdk.ShellPlan{
Definitions: p.definitions(srcDir, v),
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a user able to specify the definitions they want to use, if the default-planner one is not correct for them?

Copy link
Collaborator Author

@LucilleH LucilleH Oct 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not right now. Though, this is not a regression because we have that problem before.

@@ -30,3 +30,17 @@ func Exclude(s []string, elems []string) []string {
}
return filtered
}

// returns true when s includes all elements from elems.
func Contains(s []string, elems []string) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for my sanity:
could we call s as superset and elems as subset ?

packagesPlan, err := MergePlans(userPlan, automatedPlan)
packagesPlan := &BuildPlan{
DevPackages: append([]string{}, userPlan.DevPackages...),
RuntimePackages: []string{},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also add NixOverlays here since both userPlan and automatedPlan might specify that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppendWithSlice should handle that case properly

@savil
Copy link
Collaborator

savil commented Oct 12, 2022

My suggestion as a path forward to land this quickly (which I do want for the shellPlanner versus buildPlanner separation) is to add back to the ShellPlanners the DevPackages for each language. What do you think @LucilleH ?

Then we can independently iron out the product story around (possibly) dropping the auto-add of packages from the planners. As is, I fear we'll break functionality for quite a few users.

@LucilleH
Copy link
Collaborator Author

LucilleH commented Oct 13, 2022

My suggestion as a path forward to land this quickly (which I do want for the shellPlanner versus buildPlanner separation) is to add back to the ShellPlanners the DevPackages for each language. What do you think @LucilleH ?

Since shell plans are only selected when users have all of the packages a shell plan expect, user defined packages will always include devPackages from shell plan @savil
See bullet 2 from PR description

@LucilleH LucilleH force-pushed the lucille--shell-struct branch 2 times, most recently from a1126e9 to 62cb346 Compare October 13, 2022 15:07
@LucilleH LucilleH requested a review from savil October 13, 2022 15:18
@LucilleH LucilleH force-pushed the lucille--shell-struct branch 4 times, most recently from 3a50d46 to 2614d98 Compare October 14, 2022 17:47
Copy link
Collaborator

@savil savil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! Lets ship it!

Shell plan is applied when user defined packages include all of the packages the shell plan requires include one of the packages from the shell plan.

This could get tricky in some situations. For a C++ project, the user may add gcc package.

But this would then activate the Rust shell planner (which includes two packages, a rust pkg and "gcc"). We could maybe tighten this up in a follow up.

}

rustPkgDev := fmt.Sprintf("rust-bin.stable.%s.default", rustVersion)
plan.DevPackages = []string{rustPkgDev, "gcc"}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A style nit is that its easier to track what fields are filled in the ShellPlan struct if we construct it at the end, as in:

manifest, err := ...
rustVersion, err := ...
rustPkgDev := fmt.Sprintf(....
return &plansdk.ShellPlan {
   DevPackages: []string{rustPkgDev, "gcc"}
   NixOverlays: ...
}

Copy link
Collaborator Author

@LucilleH LucilleH Oct 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to return the nix overlay on error cases, which is why this is formatted this way. Thoughts?

// if merge fails, we return no errors for now.
if len(userPkgs) == 0 {
// Since user have no packages added yet, we merge all applicable shell planners
// And make proper suggestion accordingly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this suggestion made?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In devbox.go, ShellPlan()

@LucilleH
Copy link
Collaborator Author

But this would then activate the Rust shell planner (which includes two packages, a rust pkg and "gcc"). We could maybe tighten this up in a follow up.

Rust shell planner will only be applied when it is relevant (cargo.toml file exist), so I think this is ok?

@LucilleH LucilleH force-pushed the lucille--shell-struct branch 3 times, most recently from 36bdb66 to 8c4c7e3 Compare October 17, 2022 20:21
@LucilleH LucilleH force-pushed the lucille--shell-struct branch from 8c4c7e3 to ba4a3b7 Compare October 17, 2022 20:24
@LucilleH LucilleH merged commit 0faa80b into main Oct 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: ability to turn off autodetection languages/stacks
2 participants