Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c7d0cb4
Added gRPC functions to manage libraries in profiles
cmaglie Oct 3, 2025
92cf64f
Removed ProfileDump gRPC command.
cmaglie Oct 6, 2025
ed5d6a2
Moved SketchProfileLibraryReference in the proper commono.proto file
cmaglie Oct 6, 2025
c74f073
linter: removed unneeded type specification
cmaglie Oct 6, 2025
34c378c
Renamed SketchProfileLibraryReference -> ProfileLibraryReference
cmaglie Oct 22, 2025
0c78b3f
Renamed InitProfile -> ProfileCreate
cmaglie Oct 22, 2025
3c5a3c9
Small refactoring, no code change
cmaglie Oct 23, 2025
4d78f7f
Fixed error messages
cmaglie Oct 23, 2025
d4cbad2
Removed unnecessary type specifier
cmaglie Oct 24, 2025
140c069
Refactored libraryResolveDependencies.
cmaglie Oct 27, 2025
5ac03d8
Added support for 'dependency:' field in profiles libraries
cmaglie Oct 29, 2025
e17724b
ProfileLibAdd and ProfileLibRemove can now cleanup unneeded dependencies
cmaglie Oct 29, 2025
4bb86e7
Better error messages
cmaglie Nov 21, 2025
f520cb9
Simplified Profile.RemoveLibrary(...) method.
cmaglie Nov 21, 2025
4e80e73
Fixed algorithm for determination of required deps
cmaglie Nov 21, 2025
dbafbe6
Updated docs
cmaglie Nov 25, 2025
4f527c0
Rename DuplicateProfileError -> ProfileAlreadyExitsError
cmaglie Nov 26, 2025
1909ab7
Removed useless field in gRPC ProfileCreateResponse
cmaglie Nov 26, 2025
8b4b3a5
fix: ProfileCreate sets the new profile as default only if asked to d…
cmaglie Nov 26, 2025
ad5d941
Improved docs
cmaglie Dec 1, 2025
3a14601
Applied code review suggestion
cmaglie Dec 1, 2025
2aa7e32
Using cmp.Or helper
cmaglie Dec 1, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ProfileLibAdd and ProfileLibRemove can now cleanup unneeded dependencies
  • Loading branch information
cmaglie committed Nov 25, 2025
commit e17724b36e05ebcb29327273081b596520a9ea4a
34 changes: 23 additions & 11 deletions commands/service_profile_lib_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package commands

import (
"context"
"slices"

"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
Expand Down Expand Up @@ -76,27 +77,35 @@ func (s *arduinoCoreServerImpl) ProfileLibAdd(ctx context.Context, req *rpc.Prof
return nil, err
}

add := func(libReleaseToAdd *librariesindex.Release) {
add := func(libReleaseToAdd *librariesindex.Release, isDep bool) {
libRefToAdd := &sketch.ProfileLibraryReference{
Library: libReleaseToAdd.GetName(),
Version: libReleaseToAdd.GetVersion(),
Library: libReleaseToAdd.GetName(),
Version: libReleaseToAdd.GetVersion(),
IsDependency: isDep,
}
existingLibRef, _ := profile.GetLibrary(libReleaseToAdd.GetName())
if existingLibRef == nil {
profile.Libraries = append(profile.Libraries, libRefToAdd)
addedLibs = append(addedLibs, libRefToAdd)
return
}
// If the same version of the library has been already added to the profile, skip it
if existingLibRef.Version.Equal(libReleaseToAdd.GetVersion()) {
skippedLibs = append(skippedLibs, libRefToAdd)
return

// The library is already present in the profile.

// If the existing library was a dependency, and we are adding a non-dependency,
// update the flag to indicate that it's not a dependency anymore.
if !isDep && existingLibRef.IsDependency {
existingLibRef.IsDependency = false
}
// If the library has been already added to the profile, just update the version
if req.GetNoOverwrite() {

// If no-overwrite is specified, skip updating the library version.
// If the same version of the library has been already added to the profile, skip it.
if req.GetNoOverwrite() || existingLibRef.Version.Equal(libReleaseToAdd.GetVersion()) {
skippedLibs = append(skippedLibs, libRefToAdd)
return
}

// otherwise update the version of the library
existingLibRef.Version = libReleaseToAdd.GetVersion()
addedLibs = append(addedLibs, existingLibRef)
}
Expand All @@ -107,11 +116,14 @@ func (s *arduinoCoreServerImpl) ProfileLibAdd(ctx context.Context, req *rpc.Prof
if err != nil {
return nil, err
}
// sort to make the output order deterministic
slices.SortFunc(libWithDeps, librariesindex.ReleaseCompare)
for _, lib := range libWithDeps {
add(lib)
isDep := libRelease.GetName() != lib.GetName()
add(lib, isDep)
}
} else {
add(libRelease)
add(libRelease, false)
}
} else {
return nil, &cmderrors.InvalidArgumentError{Message: "library must be specified"}
Expand Down
71 changes: 67 additions & 4 deletions commands/service_profile_lib_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ package commands

import (
"context"
"slices"

"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
"github.com/arduino/arduino-cli/internal/arduino/sketch"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
paths "github.com/arduino/go-paths-helper"
Expand Down Expand Up @@ -47,19 +50,79 @@ func (s *arduinoCoreServerImpl) ProfileLibRemove(ctx context.Context, req *rpc.P
return nil, err
}

var removedLibraries []*rpc.ProfileLibraryReference
remove := func(libraryToRemove *sketch.ProfileLibraryReference) error {
removedLibrary, err := profile.RemoveLibrary(libraryToRemove)
if err != nil {
return &cmderrors.InvalidArgumentError{Cause: err}
}
removedLibraries = append(removedLibraries, removedLibrary.ToRpc())
return nil
}

libToRemove, err := sketch.FromRpcProfileLibraryReference(req.GetLibrary())
if err != nil {
return nil, &cmderrors.InvalidArgumentError{Message: "invalid library reference", Cause: err}
}
removedLib, err := profile.RemoveLibrary(libToRemove)
if err != nil {
return nil, &cmderrors.InvalidArgumentError{Cause: err}
if err := remove(libToRemove); err != nil {
return nil, err
}

// Get the dependencies of the libraries to see if any of them could be removed as well
if req.GetRemoveDependencies() {
if req.GetLibrary().GetIndexLibrary() == nil {
// No dependencies to remove
return nil, &cmderrors.InvalidArgumentError{Message: "automatic dependency removal is supported only for IndexLibraries"}
}
// Obtain the library index from the manager
li, err := instances.GetLibrariesIndex(req.GetInstance())
if err != nil {
return nil, err
}

// Get all the dependencies required by the profile excluding the removed library
requiredDeps := map[string]bool{}
requiredDeps[libToRemove.String()] = true
for _, profLib := range profile.Libraries {
if profLib.IsDependency {
continue
}
if profLib.Library == "" {
continue
}
deps, err := libraryResolveDependencies(li, profLib.Library, profLib.Version.String(), nil)
if err != nil {
return nil, &cmderrors.InvalidArgumentError{Cause: err, Message: "cannot resolve dependencies for installed libraries"}
}
for _, dep := range deps {
requiredDeps[dep.String()] = true
}
}

depsOfLibToRemove, err := libraryResolveDependencies(li, libToRemove.Library, libToRemove.Version.String(), nil)
if err != nil {
return nil, err
}
// sort to make the output order deterministic
slices.SortFunc(depsOfLibToRemove, librariesindex.ReleaseCompare)
// deps contains the main library as well, so we skip it when removing dependencies
for _, depToRemove := range depsOfLibToRemove {
if requiredDeps[depToRemove.String()] {
continue
}
if err := remove(&sketch.ProfileLibraryReference{Library: depToRemove.Library.Name, Version: depToRemove.Version}); err != nil {
return nil, err
}
}
}

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

return &rpc.ProfileLibRemoveResponse{Library: removedLib.ToRpc(), ProfileName: profileName}, nil
return &rpc.ProfileLibRemoveResponse{
RemovedLibraries: removedLibraries,
ProfileName: profileName,
}, nil
}
9 changes: 9 additions & 0 deletions internal/arduino/libraries/librariesindex/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package librariesindex

import (
"sort"
"strings"

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

// ReleaseCompare compares two library releases by name, or by version if the names are equal.
func ReleaseCompare(r1, r2 *Release) int {
if cmp := strings.Compare(r1.GetName(), r2.GetName()); cmp != 0 {
return cmp
}
return r1.GetVersion().CompareTo(r2.GetVersion())
}

// Dependency is a library dependency
type Dependency struct {
Name string
Expand Down
66 changes: 66 additions & 0 deletions internal/integrationtest/arduino-cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,3 +741,69 @@ func (inst *ArduinoCLIInstance) NewSketch(ctx context.Context, sketchName, sketc
logCallf(">>> NewSketch(%+v)\n", req)
return inst.cli.daemonClient.NewSketch(ctx, req)
}

func (inst *ArduinoCLIInstance) ProfileCreate(
ctx context.Context,
profileName, sketchPath string,
fqbn string,
defaultProfile bool,
) (*commands.ProfileCreateResponse, error) {
req := &commands.ProfileCreateRequest{
Instance: inst.instance,
SketchPath: sketchPath,
ProfileName: profileName,
Fqbn: fqbn,
DefaultProfile: defaultProfile,
}
logCallf(">>> ProfileCreate(%+v)\n", req)
resp, err := inst.cli.daemonClient.ProfileCreate(ctx, req)
return resp, err
}

func (inst *ArduinoCLIInstance) ProfileLibList(ctx context.Context, sketchPath, profileName string) (*commands.ProfileLibListResponse, error) {
req := &commands.ProfileLibListRequest{
SketchPath: sketchPath,
ProfileName: profileName,
}
logCallf(">>> ProfileListLib(%+v)\n", req)
resp, err := inst.cli.daemonClient.ProfileLibList(ctx, req)
return resp, err
}

func (inst *ArduinoCLIInstance) ProfileLibAdd(
ctx context.Context,
sketchPath, profileName string,
library *commands.ProfileLibraryReference,
addDependencies, noOverwrite bool,
) (*commands.ProfileLibAddResponse, error) {
req := &commands.ProfileLibAddRequest{
Instance: inst.instance,
SketchPath: sketchPath,
ProfileName: profileName,
Library: library,
AddDependencies: &addDependencies,
NoOverwrite: &noOverwrite,
}
logCallf(">>> ProfileLibAdd(%+v)\n", req)
resp, err := inst.cli.daemonClient.ProfileLibAdd(ctx, req)
return resp, err
}

func (inst *ArduinoCLIInstance) ProfileLibRemove(
ctx context.Context,
sketchPath,
profileName string,
library *commands.ProfileLibraryReference,
removeDependencies bool,
) (*commands.ProfileLibRemoveResponse, error) {
req := &commands.ProfileLibRemoveRequest{
Instance: inst.instance,
SketchPath: sketchPath,
ProfileName: profileName,
Library: library,
RemoveDependencies: &removeDependencies,
}
logCallf(">>> ProfileLibRemove(%+v)\n", req)
resp, err := inst.cli.daemonClient.ProfileLibRemove(ctx, req)
return resp, err
}
Loading