Skip to content

Expose Dependencies field in Library struct #2891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion commands/service_library_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"

"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/libraries"
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
semver "go.bug.st/relaxed-semver"
Expand Down Expand Up @@ -116,7 +117,7 @@ func getLibraryParameters(rel *librariesindex.Release) *rpc.LibraryRelease {
}
}

func getLibraryDependenciesParameter(deps []*librariesindex.Dependency) []*rpc.LibraryDependency {
func getLibraryDependenciesParameter(deps []*libraries.Dependency) []*rpc.LibraryDependency {
res := []*rpc.LibraryDependency{}
for _, dep := range deps {
res = append(res, &rpc.LibraryDependency{
Expand Down
25 changes: 25 additions & 0 deletions internal/arduino/libraries/libraries.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
paths "github.com/arduino/go-paths-helper"
properties "github.com/arduino/go-properties-orderedmap"
"go.bug.st/f"
semver "go.bug.st/relaxed-semver"
)

Expand Down Expand Up @@ -78,6 +79,7 @@ type Library struct {
License string
Properties *properties.Map
Examples paths.PathList
Dependencies []*Dependency
declaredHeaders []string
sourceHeaders []string
CompatibleWith map[string]bool
Expand Down Expand Up @@ -142,6 +144,13 @@ func (library *Library) ToRPCLibrary() (*rpc.Library, error) {
Examples: library.Examples.AsStrings(),
ProvidesIncludes: headers,
CompatibleWith: library.CompatibleWith,
Dependencies: f.Map(library.Dependencies, func(d *Dependency) *rpc.LibraryDependency {
dep := &rpc.LibraryDependency{Name: d.GetName()}
if d.GetConstraint() != nil {
dep.VersionConstraint = d.GetConstraint().String()
}
return dep
}),
}, nil
}

Expand Down Expand Up @@ -239,3 +248,19 @@ func (library *Library) SourceHeaders() ([]string, error) {
}
return library.sourceHeaders, nil
}

// Dependency is a library dependency
type Dependency struct {
Name string
VersionConstraint semver.Constraint
}

// GetName returns the name of the dependency
func (r *Dependency) GetName() string {
return r.Name
}

// GetConstraint returns the version Constraint of the dependecy
func (r *Dependency) GetConstraint() semver.Constraint {
return r.VersionConstraint
}
22 changes: 3 additions & 19 deletions internal/arduino/libraries/librariesindex/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type Library struct {
type Release struct {
Author string
Version *semver.Version
Dependencies []*Dependency
Dependencies []*libraries.Dependency
Maintainer string
Sentence string
Paragraph string
Expand Down Expand Up @@ -86,26 +86,10 @@ func (r *Release) GetVersion() *semver.Version {
}

// GetDependencies returns the dependencies of this library.
func (r *Release) GetDependencies() []*Dependency {
func (r *Release) GetDependencies() []*libraries.Dependency {
return r.Dependencies
}

// Dependency is a library dependency
type Dependency struct {
Name string
VersionConstraint semver.Constraint
}

// GetName returns the name of the dependency
func (r *Dependency) GetName() string {
return r.Name
}

// GetConstraint returns the version Constraint of the dependecy
func (r *Dependency) GetConstraint() semver.Constraint {
return r.VersionConstraint
}

func (r *Release) String() string {
return r.Library.Name + "@" + r.Version.String()
}
Expand Down Expand Up @@ -155,7 +139,7 @@ func (idx *Index) FindLibraryUpdate(lib *libraries.Library) *Release {
// An optional "override" releases may be passed if we want to exclude the same
// libraries from the index (for example if we want to keep an installed library).
func (idx *Index) ResolveDependencies(lib *Release, overrides []*Release) []*Release {
resolver := semver.NewResolver[*Release, *Dependency]()
resolver := semver.NewResolver[*Release, *libraries.Dependency]()

overridden := map[string]bool{}
for _, override := range overrides {
Expand Down
9 changes: 5 additions & 4 deletions internal/arduino/libraries/librariesindex/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package librariesindex
import (
"errors"

"github.com/arduino/arduino-cli/internal/arduino/libraries"
"github.com/arduino/arduino-cli/internal/arduino/resources"
"github.com/arduino/arduino-cli/internal/i18n"
"github.com/arduino/go-paths-helper"
Expand Down Expand Up @@ -124,8 +125,8 @@ func (indexLib *indexRelease) extractReleaseIn(library *Library) {
}
}

func (indexLib *indexRelease) extractDependencies() []*Dependency {
res := []*Dependency{}
func (indexLib *indexRelease) extractDependencies() []*libraries.Dependency {
res := []*libraries.Dependency{}
if len(indexLib.Dependencies) == 0 {
return res
}
Expand All @@ -135,13 +136,13 @@ func (indexLib *indexRelease) extractDependencies() []*Dependency {
return res
}

func (indexDep *indexDependency) extractDependency() *Dependency {
func (indexDep *indexDependency) extractDependency() *libraries.Dependency {
var constraint semver.Constraint
if c, err := semver.ParseConstraint(indexDep.Version); err == nil {
constraint = c
}
// FIXME: else { report invalid constraint }
return &Dependency{
return &libraries.Dependency{
Name: indexDep.Name,
VersionConstraint: constraint,
}
Expand Down
38 changes: 38 additions & 0 deletions internal/arduino/libraries/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package libraries
import (
"errors"
"fmt"
"regexp"
"strings"

"github.com/arduino/arduino-cli/internal/arduino/globals"
Expand Down Expand Up @@ -130,6 +131,12 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
library.LDflags = strings.TrimSpace(libProperties.Get("ldflags"))
library.Properties = libProperties
library.InDevelopment = libraryDir.Join(".development").Exist()

dependencies, err := extractDependenciesList(libProperties.Get("depends"))
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.Tr("scanning library dependencies"), err)
}
library.Dependencies = dependencies
return library, nil
}

Expand Down Expand Up @@ -220,3 +227,34 @@ func filterExamplesDirs(dir *paths.Path) bool {
}
return false
}

var libraryDependsRegex = regexp.MustCompile(`^([^()]+?) *(?: \((.*)\))?$`)

// extractDependenciesList extracts dependencies from the "depends" field of library.properties
func extractDependenciesList(depends string) ([]*Dependency, error) {
deps := []*Dependency{}
depends = strings.TrimSpace(depends)
if depends == "" {
return deps, nil
}
for dep := range strings.SplitSeq(depends, ",") {
dep = strings.TrimSpace(dep)
if dep == "" {
return nil, fmt.Errorf("invalid dep: %s", dep)
}
matches := libraryDependsRegex.FindAllStringSubmatch(dep, -1)
if matches == nil {
return nil, fmt.Errorf("invalid dep: %s", dep)
}

depConstraint, err := semver.ParseConstraint(matches[0][2])
if err != nil {
return nil, fmt.Errorf("invalid dep constraint: %s", matches[0][2])
}
deps = append(deps, &Dependency{
Name: matches[0][1],
VersionConstraint: depConstraint,
})
}
return deps, nil
}
66 changes: 66 additions & 0 deletions internal/arduino/libraries/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This file is part of arduino-cli.
//
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package libraries

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestDependencyExtract(t *testing.T) {
check := func(depDefinition string, name []string, ver []string) {
dep, err := extractDependenciesList(depDefinition)
require.NoError(t, err)
require.NotNil(t, dep)
require.Len(t, dep, len(name))
for i := range name {
require.Equal(t, name[i], dep[i].Name, depDefinition)
require.Equal(t, ver[i], dep[i].VersionConstraint.String(), depDefinition)
}
}
invalid := func(depends string) {
dep, err := extractDependenciesList(depends)
require.Nil(t, dep)
require.Error(t, err)
}
check("ciao", []string{"ciao"}, []string{""})
check("MyLib (>1.2.3)", []string{"MyLib"}, []string{">1.2.3"})
check("MyLib (>=1.2.3)", []string{"MyLib"}, []string{">=1.2.3"})
check("MyLib (<1.2.3)", []string{"MyLib"}, []string{"<1.2.3"})
check("MyLib (<=1.2.3)", []string{"MyLib"}, []string{"<=1.2.3"})
check("MyLib (!=1.2.3)", []string{"MyLib"}, []string{"!(=1.2.3)"})
check("MyLib (>1.0.0 && <2.1.0)", []string{"MyLib"}, []string{"(>1.0.0 && <2.1.0)"})
check("MyLib (<1.0.0 || >2.0.0)", []string{"MyLib"}, []string{"(<1.0.0 || >2.0.0)"})
check("MyLib ((>0.1.0 && <2.0.0) || >2.1.0)", []string{"MyLib"}, []string{"((>0.1.0 && <2.0.0) || >2.1.0)"})
check("MyLib ()", []string{"MyLib"}, []string{""})
check("MyLib (>=1.2.3),AnotherLib, YetAnotherLib (=1.0.0)",
[]string{"MyLib", "AnotherLib", "YetAnotherLib"},
[]string{">=1.2.3", "", "=1.0.0"})
invalid("MyLib,,AnotherLib")
invalid("(MyLib)")
invalid("MyLib(=1.2.3)")
check("Arduino Uno WiFi Dev Ed Library, LoRa Node (^2.1.2)",
[]string{"Arduino Uno WiFi Dev Ed Library", "LoRa Node"},
[]string{"", "^2.1.2"})
check("Arduino Uno WiFi Dev Ed Library , LoRa Node (^2.1.2)",
[]string{"Arduino Uno WiFi Dev Ed Library", "LoRa Node"},
[]string{"", "^2.1.2"})
check("Arduino_OAuth, ArduinoHttpClient (<0.3.0), NonExistentLib",
[]string{"Arduino_OAuth", "ArduinoHttpClient", "NonExistentLib"},
[]string{"", "<0.3.0", ""})
check("", []string{}, []string{})
}
2 changes: 2 additions & 0 deletions internal/cli/feedback/result/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ type Library struct {
ProvidesIncludes []string `json:"provides_includes,omitempty"`
CompatibleWith orderedmap.Map[string, bool] `json:"compatible_with,omitempty"`
InDevelopment bool `json:"in_development,omitempty"`
Dependencies []*LibraryDependency `json:"dependencies,omitempty"`
}

func NewLibrary(l *rpc.Library) *Library {
Expand Down Expand Up @@ -279,6 +280,7 @@ func NewLibrary(l *rpc.Library) *Library {
ProvidesIncludes: l.GetProvidesIncludes(),
CompatibleWith: libraryCompatibleWithMap,
InDevelopment: l.GetInDevelopment(),
Dependencies: NewLibraryDependencies(l.GetDependencies()),
}
}

Expand Down
38 changes: 38 additions & 0 deletions internal/integrationtest/lib/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1756,3 +1756,41 @@ func TestDependencyResolverNoOverwrite(t *testing.T) {
_, _, err = cli.Run("lib", "install", "[email protected]", "--no-overwrite")
require.NoError(t, err)
}

func TestLibListContainsDependenciesField(t *testing.T) {
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()

_, _, err := cli.Run("lib", "update-index")
require.NoError(t, err)

_, _, err = cli.Run("lib", "install", "[email protected]")
require.NoError(t, err)
stdOut, _, err := cli.Run("lib", "list", "--json")
require.NoError(t, err)
requirejson.Contains(t, stdOut, `{"installed_libraries": [ { "library": {
"name":"Arduino_ConnectionHandler",
"version": "0.6.6",
"dependencies": [
{"name": "Arduino_DebugUtils"},
{"name": "WiFi101"},
{"name": "WiFiNINA"},
{"name": "MKRGSM"},
{"name": "MKRNB"},
{"name": "MKRWAN"}
]
} } ]}`)

_, _, err = cli.Run("lib", "install", "[email protected]")
require.NoError(t, err)
stdOut, _, err = cli.Run("lib", "list", "--json")
require.NoError(t, err)
requirejson.Contains(t, stdOut, `{"installed_libraries": [ { "library": {
"name":"DebugLog",
"version": "0.8.4",
"dependencies": [
{"name": "ArxContainer", "version_constraint": ">=0.6.0"},
{"name": "ArxTypeTraits"}
]
} } ]}`)
}
Loading