Skip to content

Commit 2327b35

Browse files
committed
ProfileLibAdd and ProfileLibRemove can now cleanup unneeded dependencies
1 parent a234e99 commit 2327b35

File tree

7 files changed

+1097
-600
lines changed

7 files changed

+1097
-600
lines changed

commands/service_profile_lib_add.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package commands
1818
import (
1919
"context"
2020
"net/url"
21+
"slices"
2122

2223
"github.com/arduino/arduino-cli/commands/cmderrors"
2324
"github.com/arduino/arduino-cli/commands/internal/instances"
@@ -86,27 +87,35 @@ func (s *arduinoCoreServerImpl) ProfileLibAdd(ctx context.Context, req *rpc.Prof
8687
return nil, err
8788
}
8889

89-
add := func(libReleaseToAdd *librariesindex.Release) {
90+
add := func(libReleaseToAdd *librariesindex.Release, isDep bool) {
9091
libRefToAdd := &sketch.ProfileLibraryReference{
91-
Library: libReleaseToAdd.GetName(),
92-
Version: libReleaseToAdd.GetVersion(),
92+
Library: libReleaseToAdd.GetName(),
93+
Version: libReleaseToAdd.GetVersion(),
94+
IsDependency: isDep,
9395
}
9496
existingLibRef, _ := profile.GetLibrary(libReleaseToAdd.GetName())
9597
if existingLibRef == nil {
9698
profile.Libraries = append(profile.Libraries, libRefToAdd)
9799
addedLibs = append(addedLibs, libRefToAdd)
98100
return
99101
}
100-
// If the same version of the library has been already added to the profile, skip it
101-
if existingLibRef.Version.Equal(libReleaseToAdd.GetVersion()) {
102-
skippedLibs = append(skippedLibs, libRefToAdd)
103-
return
102+
103+
// The library is already present in the profile.
104+
105+
// If the existing library was a dependency, and we are adding a non-dependency,
106+
// update the flag to indicate that it's not a dependency anymore.
107+
if !isDep && existingLibRef.IsDependency {
108+
existingLibRef.IsDependency = false
104109
}
105-
// If the library has been already added to the profile, just update the version
106-
if req.GetNoOverwrite() {
110+
111+
// If no-overwrite is specified, skip updating the library version.
112+
// If the same version of the library has been already added to the profile, skip it.
113+
if req.GetNoOverwrite() || existingLibRef.Version.Equal(libReleaseToAdd.GetVersion()) {
107114
skippedLibs = append(skippedLibs, libRefToAdd)
108115
return
109116
}
117+
118+
// otherwise update the version of the library
110119
existingLibRef.Version = libReleaseToAdd.GetVersion()
111120
addedLibs = append(addedLibs, existingLibRef)
112121
}
@@ -117,11 +126,14 @@ func (s *arduinoCoreServerImpl) ProfileLibAdd(ctx context.Context, req *rpc.Prof
117126
if err != nil {
118127
return nil, err
119128
}
129+
// sort to make the output order deterministic
130+
slices.SortFunc(libWithDeps, librariesindex.ReleaseCompare)
120131
for _, lib := range libWithDeps {
121-
add(lib)
132+
isDep := libRelease.GetName() != lib.GetName()
133+
add(lib, isDep)
122134
}
123135
} else {
124-
add(libRelease)
136+
add(libRelease, false)
125137
}
126138
} else {
127139
return nil, &cmderrors.InvalidArgumentError{Message: "library must be specified"}

commands/service_profile_lib_remove.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ package commands
1717

1818
import (
1919
"context"
20+
"slices"
2021

2122
"github.com/arduino/arduino-cli/commands/cmderrors"
23+
"github.com/arduino/arduino-cli/commands/internal/instances"
24+
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
2225
"github.com/arduino/arduino-cli/internal/arduino/sketch"
2326
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2427
paths "github.com/arduino/go-paths-helper"
@@ -47,19 +50,79 @@ func (s *arduinoCoreServerImpl) ProfileLibRemove(ctx context.Context, req *rpc.P
4750
return nil, err
4851
}
4952

53+
var removedLibraries []*rpc.ProfileLibraryReference
54+
remove := func(libraryToRemove *sketch.ProfileLibraryReference) error {
55+
removedLibrary, err := profile.RemoveLibrary(libraryToRemove)
56+
if err != nil {
57+
return &cmderrors.InvalidArgumentError{Cause: err}
58+
}
59+
removedLibraries = append(removedLibraries, removedLibrary.ToRpc())
60+
return nil
61+
}
62+
5063
libToRemove, err := sketch.FromRpcProfileLibraryReference(req.GetLibrary())
5164
if err != nil {
5265
return nil, &cmderrors.InvalidArgumentError{Message: "invalid library reference", Cause: err}
5366
}
54-
removedLib, err := profile.RemoveLibrary(libToRemove)
55-
if err != nil {
56-
return nil, &cmderrors.InvalidArgumentError{Cause: err}
67+
if err := remove(libToRemove); err != nil {
68+
return nil, err
69+
}
70+
71+
// Get the dependencies of the libraries to see if any of them could be removed as well
72+
if req.GetRemoveDependencies() {
73+
if req.GetLibrary().GetIndexLibrary() == nil {
74+
// No dependencies to remove
75+
return nil, &cmderrors.InvalidArgumentError{Message: "automatic dependency removal is supported only for IndexLibraries"}
76+
}
77+
// Obtain the library index from the manager
78+
li, err := instances.GetLibrariesIndex(req.GetInstance())
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// Get all the dependencies required by the profile excluding the removed library
84+
requiredDeps := map[string]bool{}
85+
requiredDeps[libToRemove.String()] = true
86+
for _, profLib := range profile.Libraries {
87+
if profLib.IsDependency {
88+
continue
89+
}
90+
if profLib.Library == "" {
91+
continue
92+
}
93+
deps, err := libraryResolveDependencies(li, profLib.Library, profLib.Version.String(), nil)
94+
if err != nil {
95+
return nil, &cmderrors.InvalidArgumentError{Cause: err, Message: "cannot resolve dependencies for installed libraries"}
96+
}
97+
for _, dep := range deps {
98+
requiredDeps[dep.String()] = true
99+
}
100+
}
101+
102+
depsOfLibToRemove, err := libraryResolveDependencies(li, libToRemove.Library, libToRemove.Version.String(), nil)
103+
if err != nil {
104+
return nil, err
105+
}
106+
// sort to make the output order deterministic
107+
slices.SortFunc(depsOfLibToRemove, librariesindex.ReleaseCompare)
108+
// deps contains the main library as well, so we skip it when removing dependencies
109+
for _, depToRemove := range depsOfLibToRemove {
110+
if requiredDeps[depToRemove.String()] {
111+
continue
112+
}
113+
if err := remove(&sketch.ProfileLibraryReference{Library: depToRemove.Library.Name, Version: depToRemove.Version}); err != nil {
114+
return nil, err
115+
}
116+
}
57117
}
58118

59119
err = projectFilePath.WriteFile([]byte(sk.Project.AsYaml()))
60120
if err != nil {
61121
return nil, err
62122
}
63123

64-
return &rpc.ProfileLibRemoveResponse{Library: removedLib.ToRpc(), ProfileName: profileName}, nil
124+
return &rpc.ProfileLibRemoveResponse{
125+
RemovedLibraries: removedLibraries,
126+
ProfileName: profileName,
127+
}, nil
65128
}

internal/arduino/libraries/librariesindex/index.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package librariesindex
1717

1818
import (
1919
"sort"
20+
"strings"
2021

2122
"github.com/arduino/arduino-cli/commands/cmderrors"
2223
"github.com/arduino/arduino-cli/internal/arduino/libraries"
@@ -90,6 +91,14 @@ func (r *Release) GetDependencies() []*Dependency {
9091
return r.Dependencies
9192
}
9293

94+
// ReleaseCompare compares two library releases by name, or by version if the names are equal.
95+
func ReleaseCompare(r1, r2 *Release) int {
96+
if cmp := strings.Compare(r1.GetName(), r2.GetName()); cmp != 0 {
97+
return cmp
98+
}
99+
return r1.GetVersion().CompareTo(r2.GetVersion())
100+
}
101+
93102
// Dependency is a library dependency
94103
type Dependency struct {
95104
Name string

internal/integrationtest/arduino-cli.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,69 @@ func (inst *ArduinoCLIInstance) NewSketch(ctx context.Context, sketchName, sketc
741741
logCallf(">>> NewSketch(%+v)\n", req)
742742
return inst.cli.daemonClient.NewSketch(ctx, req)
743743
}
744+
745+
func (inst *ArduinoCLIInstance) ProfileCreate(
746+
ctx context.Context,
747+
profileName, sketchPath string,
748+
fqbn string,
749+
defaultProfile bool,
750+
) (*commands.ProfileCreateResponse, error) {
751+
req := &commands.ProfileCreateRequest{
752+
Instance: inst.instance,
753+
SketchPath: sketchPath,
754+
ProfileName: profileName,
755+
Fqbn: fqbn,
756+
DefaultProfile: defaultProfile,
757+
}
758+
logCallf(">>> ProfileCreate(%+v)\n", req)
759+
resp, err := inst.cli.daemonClient.ProfileCreate(ctx, req)
760+
return resp, err
761+
}
762+
763+
func (inst *ArduinoCLIInstance) ProfileLibList(ctx context.Context, sketchPath, profileName string) (*commands.ProfileLibListResponse, error) {
764+
req := &commands.ProfileLibListRequest{
765+
SketchPath: sketchPath,
766+
ProfileName: profileName,
767+
}
768+
logCallf(">>> ProfileListLib(%+v)\n", req)
769+
resp, err := inst.cli.daemonClient.ProfileLibList(ctx, req)
770+
return resp, err
771+
}
772+
773+
func (inst *ArduinoCLIInstance) ProfileLibAdd(
774+
ctx context.Context,
775+
sketchPath, profileName string,
776+
library *commands.ProfileLibraryReference,
777+
addDependencies, noOverwrite bool,
778+
) (*commands.ProfileLibAddResponse, error) {
779+
req := &commands.ProfileLibAddRequest{
780+
Instance: inst.instance,
781+
SketchPath: sketchPath,
782+
ProfileName: profileName,
783+
Library: library,
784+
AddDependencies: &addDependencies,
785+
NoOverwrite: &noOverwrite,
786+
}
787+
logCallf(">>> ProfileLibAdd(%+v)\n", req)
788+
resp, err := inst.cli.daemonClient.ProfileLibAdd(ctx, req)
789+
return resp, err
790+
}
791+
792+
func (inst *ArduinoCLIInstance) ProfileLibRemove(
793+
ctx context.Context,
794+
sketchPath,
795+
profileName string,
796+
library *commands.ProfileLibraryReference,
797+
removeDependencies bool,
798+
) (*commands.ProfileLibRemoveResponse, error) {
799+
req := &commands.ProfileLibRemoveRequest{
800+
Instance: inst.instance,
801+
SketchPath: sketchPath,
802+
ProfileName: profileName,
803+
Library: library,
804+
RemoveDependencies: &removeDependencies,
805+
}
806+
logCallf(">>> ProfileLibRemove(%+v)\n", req)
807+
resp, err := inst.cli.daemonClient.ProfileLibRemove(ctx, req)
808+
return resp, err
809+
}

0 commit comments

Comments
 (0)