Skip to content

Commit 0381aa5

Browse files
authored
Library update/install with --no-overwrite will perform the update if it's possible to keep already installed dependencies at their current version (#2431)
* Updated semver library * Improved behaviour of 'lib install --no-overwrite' flag Previously the --no-overwrite flag would fail to install if a library dependency was already installed but not at the latest version. After this change the already present library may be accepted as a dependency if it match the version constraints of the installed library. * Fixed integration test * Added integration test * Always use 'installed' version if available * Allow a bit more time for slow CI
1 parent af0cc74 commit 0381aa5

File tree

14 files changed

+423
-319
lines changed

14 files changed

+423
-319
lines changed

.licenses/go/go.bug.st/relaxed-semver.dep.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: go.bug.st/relaxed-semver
3-
version: v0.11.0
3+
version: v0.12.0
44
type: go
55
summary:
66
homepage: https://pkg.go.dev/go.bug.st/relaxed-semver

arduino/libraries/librariesindex/index.go

+22-22
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Library struct {
4444
type Release struct {
4545
Author string
4646
Version *semver.Version
47-
Dependencies []semver.Dependency
47+
Dependencies []*Dependency
4848
Maintainer string
4949
Sentence string
5050
Paragraph string
@@ -85,7 +85,7 @@ func (r *Release) GetVersion() *semver.Version {
8585
}
8686

8787
// GetDependencies returns the dependencies of this library.
88-
func (r *Release) GetDependencies() []semver.Dependency {
88+
func (r *Release) GetDependencies() []*Dependency {
8989
return r.Dependencies
9090
}
9191

@@ -144,31 +144,31 @@ func (idx *Index) FindLibraryUpdate(lib *libraries.Library) *Release {
144144
return nil
145145
}
146146

147-
// ResolveDependencies returns the dependencies of a library release.
148-
func (idx *Index) ResolveDependencies(lib *Release) []*Release {
149-
// Box lib index *Release to be digested by dep-resolver
150-
// (TODO: There is a better use of golang interfaces to avoid this?)
151-
allReleases := map[string]semver.Releases{}
152-
for _, indexLib := range idx.Libraries {
153-
releases := semver.Releases{}
147+
// ResolveDependencies resolve the dependencies of a library release and returns a
148+
// possible solution (the set of library releases to install together with the library).
149+
// An optional "override" releases may be passed if we want to exclude the same
150+
// libraries from the index (for example if we want to keep an installed library).
151+
func (idx *Index) ResolveDependencies(lib *Release, overrides []*Release) []*Release {
152+
resolver := semver.NewResolver[*Release, *Dependency]()
153+
154+
overridden := map[string]bool{}
155+
for _, override := range overrides {
156+
resolver.AddRelease(override)
157+
overridden[override.GetName()] = true
158+
}
159+
160+
// Create and populate the library resolver
161+
for libName, indexLib := range idx.Libraries {
162+
if _, ok := overridden[libName]; ok {
163+
continue
164+
}
154165
for _, indexLibRelease := range indexLib.Releases {
155-
releases = append(releases, indexLibRelease)
166+
resolver.AddRelease(indexLibRelease)
156167
}
157-
allReleases[indexLib.Name] = releases
158168
}
159169

160170
// Perform lib resolution
161-
archive := &semver.Archive{
162-
Releases: allReleases,
163-
}
164-
deps := archive.Resolve(lib)
165-
166-
// Unbox resolved deps back into *Release
167-
res := []*Release{}
168-
for _, dep := range deps {
169-
res = append(res, dep.(*Release))
170-
}
171-
return res
171+
return resolver.Resolve(lib)
172172
}
173173

174174
// Versions returns an array of all versions available of the library

arduino/libraries/librariesindex/index_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestIndexer(t *testing.T) {
9090
rtcInexistent2 := index.FindLibraryUpdate(&libraries.Library{Name: "RTCZero-blah", Version: semver.MustParse("1.0.0")})
9191
require.Nil(t, rtcInexistent2)
9292

93-
resolve1 := index.ResolveDependencies(alp.Releases["1.2.1"])
93+
resolve1 := index.ResolveDependencies(alp.Releases["1.2.1"], nil)
9494
require.Len(t, resolve1, 2)
9595
require.Contains(t, resolve1, alp.Releases["1.2.1"])
9696
require.Contains(t, resolve1, rtc.Releases["1.6.0"])
@@ -108,7 +108,7 @@ func TestIndexer(t *testing.T) {
108108
require.NotNil(t, http040)
109109
require.Equal(t, "ArduinoHttpClient@0.4.0", http040.String())
110110

111-
resolve2 := index.ResolveDependencies(oauth010)
111+
resolve2 := index.ResolveDependencies(oauth010, nil)
112112
require.Len(t, resolve2, 4)
113113
require.Contains(t, resolve2, oauth010)
114114
require.Contains(t, resolve2, eccx135)

arduino/libraries/librariesindex/json.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ func (indexLib *indexRelease) extractReleaseIn(library *Library) {
126126
}
127127
}
128128

129-
func (indexLib *indexRelease) extractDependencies() []semver.Dependency {
130-
res := []semver.Dependency{}
129+
func (indexLib *indexRelease) extractDependencies() []*Dependency {
130+
res := []*Dependency{}
131131
if indexLib.Dependencies == nil || len(indexLib.Dependencies) == 0 {
132132
return res
133133
}

arduino/libraries/librariesmanager/librariesmanager.go

+14
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,20 @@ func (lm *LibrariesManager) FindByReference(libRef *librariesindex.Reference, in
225225
return alternatives.FilterByVersionAndInstallLocation(libRef.Version, installLocation)
226226
}
227227

228+
// FindAllInstalled returns all the installed libraries
229+
func (lm *LibrariesManager) FindAllInstalled() libraries.List {
230+
var res libraries.List
231+
for _, libAlternatives := range lm.Libraries {
232+
for _, libRelease := range libAlternatives {
233+
if libRelease.InstallDir == nil {
234+
continue
235+
}
236+
res.Add(libRelease)
237+
}
238+
}
239+
return res
240+
}
241+
228242
func (lm *LibrariesManager) clearLibraries() {
229243
for k := range lm.Libraries {
230244
delete(lm.Libraries, k)

commands/lib/install.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloa
4747
}
4848
} else {
4949
res, err := LibraryResolveDependencies(ctx, &rpc.LibraryResolveDependenciesRequest{
50-
Instance: req.GetInstance(),
51-
Name: req.GetName(),
52-
Version: req.GetVersion(),
50+
Instance: req.GetInstance(),
51+
Name: req.GetName(),
52+
Version: req.GetVersion(),
53+
DoNotUpdateInstalledLibraries: req.GetNoOverwrite(),
5354
})
5455
if err != nil {
5556
return err

commands/lib/resolve_deps.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222

2323
"github.com/arduino/arduino-cli/arduino"
2424
"github.com/arduino/arduino-cli/arduino/libraries"
25+
"github.com/arduino/arduino-cli/arduino/libraries/librariesindex"
2526
"github.com/arduino/arduino-cli/commands/internal/instances"
2627
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
28+
semver "go.bug.st/relaxed-semver"
2729
)
2830

2931
// LibraryResolveDependencies FIXMEDOC
@@ -46,7 +48,21 @@ func LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDepe
4648
}
4749

4850
// Resolve all dependencies...
49-
deps := lm.Index.ResolveDependencies(reqLibRelease)
51+
var overrides []*librariesindex.Release
52+
if req.GetDoNotUpdateInstalledLibraries() {
53+
libs := lm.FindAllInstalled()
54+
libs = libs.FilterByVersionAndInstallLocation(nil, libraries.User)
55+
for _, lib := range libs {
56+
release := lm.Index.FindRelease(&librariesindex.Reference{
57+
Name: lib.Name,
58+
Version: lib.Version,
59+
})
60+
if release != nil {
61+
overrides = append(overrides, release)
62+
}
63+
}
64+
}
65+
deps := lm.Index.ResolveDependencies(reqLibRelease, overrides)
5066

5167
// If no solution has been found
5268
if len(deps) == 0 {
@@ -65,14 +81,19 @@ func LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDepe
6581
res := []*rpc.LibraryDependencyStatus{}
6682
for _, dep := range deps {
6783
// ...and add information on currently installed versions of the libraries
68-
installed := ""
84+
var installed *semver.Version
85+
required := dep.GetVersion()
6986
if installedLib, has := installedLibs[dep.GetName()]; has {
70-
installed = installedLib.Version.String()
87+
installed = installedLib.Version
88+
if installed != nil && required != nil && installed.Equal(required) {
89+
// avoid situations like installed=0.53 and required=0.53.0
90+
required = installed
91+
}
7192
}
7293
res = append(res, &rpc.LibraryDependencyStatus{
7394
Name: dep.GetName(),
74-
VersionRequired: dep.GetVersion().String(),
75-
VersionInstalled: installed,
95+
VersionRequired: required.String(),
96+
VersionInstalled: installed.String(),
7697
})
7798
}
7899
sort.Slice(res, func(i, j int) bool {

commands/lib/search.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func getLibraryParameters(rel *librariesindex.Release) *rpc.LibraryRelease {
118118
}
119119
}
120120

121-
func getLibraryDependenciesParameter(deps []semver.Dependency) []*rpc.LibraryDependency {
121+
func getLibraryDependenciesParameter(deps []*librariesindex.Dependency) []*rpc.LibraryDependency {
122122
res := []*rpc.LibraryDependency{}
123123
for _, dep := range deps {
124124
res = append(res, &rpc.LibraryDependency{

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ require (
3636
github.com/xeipuuv/gojsonschema v1.2.0
3737
go.bug.st/cleanup v1.0.0
3838
go.bug.st/downloader/v2 v2.1.1
39-
go.bug.st/relaxed-semver v0.11.0
39+
go.bug.st/relaxed-semver v0.12.0
4040
go.bug.st/serial v1.6.1
4141
go.bug.st/testifyjson v1.1.1
4242
golang.org/x/term v0.14.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,8 @@ go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk=
455455
go.bug.st/downloader/v2 v2.1.1 h1:nyqbUizo3E2IxCCm4YFac4FtSqqFpqWP+Aae5GCMuw4=
456456
go.bug.st/downloader/v2 v2.1.1/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII=
457457
go.bug.st/relaxed-semver v0.9.0/go.mod h1:ug0/W/RPYUjliE70Ghxg77RDHmPxqpo7SHV16ijss7Q=
458-
go.bug.st/relaxed-semver v0.11.0 h1:ngzpUlBEZ5F9hJnMZP55LIFbgX3bCztBBufMhJViAsY=
459-
go.bug.st/relaxed-semver v0.11.0/go.mod h1:rqPEm+790OTQlAdfSJSHWwpKOg3A8UyvAWMZxYkQivc=
458+
go.bug.st/relaxed-semver v0.12.0 h1:se8v3lTdAAFp68+/RS/0Y/nFdnpdzkP5ICY04SPau4E=
459+
go.bug.st/relaxed-semver v0.12.0/go.mod h1:Cpcbiig6Omwlq6bS7i3MQWiqS7W7HDd8CAnZFC40Cl0=
460460
go.bug.st/serial v1.3.2/go.mod h1:jDkjqASf/qSjmaOxHSHljwUQ6eHo/ZX/bxJLQqSlvZg=
461461
go.bug.st/serial v1.6.1 h1:VSSWmUxlj1T/YlRo2J104Zv3wJFrjHIl/T3NeruWAHY=
462462
go.bug.st/serial v1.6.1/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=

internal/cli/lib/check_deps.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
)
3434

3535
func initDepsCommand() *cobra.Command {
36+
var noOverwrite bool
3637
depsCommand := &cobra.Command{
3738
Use: fmt.Sprintf("deps %s[@%s]...", tr("LIBRARY"), tr("VERSION_NUMBER")),
3839
Short: tr("Check dependencies status for the specified library."),
@@ -41,15 +42,18 @@ func initDepsCommand() *cobra.Command {
4142
" " + os.Args[0] + " lib deps AudioZero # " + tr("for the latest version.") + "\n" +
4243
" " + os.Args[0] + " lib deps AudioZero@1.0.0 # " + tr("for the specific version."),
4344
Args: cobra.ExactArgs(1),
44-
Run: runDepsCommand,
45+
Run: func(cmd *cobra.Command, args []string) {
46+
runDepsCommand(args, noOverwrite)
47+
},
4548
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
4649
return arguments.GetInstalledLibraries(), cobra.ShellCompDirectiveDefault
4750
},
4851
}
52+
depsCommand.Flags().BoolVar(&noOverwrite, "no-overwrite", false, tr("Do not try to update library dependencies if already installed."))
4953
return depsCommand
5054
}
5155

52-
func runDepsCommand(cmd *cobra.Command, args []string) {
56+
func runDepsCommand(args []string, noOverwrite bool) {
5357
instance := instance.CreateAndInit()
5458
logrus.Info("Executing `arduino-cli lib deps`")
5559
libRef, err := ParseLibraryReferenceArgAndAdjustCase(instance, args[0])
@@ -58,9 +62,10 @@ func runDepsCommand(cmd *cobra.Command, args []string) {
5862
}
5963

6064
deps, err := lib.LibraryResolveDependencies(context.Background(), &rpc.LibraryResolveDependenciesRequest{
61-
Instance: instance,
62-
Name: libRef.Name,
63-
Version: libRef.Version,
65+
Instance: instance,
66+
Name: libRef.Name,
67+
Version: libRef.Version,
68+
DoNotUpdateInstalledLibraries: noOverwrite,
6469
})
6570
if err != nil {
6671
feedback.Fatal(tr("Error resolving dependencies for %[1]s: %[2]s", libRef, err), feedback.ErrGeneric)

internal/integrationtest/lib/lib_test.go

+52-6
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,10 @@ func TestInstallLibraryWithDependencies(t *testing.T) {
589589
require.NoError(t, err)
590590
_, _, err = cli.Run("lib", "install", "SD@1.2.3")
591591
require.NoError(t, err)
592-
_, _, err = cli.Run("lib", "install", "Arduino_Builtin", "--no-overwrite")
593-
require.Error(t, err)
592+
// This time it should accept the installation with the currently installed SD 1.2.3
593+
out, _, err := cli.Run("lib", "install", "Arduino_Builtin", "--no-overwrite")
594+
require.NoError(t, err)
595+
require.Contains(t, string(out), "Already installed SD@1.2.3")
594596
}
595597

596598
func TestInstallNoDeps(t *testing.T) {
@@ -1653,9 +1655,6 @@ func TestDependencyResolver(t *testing.T) {
16531655
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
16541656
defer env.CleanUp()
16551657

1656-
_, _, err := cli.Run("lib", "update-index")
1657-
require.NoError(t, err)
1658-
16591658
done := make(chan bool)
16601659
go func() {
16611660
_, _, err := cli.Run("lib", "install", "NTPClient_Generic")
@@ -1665,7 +1664,54 @@ func TestDependencyResolver(t *testing.T) {
16651664

16661665
select {
16671666
case <-done:
1668-
case <-time.After(time.Second * 2):
1667+
case <-time.After(time.Second * 10):
16691668
require.FailNow(t, "The install command didn't complete in the allocated time")
16701669
}
16711670
}
1671+
1672+
func TestDependencyResolverNoOverwrite(t *testing.T) {
1673+
// https://github.com/arduino/arduino-cli/issues/1799
1674+
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
1675+
defer env.CleanUp()
1676+
1677+
_, _, err := cli.Run("lib", "install", "Bounce2@2.53.0")
1678+
require.NoError(t, err)
1679+
1680+
out, _, err := cli.Run("lib", "deps", "EncoderTool@2.2.0", "--format", "json")
1681+
require.NoError(t, err)
1682+
outjson := requirejson.Parse(t, out)
1683+
outjson.MustContain(`{
1684+
"dependencies": [
1685+
{
1686+
"name": "Bounce2",
1687+
"version_installed": "2.53"
1688+
},
1689+
{
1690+
"name": "EncoderTool",
1691+
"version_required": "2.2.0"
1692+
}
1693+
]
1694+
}`)
1695+
require.NotEqual(t, outjson.Query("dependencies[0].version_required").String(), `"2.53.0"`)
1696+
require.NotEqual(t, outjson.Query("dependencies[0].version_required").String(), `"2.53"`)
1697+
1698+
out, _, err = cli.Run("lib", "deps", "EncoderTool@2.2.0", "--no-overwrite", "--format", "json")
1699+
require.NoError(t, err)
1700+
outjson = requirejson.Parse(t, out)
1701+
outjson.MustContain(`{
1702+
"dependencies": [
1703+
{
1704+
"name": "Bounce2",
1705+
"version_required": "2.53",
1706+
"version_installed": "2.53"
1707+
},
1708+
{
1709+
"name": "EncoderTool",
1710+
"version_required": "2.2.0"
1711+
}
1712+
]
1713+
}`)
1714+
1715+
_, _, err = cli.Run("lib", "install", "EncoderTool@2.2.0", "--no-overwrite")
1716+
require.NoError(t, err)
1717+
}

0 commit comments

Comments
 (0)