Skip to content

Commit 3dc466c

Browse files
authored
[versioned] Add @latest to unversioned packages (#998)
## Summary Stacked on #997 This adds `@latest` whenever an unversioned non-flake package is added. This PR will be blocked until the php and haskell planners are updated to work with versioned packages. ## How was it tested? ```bash devbox add apache php curl ``` inspected devbox.json and lockfile.
1 parent d120b4d commit 3dc466c

File tree

6 files changed

+70
-49
lines changed

6 files changed

+70
-49
lines changed

examples/development/csharp/hello-world/devbox.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"packages": [
3-
"dotnet-sdk"
3+
"dotnet-sdk@latest"
44
],
55
"shell": {
66
"init_hook": null,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package featureflag
2+
3+
var AutoLatest = disabled("AUTO_LATEST")

internal/impl/devbox.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -959,24 +959,24 @@ func (d *Devbox) packagesAsInputs() []*nix.Input {
959959
}
960960

961961
func (d *Devbox) findPackageByName(name string) (string, error) {
962-
results := []string{}
962+
results := map[string]bool{}
963963
for _, pkg := range d.cfg.Packages {
964964
i := nix.InputFromString(pkg, d.lockfile)
965965
if i.String() == name || i.CanonicalName() == name {
966-
results = append(results, pkg)
966+
results[i.String()] = true
967967
}
968968
}
969969
if len(results) > 1 {
970970
return "", usererr.New(
971971
"found multiple packages with name %s: %s. Please specify version",
972972
name,
973-
results,
973+
lo.Keys(results),
974974
)
975975
}
976976
if len(results) == 0 {
977977
return "", usererr.New("no package found with name %s", name)
978978
}
979-
return results[0], nil
979+
return lo.Keys(results)[0], nil
980980
}
981981

982982
// configEnvs takes the computed env variables (nix + plugin) and adds env

internal/impl/packages.go

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"runtime/trace"
1414
"strings"
1515

16-
"github.com/fatih/color"
1716
"github.com/pkg/errors"
1817
"github.com/samber/lo"
1918
"golang.org/x/exp/slices"
2019

20+
"go.jetpack.io/devbox/internal/boxcli/usererr"
2121
"go.jetpack.io/devbox/internal/debug"
2222
"go.jetpack.io/devbox/internal/lock"
2323
"go.jetpack.io/devbox/internal/nix"
@@ -29,54 +29,53 @@ import (
2929
// packages.go has functions for adding, removing and getting info about nix packages
3030

3131
// Add adds the `pkgs` to the config (i.e. devbox.json) and nix profile for this devbox project
32-
func (d *Devbox) Add(ctx context.Context, pkgs ...string) error {
32+
func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
3333
ctx, task := trace.NewTask(ctx, "devboxAdd")
3434
defer task.End()
3535

36-
pkgs = lo.Uniq(pkgs)
36+
pkgs := nix.InputsFromStrings(lo.Uniq(pkgsNames), d.lockfile)
3737

38-
original := d.cfg.Packages
38+
versionedPackages := []*nix.Input{}
39+
// Add to Packages of the config only if it's not already there. We do this
40+
// before addin @latest to ensure we don't accidentally add a package that
41+
// is already in the config.
42+
for _, pkg := range pkgs {
43+
versioned := pkg.Versioned()
44+
versionedPackages = append(
45+
versionedPackages,
46+
nix.InputFromString(versioned, d.lockfile),
47+
)
48+
// Only add if the package doesn't exist versioned or unversioned.
49+
if !slices.Contains(d.cfg.Packages, pkg.Raw) && !slices.Contains(d.cfg.Packages, versioned) {
50+
d.cfg.Packages = append(d.cfg.Packages, versioned)
51+
}
52+
}
53+
pkgs = versionedPackages
3954

4055
// Check packages are valid before adding.
4156
for _, pkg := range pkgs {
42-
ok, err := nix.PkgExists(pkg, d.lockfile)
57+
ok, err := pkg.ValidateExists()
4358
if err != nil {
4459
return err
4560
}
4661
if !ok {
47-
return errors.WithMessage(nix.ErrPackageNotFound, pkg)
48-
}
49-
}
50-
51-
// Add to Packages of the config only if it's not already there
52-
for _, pkg := range pkgs {
53-
if slices.Contains(d.cfg.Packages, pkg) {
54-
continue
62+
return errors.WithMessage(nix.ErrPackageNotFound, pkg.Raw)
5563
}
56-
d.cfg.Packages = append(d.cfg.Packages, pkg)
57-
}
58-
if err := d.saveCfg(); err != nil {
59-
return err
6064
}
6165

6266
d.pluginManager.ApplyOptions(plugin.WithAddMode())
6367
if err := d.ensurePackagesAreInstalled(ctx, install); err != nil {
64-
// if installation fails, revert devbox.json
65-
// This is not perfect because there may be more than 1 package being
66-
// installed and we don't know which one failed. But it's better than
67-
// blindly add all packages.
68-
color.New(color.FgRed).Fprintf(
69-
d.writer,
70-
"There was an error installing nix packages: %v. "+
71-
"Packages were not added to devbox.json\n",
72-
strings.Join(pkgs, ", "),
68+
return usererr.WithUserMessage(
69+
err,
70+
"There was an error installing nix packages",
7371
)
74-
d.cfg.Packages = original
75-
_ = d.saveCfg() // ignore error to ensure we return the original error
72+
}
73+
74+
if err := d.saveCfg(); err != nil {
7675
return err
7776
}
7877

79-
for _, input := range nix.InputsFromStrings(pkgs, d.lockfile) {
78+
for _, input := range pkgs {
8079
if err := plugin.PrintReadme(
8180
input,
8281
d.projectDir,
@@ -87,48 +86,58 @@ func (d *Devbox) Add(ctx context.Context, pkgs ...string) error {
8786
}
8887
}
8988

90-
if err := d.lockfile.Add(pkgs...); err != nil {
89+
if err := d.lockfile.Add(
90+
lo.Map(pkgs, func(pkg *nix.Input, _ int) string { return pkg.Raw })...,
91+
); err != nil {
9192
return err
9293
}
9394

9495
return wrapnix.CreateWrappers(ctx, d)
9596
}
9697

97-
// Remove removes the `pkgs` from the config (i.e. devbox.json) and nix profile for this devbox project
98+
// Remove removes the `pkgs` from the config (i.e. devbox.json) and nix profile
99+
// for this devbox project
98100
func (d *Devbox) Remove(ctx context.Context, pkgs ...string) error {
99101
ctx, task := trace.NewTask(ctx, "devboxRemove")
100102
defer task.End()
101103

102-
pkgs = lo.Uniq(pkgs)
104+
packagesToUninstall := []string{}
105+
missingPkgs := []string{}
106+
for _, pkg := range lo.Uniq(pkgs) {
107+
found, _ := d.findPackageByName(pkg)
108+
if found != "" {
109+
packagesToUninstall = append(packagesToUninstall, found)
110+
d.cfg.Packages = lo.Without(d.cfg.Packages, found)
111+
} else {
112+
missingPkgs = append(missingPkgs, pkg)
113+
}
114+
}
103115

104-
// First, save which packages are being uninstalled. Do this before we modify d.cfg.RawPackages below.
105-
uninstalledPackages := lo.Intersect(d.cfg.Packages, pkgs)
106-
remainingPkgs, missingPkgs := lo.Difference(d.cfg.Packages, pkgs)
107116
if len(missingPkgs) > 0 {
108117
ux.Fwarning(
109118
d.writer,
110119
"the following packages were not found in your devbox.json: %s\n",
111120
strings.Join(missingPkgs, ", "),
112121
)
113122
}
114-
d.cfg.Packages = remainingPkgs
115-
if err := d.saveCfg(); err != nil {
123+
124+
if err := plugin.Remove(d.projectDir, packagesToUninstall); err != nil {
116125
return err
117126
}
118127

119-
if err := plugin.Remove(d.projectDir, uninstalledPackages); err != nil {
128+
if err := d.removePackagesFromProfile(ctx, packagesToUninstall); err != nil {
120129
return err
121130
}
122131

123-
if err := d.removePackagesFromProfile(ctx, uninstalledPackages); err != nil {
132+
if err := d.ensurePackagesAreInstalled(ctx, uninstall); err != nil {
124133
return err
125134
}
126135

127-
if err := d.ensurePackagesAreInstalled(ctx, uninstall); err != nil {
136+
if err := d.lockfile.Remove(packagesToUninstall...); err != nil {
128137
return err
129138
}
130139

131-
if err := d.lockfile.Remove(uninstalledPackages...); err != nil {
140+
if err := d.saveCfg(); err != nil {
132141
return err
133142
}
134143

internal/nix/input.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/samber/lo"
1616

17+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
1718
"go.jetpack.io/devbox/internal/boxcli/usererr"
1819
"go.jetpack.io/devbox/internal/lock"
1920
"go.jetpack.io/devbox/internal/searcher"
@@ -22,6 +23,7 @@ import (
2223
type Input struct {
2324
url.URL
2425
lockfile lock.Locker
26+
Raw string
2527
}
2628

2729
func InputsFromStrings(names []string, l lock.Locker) []*Input {
@@ -39,7 +41,7 @@ func InputFromString(s string, l lock.Locker) *Input {
3941
// path urls have a single slash (instead of possibly 3 slashes)
4042
u, _ = url.Parse("path:" + filepath.Join(l.ProjectDir(), u.Opaque))
4143
}
42-
return &Input{*u, l}
44+
return &Input{*u, l, s}
4345
}
4446

4547
func (i *Input) IsLocal() bool {
@@ -180,7 +182,7 @@ func (i *Input) hash() string {
180182
return shortHash
181183
}
182184

183-
func (i *Input) validateExists() (bool, error) {
185+
func (i *Input) ValidateExists() (bool, error) {
184186
if i.isVersioned() {
185187
version := i.version()
186188
if version == "" && i.isVersioned() {
@@ -223,6 +225,13 @@ func (i *Input) CanonicalName() string {
223225
return name
224226
}
225227

228+
func (i *Input) Versioned() string {
229+
if featureflag.AutoLatest.Enabled() && i.IsDevboxPackage() && !i.isVersioned() {
230+
return i.Raw + "@latest"
231+
}
232+
return i.Raw
233+
}
234+
226235
// version returns the version of the package
227236
// it only applies to devbox packages
228237
func (i *Input) version() string {

internal/nix/search.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var ErrPackageNotFound = errors.New("package not found")
1616
var ErrPackageNotInstalled = errors.New("package not installed")
1717

1818
func PkgExists(pkg string, lock *lock.File) (bool, error) {
19-
return InputFromString(pkg, lock).validateExists()
19+
return InputFromString(pkg, lock).ValidateExists()
2020
}
2121

2222
type Info struct {

0 commit comments

Comments
 (0)