Skip to content

Commit 85f832e

Browse files
authored
[update] Show warning for legacy packages and fix with update (#1107)
## Summary Add warning if devbox.json contains legacy packages. Fix them with update command. Update will turn legacy packages to `<pkg>@latest` and resolve them to current hash. This guarantees version will not change. If they run update again, it will update to newest version. ## How was it tested? <img width="840" alt="image" src="https://github.com/jetpack-io/devbox/assets/544948/fe612548-356a-4ed5-810a-78a3942913d1"> ```bash # modified devbox.json to have `curl` package. devbox update ``` Inspected devbox.json and devbox.lock and verified it was now `curl@latest`
1 parent 0ac23f5 commit 85f832e

File tree

13 files changed

+136
-44
lines changed

13 files changed

+136
-44
lines changed

devbox.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ type Devbox interface {
3434
ListScripts() []string
3535
PrintEnv(ctx context.Context, includeHooks bool) (string, error)
3636
PrintGlobalList() error
37-
PrintEnvrcContent(w io.Writer) error
3837
Pull(ctx context.Context, overwrite bool, path string) error
3938
Push(url string) error
4039
// Remove removes Nix packages from the config so that it no longer exists in
@@ -58,7 +57,11 @@ type Devbox interface {
5857

5958
// Open opens a devbox by reading the config file in dir.
6059
func Open(dir string, writer io.Writer) (Devbox, error) {
61-
return impl.Open(dir, writer)
60+
return impl.Open(dir, writer, true)
61+
}
62+
63+
func OpenWithoutWarnings(dir string, writer io.Writer) (Devbox, error) {
64+
return impl.Open(dir, writer, false)
6265
}
6366

6467
// InitConfig creates a default devbox config file if one doesn't already exist.
@@ -69,3 +72,7 @@ func InitConfig(dir string, writer io.Writer) (bool, error) {
6972
func GlobalDataPath() (string, error) {
7073
return impl.GlobalDataPath()
7174
}
75+
76+
func PrintEnvrcContent(w io.Writer) error {
77+
return impl.PrintEnvrcContent(w)
78+
}

internal/boxcli/featureflag/autolatest.go

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

internal/boxcli/generate.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func direnvCmd() *cobra.Command {
9292
"Requires direnv to be installed.",
9393
Args: cobra.MaximumNArgs(0),
9494
RunE: func(cmd *cobra.Command, args []string) error {
95-
return runGenerateCmd(cmd, flags)
95+
return runGenerateDirenvCmd(cmd, flags)
9696
},
9797
}
9898
command.Flags().BoolVarP(
@@ -140,11 +140,19 @@ func runGenerateCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
140140
return box.GenerateDevcontainer(flags.force)
141141
case "dockerfile":
142142
return box.GenerateDockerfile(flags.force)
143-
case "direnv":
144-
if flags.printEnvrcContent {
145-
return box.PrintEnvrcContent(cmd.OutOrStdout())
146-
}
147-
return box.GenerateEnvrcFile(flags.force)
148143
}
149144
return nil
150145
}
146+
147+
func runGenerateDirenvCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
148+
if flags.printEnvrcContent {
149+
return devbox.PrintEnvrcContent(cmd.OutOrStdout())
150+
}
151+
152+
box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr())
153+
if err != nil {
154+
return errors.WithStack(err)
155+
}
156+
157+
return box.GenerateEnvrcFile(flags.force)
158+
}

internal/boxcli/midcobra/telemetry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func getPackagesAndCommitHash(c *cobra.Command) ([]string, string) {
222222
path = configFlag.Value.String()
223223
}
224224

225-
box, err := devbox.Open(path, os.Stdout)
225+
box, err := devbox.OpenWithoutWarnings(path, os.Stdout)
226226
if err != nil {
227227
return []string{}, ""
228228
}

internal/boxcli/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func runCmd() *cobra.Command {
4444
}
4545

4646
func listScripts(cmd *cobra.Command, flags runCmdFlags) []string {
47-
box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr())
47+
box, err := devbox.OpenWithoutWarnings(flags.config.path, cmd.ErrOrStderr())
4848
if err != nil {
4949
debug.Log("failed to open devbox: %v", err)
5050
return nil

internal/boxcli/shellenv.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
type shellEnvCmdFlags struct {
1414
config configFlags
1515
runInitHook bool
16+
install bool
1617
useCachedPrintDevEnv bool
1718
}
1819

@@ -36,6 +37,8 @@ func shellEnvCmd() *cobra.Command {
3637

3738
command.Flags().BoolVar(
3839
&flags.runInitHook, "init-hook", false, "runs init hook after exporting shell environment")
40+
command.Flags().BoolVar(
41+
&flags.install, "install", false, "install packages before exporting shell environment")
3942

4043
// This is no longer used. Remove after 0.4.8 is released.
4144
command.Flags().BoolVar(
@@ -56,6 +59,12 @@ func shellEnvFunc(cmd *cobra.Command, flags shellEnvCmdFlags) (string, error) {
5659
return "", err
5760
}
5861

62+
if flags.install {
63+
if err := box.Install(cmd.Context()); err != nil {
64+
return "", err
65+
}
66+
}
67+
5968
envStr, err := box.PrintEnv(cmd.Context(), flags.runInitHook)
6069
if err != nil {
6170
return "", err

internal/boxcli/update.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ func updateCmd() *cobra.Command {
2222
Short: "Update packages in your devbox",
2323
Long: "Update one, many, or all packages in your devbox. " +
2424
"If no packages are specified, all packages will be updated. " +
25-
"Only update versioned packages (e.g. `[email protected]`), not packages that are pinned to a nix channel (e.g. `python3`)",
25+
"Legacy non-versioned packages will be converted to @latest versioned " +
26+
"packages resolved to their current version.",
2627
PreRunE: ensureNixInstalled,
2728
RunE: func(cmd *cobra.Command, args []string) error {
2829
return updateCmdFunc(cmd, args, flags)

internal/impl/devbox.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ type Devbox struct {
7070
writer io.Writer
7171
}
7272

73-
func Open(path string, writer io.Writer) (*Devbox, error) {
73+
var legacyPackagesWarningHasBeenShown = false
74+
75+
func Open(path string, writer io.Writer, showWarnings bool) (*Devbox, error) {
7476
projectDir, err := findProjectDir(path)
7577
if err != nil {
7678
return nil, err
@@ -98,6 +100,18 @@ func Open(path string, writer io.Writer) (*Devbox, error) {
98100
plugin.WithLockfile(lock),
99101
)
100102
box.lockfile = lock
103+
104+
if showWarnings &&
105+
!legacyPackagesWarningHasBeenShown &&
106+
box.HasDeprecatedPackages() {
107+
legacyPackagesWarningHasBeenShown = true
108+
ux.Fwarning(
109+
os.Stderr, // Always stderr. box.writer should probably always be err.
110+
"Your devbox.json contains packages in legacy format. "+
111+
"Please run `devbox update` to update your devbox.json.\n",
112+
)
113+
}
114+
101115
return box, nil
102116
}
103117

@@ -395,7 +409,7 @@ func (d *Devbox) GenerateDockerfile(force bool) error {
395409
return errors.WithStack(generate.CreateDockerfile(tmplFS, d.projectDir, false /* isDevcontainer */))
396410
}
397411

398-
func (d *Devbox) PrintEnvrcContent(w io.Writer) error {
412+
func PrintEnvrcContent(w io.Writer) error {
399413
tmplName := "envrcContent.tmpl"
400414
t := template.Must(template.ParseFS(tmplFS, "tmpl/"+tmplName))
401415
// write content into file
@@ -977,6 +991,15 @@ func (d *Devbox) packagesAsInputs() []*nix.Input {
977991
return nix.InputsFromStrings(d.Packages(), d.lockfile)
978992
}
979993

994+
func (d *Devbox) HasDeprecatedPackages() bool {
995+
for _, pkg := range d.packagesAsInputs() {
996+
if pkg.IsLegacy() {
997+
return true
998+
}
999+
}
1000+
return false
1001+
}
1002+
9801003
func (d *Devbox) findPackageByName(name string) (string, error) {
9811004
results := map[string]bool{}
9821005
for _, pkg := range d.cfg.Packages {

internal/impl/devbox_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func testShellPlan(t *testing.T, testPath string) {
4141
t.Setenv(envir.XDGDataHome, "/tmp/devbox")
4242
assert := assert.New(t)
4343

44-
_, err := Open(baseDir, os.Stdout)
44+
_, err := Open(baseDir, os.Stdout, true)
4545
assert.NoErrorf(err, "%s should be a valid devbox project", baseDir)
4646
})
4747
}
@@ -65,7 +65,7 @@ func TestComputeNixEnv(t *testing.T) {
6565
path := t.TempDir()
6666
_, err := devconfig.Init(path, os.Stdout)
6767
require.NoError(t, err, "InitConfig should not fail")
68-
d, err := Open(path, os.Stdout)
68+
d, err := Open(path, os.Stdout, true)
6969
require.NoError(t, err, "Open should not fail")
7070
d.nix = &testNix{}
7171
ctx := context.Background()
@@ -78,7 +78,7 @@ func TestComputeNixPathIsIdempotent(t *testing.T) {
7878
dir := t.TempDir()
7979
_, err := devconfig.Init(dir, os.Stdout)
8080
require.NoError(t, err, "InitConfig should not fail")
81-
devbox, err := Open(dir, os.Stdout)
81+
devbox, err := Open(dir, os.Stdout, true)
8282
require.NoError(t, err, "Open should not fail")
8383
devbox.nix = &testNix{"/tmp/my/path"}
8484
ctx := context.Background()
@@ -104,7 +104,7 @@ func TestComputeNixPathWhenRemoving(t *testing.T) {
104104
dir := t.TempDir()
105105
_, err := devconfig.Init(dir, os.Stdout)
106106
require.NoError(t, err, "InitConfig should not fail")
107-
devbox, err := Open(dir, os.Stdout)
107+
devbox, err := Open(dir, os.Stdout, true)
108108
require.NoError(t, err, "Open should not fail")
109109
devbox.nix = &testNix{"/tmp/my/path"}
110110
ctx := context.Background()

internal/impl/tmpl/envrcContent.tmpl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use_devbox() {
22
watch_file devbox.json
3-
eval "$(devbox shellenv --init-hook)"
4-
devbox install
3+
eval "$(devbox shellenv --init-hook --install)"
54
}
65
use devbox

internal/impl/update.go

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,48 @@ import (
88
"fmt"
99

1010
"go.jetpack.io/devbox/internal/lock"
11+
"go.jetpack.io/devbox/internal/nix"
1112
"go.jetpack.io/devbox/internal/searcher"
1213
"go.jetpack.io/devbox/internal/ux"
1314
"go.jetpack.io/devbox/internal/wrapnix"
1415
)
1516

1617
func (d *Devbox) Update(ctx context.Context, pkgs ...string) error {
17-
var pkgsToUpdate []string
18-
for _, pkg := range pkgs {
19-
found, err := d.findPackageByName(pkg)
20-
if err != nil {
21-
return err
22-
}
23-
pkgsToUpdate = append(pkgsToUpdate, found)
18+
inputs, err := d.inputsToUpdate(pkgs...)
19+
if err != nil {
20+
return err
2421
}
25-
if len(pkgsToUpdate) == 0 {
26-
pkgsToUpdate = d.Packages()
22+
23+
for _, pkg := range inputs {
24+
if pkg.IsLegacy() {
25+
fmt.Fprintf(d.writer, "Updating %s -> %s\n", pkg.Raw, pkg.LegacyToVersioned())
26+
if err := d.Remove(ctx, pkg.Raw); err != nil {
27+
return err
28+
}
29+
if err := d.lockfile.ResolveToCurrentNixpkgCommitHash(
30+
pkg.LegacyToVersioned(),
31+
); err != nil {
32+
return err
33+
}
34+
if err := d.Add(ctx, pkg.LegacyToVersioned()); err != nil {
35+
return err
36+
}
37+
}
2738
}
2839

29-
for _, pkg := range pkgsToUpdate {
30-
if !lock.IsVersionedPackage(pkg) {
40+
for _, pkg := range inputs {
41+
if !lock.IsVersionedPackage(pkg.Raw) {
3142
fmt.Fprintf(d.writer, "Skipping %s because it is not a versioned package\n", pkg)
3243
continue
3344
}
34-
existing := d.lockfile.Packages[pkg]
35-
newEntry, err := searcher.Client().Resolve(pkg)
45+
existing := d.lockfile.Packages[pkg.Raw]
46+
newEntry, err := searcher.Client().Resolve(pkg.Raw)
3647
if err != nil {
3748
return err
3849
}
3950
if existing != nil && existing.Version != newEntry.Version {
4051
fmt.Fprintf(d.writer, "Updating %s %s -> %s\n", pkg, existing.Version, newEntry.Version)
41-
if err := d.removePackagesFromProfile(ctx, []string{pkg}); err != nil {
52+
if err := d.removePackagesFromProfile(ctx, []string{pkg.Raw}); err != nil {
4253
// Warn but continue. TODO(landau): ensurePackagesAreInstalled should
4354
// sync the profile so we don't need to do this manually.
4455
ux.Fwarning(d.writer, "Failed to remove %s from profile: %s\n", pkg, err)
@@ -49,7 +60,7 @@ func (d *Devbox) Update(ctx context.Context, pkgs ...string) error {
4960
fmt.Fprintf(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
5061
}
5162
// Set the new entry after we've removed the old package from the profile
52-
d.lockfile.Packages[pkg] = newEntry
63+
d.lockfile.Packages[pkg.Raw] = newEntry
5364
}
5465

5566
// TODO(landau): Improve output
@@ -59,3 +70,19 @@ func (d *Devbox) Update(ctx context.Context, pkgs ...string) error {
5970

6071
return wrapnix.CreateWrappers(ctx, d)
6172
}
73+
74+
func (d *Devbox) inputsToUpdate(pkgs ...string) ([]*nix.Input, error) {
75+
var pkgsToUpdate []string
76+
for _, pkg := range pkgs {
77+
found, err := d.findPackageByName(pkg)
78+
if err != nil {
79+
return nil, err
80+
}
81+
pkgsToUpdate = append(pkgsToUpdate, found)
82+
}
83+
if len(pkgsToUpdate) == 0 {
84+
pkgsToUpdate = d.Packages()
85+
}
86+
87+
return nix.InputsFromStrings(pkgsToUpdate, d.lockfile), nil
88+
}

internal/lock/lockfile.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ func (l *File) ForceResolve(pkg string) (*Package, error) {
9494
return l.Resolve(pkg)
9595
}
9696

97+
func (l *File) ResolveToCurrentNixpkgCommitHash(pkg string) error {
98+
name, version, found := strings.Cut(pkg, "@")
99+
if found && version != "latest" {
100+
return errors.New(
101+
"only allowed version is @latest. Otherwise we can't guarantee the " +
102+
"version will resolve")
103+
}
104+
l.Packages[pkg] = &Package{Resolved: l.LegacyNixpkgsPath(name)}
105+
return nil
106+
}
107+
97108
func (l *File) Save() error {
98109
// Never write lockfile if versioned packages is not enabled
99110
if !featureflag.LockFile.Enabled() {

0 commit comments

Comments
 (0)