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
Added support for 'dependency:' field in profiles libraries
  • Loading branch information
cmaglie committed Nov 25, 2025
commit 5ac03d82a35011f53f7fbcd1b512cea8f925ed47
54 changes: 39 additions & 15 deletions internal/arduino/sketch/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,15 +348,16 @@ func (p *ProfilePlatformReference) UnmarshalYAML(unmarshal func(interface{}) err

// ProfileLibraryReference is a reference to a library
type ProfileLibraryReference struct {
Library string
InstallDir *paths.Path
Version *semver.Version
Library string
Version *semver.Version
IsDependency bool
InstallDir *paths.Path
}

// UnmarshalYAML decodes a ProfileLibraryReference from YAML source.
func (l *ProfileLibraryReference) UnmarshalYAML(unmarshal func(interface{}) error) error {
var dataMap map[string]any
var data string
var libReference string
if err := unmarshal(&dataMap); err == nil {
if installDir, ok := dataMap["dir"]; ok {
if installDir, ok := installDir.(string); !ok {
Expand All @@ -366,15 +367,24 @@ func (l *ProfileLibraryReference) UnmarshalYAML(unmarshal func(interface{}) erro
l.Library = l.InstallDir.Base()
return nil
}
} else if depLib, ok := dataMap["dependency"]; ok {
if libReference, ok = depLib.(string); !ok {
return fmt.Errorf("%s: %s", i18n.Tr("invalid library reference"), dataMap)
}
l.IsDependency = true
// Fallback
} else {
return fmt.Errorf("%s: %s", i18n.Tr("invalid library reference"), dataMap)
}
} else if err := unmarshal(&data); err != nil {
} else if err := unmarshal(&libReference); err != nil {
return err
} else {
l.IsDependency = false
}

if libName, libVersion, ok := parseNameAndVersion(data); !ok {
return fmt.Errorf("%s: %s", i18n.Tr("invalid library reference"), data)
// Parse reference in the format "LIBRARY_NAME (VERSION)"
if libName, libVersion, ok := parseNameAndVersion(libReference); !ok {
return fmt.Errorf("%s: %s", i18n.Tr("invalid library reference"), libReference)
} else if v, err := semver.Parse(libVersion); err != nil {
return fmt.Errorf("%s: %w", i18n.Tr("invalid version"), err)
} else {
Expand All @@ -389,17 +399,25 @@ func (l *ProfileLibraryReference) AsYaml() string {
if l.InstallDir != nil {
return fmt.Sprintf(" - dir: %s\n", l.InstallDir)
}
return fmt.Sprintf(" - %s (%s)\n", l.Library, l.Version)
dep := ""
if l.IsDependency {
dep = "dependency: "
}
return fmt.Sprintf(" - %s%s (%s)\n", dep, l.Library, l.Version)
}

func (l *ProfileLibraryReference) String() string {
if l.InstallDir != nil {
return fmt.Sprintf("%s@dir:%s", l.Library, l.InstallDir)
return "@dir:" + l.InstallDir.String()
}
dep := ""
if l.IsDependency {
dep = " (dep)"
}
if l.Version == nil {
return l.Library
return l.Library + dep
}
return fmt.Sprintf("%s@%s", l.Library, l.Version)
return fmt.Sprintf("%s@%s%s", l.Library, l.Version, dep)
}

// Match checks if this library reference matches another one.
Expand Down Expand Up @@ -434,8 +452,9 @@ func (l *ProfileLibraryReference) ToRpc() *rpc.ProfileLibraryReference {
return &rpc.ProfileLibraryReference{
Library: &rpc.ProfileLibraryReference_IndexLibrary_{
IndexLibrary: &rpc.ProfileLibraryReference_IndexLibrary{
Name: l.Library,
Version: l.Version.String(),
Name: l.Library,
Version: l.Version.String(),
IsDependency: l.IsDependency,
},
},
}
Expand All @@ -459,7 +478,11 @@ func FromRpcProfileLibraryReference(l *rpc.ProfileLibraryReference) (*ProfileLib
}
version = v
}
return &ProfileLibraryReference{Library: indexLib.GetName(), Version: version}, nil
return &ProfileLibraryReference{
Library: indexLib.GetName(),
Version: version,
IsDependency: indexLib.GetIsDependency(),
}, nil
}
return nil, &cmderrors.InvalidArgumentError{Message: "library not specified"}
}
Expand All @@ -468,7 +491,8 @@ func FromRpcProfileLibraryReference(l *rpc.ProfileLibraryReference) (*ProfileLib
func (l *ProfileLibraryReference) InternalUniqueIdentifier() string {
f.Assert(l.InstallDir == nil,
"InternalUniqueIdentifier should not be called for library references with an install directory")
id := l.String()

id := l.Library + "@" + l.Version.String()
h := sha256.Sum256([]byte(id))
res := fmt.Sprintf("%s_%s", id, hex.EncodeToString(h[:])[:16])
return utils.SanitizeName(res)
Expand Down
21 changes: 21 additions & 0 deletions internal/arduino/sketch/profiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ func TestProjectFileLoading(t *testing.T) {
require.Error(t, err)
}
}

func TestProjectFileLibraries(t *testing.T) {
sketchProj := paths.New("testdata", "profiles", "profile_with_libraries.yml")
proj, err := LoadProjectFile(sketchProj)
require.NoError(t, err)
require.Len(t, proj.Profiles, 1)
prof := proj.Profiles[0]
require.Len(t, prof.Libraries, 4)
require.Equal(t, "FlashStorage@1.2.3", prof.Libraries[0].String())
require.Equal(t, "@dir:/path/to/system/lib", prof.Libraries[1].String())
require.Equal(t, "@dir:path/to/sketch/lib", prof.Libraries[2].String())
require.Equal(t, "DependencyLib@2.3.4 (dep)", prof.Libraries[3].String())
require.Equal(t, "FlashStorage_1.2.3_e525d7c96b27788f", prof.Libraries[0].InternalUniqueIdentifier())
require.Panics(t, func() { prof.Libraries[1].InternalUniqueIdentifier() })
require.Panics(t, func() { prof.Libraries[2].InternalUniqueIdentifier() })
require.Equal(t, "DependencyLib_2.3.4_ecde631facb47ae5", prof.Libraries[3].InternalUniqueIdentifier())

orig, err := sketchProj.ReadFile()
require.NoError(t, err)
require.Equal(t, string(orig), proj.AsYaml())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
profiles:
giga:
fqbn: arduino:mbed_giga:giga
platforms:
- platform: arduino:mbed_giga (4.3.1)
libraries:
- FlashStorage (1.2.3)
- dir: /path/to/system/lib
- dir: path/to/sketch/lib
- dependency: DependencyLib (2.3.4)

default_profile: giga_any
10 changes: 6 additions & 4 deletions internal/cli/feedback/result/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1137,13 +1137,15 @@ func NewProfileLibraryReference_LocalLibraryResult(resp *rpc.ProfileLibraryRefer
}

type ProfileLibraryReference_IndexLibraryResult struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
IsDependency bool `json:"is_dependency,omitempty"`
}

func NewProfileLibraryReference_IndexLibraryResult(resp *rpc.ProfileLibraryReference_IndexLibrary) *ProfileLibraryReference_IndexLibraryResult {
return &ProfileLibraryReference_IndexLibraryResult{
Name: resp.GetName(),
Version: resp.GetVersion(),
Name: resp.GetName(),
Version: resp.GetVersion(),
IsDependency: resp.GetIsDependency(),
}
}
33 changes: 22 additions & 11 deletions rpc/cc/arduino/cli/commands/v1/common.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rpc/cc/arduino/cli/commands/v1/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ message ProfileLibraryReference {
string name = 1;
// Version of the library if taken from the Library Index.
string version = 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clients would love to use a dedicated message to add the latest version of the library (without listing the available versions), and maybe add the currently installed version that errors if the library is not installed.

// If true, this library is marked as a dependency.
bool is_dependency = 3;
}
message LocalLibrary {
// Absolute path to the library.
Expand Down