Skip to content

Commit 86fcc6d

Browse files
libraries: expose Dependencies field
1 parent c11b9dd commit 86fcc6d

File tree

10 files changed

+292
-118
lines changed

10 files changed

+292
-118
lines changed

Diff for: commands/service_library_search.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"strings"
2222

2323
"github.com/arduino/arduino-cli/commands/internal/instances"
24+
"github.com/arduino/arduino-cli/internal/arduino/libraries"
2425
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
2526
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2627
semver "go.bug.st/relaxed-semver"
@@ -116,7 +117,7 @@ func getLibraryParameters(rel *librariesindex.Release) *rpc.LibraryRelease {
116117
}
117118
}
118119

119-
func getLibraryDependenciesParameter(deps []*librariesindex.Dependency) []*rpc.LibraryDependency {
120+
func getLibraryDependenciesParameter(deps []*libraries.Dependency) []*rpc.LibraryDependency {
120121
res := []*rpc.LibraryDependency{}
121122
for _, dep := range deps {
122123
res = append(res, &rpc.LibraryDependency{

Diff for: internal/arduino/libraries/libraries.go

+25
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2626
paths "github.com/arduino/go-paths-helper"
2727
properties "github.com/arduino/go-properties-orderedmap"
28+
"go.bug.st/f"
2829
semver "go.bug.st/relaxed-semver"
2930
)
3031

@@ -78,6 +79,7 @@ type Library struct {
7879
License string
7980
Properties *properties.Map
8081
Examples paths.PathList
82+
Dependencies []*Dependency
8183
declaredHeaders []string
8284
sourceHeaders []string
8385
CompatibleWith map[string]bool
@@ -142,6 +144,13 @@ func (library *Library) ToRPCLibrary() (*rpc.Library, error) {
142144
Examples: library.Examples.AsStrings(),
143145
ProvidesIncludes: headers,
144146
CompatibleWith: library.CompatibleWith,
147+
Dependencies: f.Map(library.Dependencies, func(d *Dependency) *rpc.LibraryDependency {
148+
dep := &rpc.LibraryDependency{Name: d.GetName()}
149+
if d.GetConstraint() != nil {
150+
dep.VersionConstraint = d.GetConstraint().String()
151+
}
152+
return dep
153+
}),
145154
}, nil
146155
}
147156

@@ -239,3 +248,19 @@ func (library *Library) SourceHeaders() ([]string, error) {
239248
}
240249
return library.sourceHeaders, nil
241250
}
251+
252+
// Dependency is a library dependency
253+
type Dependency struct {
254+
Name string
255+
VersionConstraint semver.Constraint
256+
}
257+
258+
// GetName returns the name of the dependency
259+
func (r *Dependency) GetName() string {
260+
return r.Name
261+
}
262+
263+
// GetConstraint returns the version Constraint of the dependecy
264+
func (r *Dependency) GetConstraint() semver.Constraint {
265+
return r.VersionConstraint
266+
}

Diff for: internal/arduino/libraries/librariesindex/index.go

+3-19
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type Library struct {
4545
type Release struct {
4646
Author string
4747
Version *semver.Version
48-
Dependencies []*Dependency
48+
Dependencies []*libraries.Dependency
4949
Maintainer string
5050
Sentence string
5151
Paragraph string
@@ -86,26 +86,10 @@ func (r *Release) GetVersion() *semver.Version {
8686
}
8787

8888
// GetDependencies returns the dependencies of this library.
89-
func (r *Release) GetDependencies() []*Dependency {
89+
func (r *Release) GetDependencies() []*libraries.Dependency {
9090
return r.Dependencies
9191
}
9292

93-
// Dependency is a library dependency
94-
type Dependency struct {
95-
Name string
96-
VersionConstraint semver.Constraint
97-
}
98-
99-
// GetName returns the name of the dependency
100-
func (r *Dependency) GetName() string {
101-
return r.Name
102-
}
103-
104-
// GetConstraint returns the version Constraint of the dependecy
105-
func (r *Dependency) GetConstraint() semver.Constraint {
106-
return r.VersionConstraint
107-
}
108-
10993
func (r *Release) String() string {
11094
return r.Library.Name + "@" + r.Version.String()
11195
}
@@ -155,7 +139,7 @@ func (idx *Index) FindLibraryUpdate(lib *libraries.Library) *Release {
155139
// An optional "override" releases may be passed if we want to exclude the same
156140
// libraries from the index (for example if we want to keep an installed library).
157141
func (idx *Index) ResolveDependencies(lib *Release, overrides []*Release) []*Release {
158-
resolver := semver.NewResolver[*Release, *Dependency]()
142+
resolver := semver.NewResolver[*Release, *libraries.Dependency]()
159143

160144
overridden := map[string]bool{}
161145
for _, override := range overrides {

Diff for: internal/arduino/libraries/librariesindex/json.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package librariesindex
1818
import (
1919
"errors"
2020

21+
"github.com/arduino/arduino-cli/internal/arduino/libraries"
2122
"github.com/arduino/arduino-cli/internal/arduino/resources"
2223
"github.com/arduino/arduino-cli/internal/i18n"
2324
"github.com/arduino/go-paths-helper"
@@ -124,8 +125,8 @@ func (indexLib *indexRelease) extractReleaseIn(library *Library) {
124125
}
125126
}
126127

127-
func (indexLib *indexRelease) extractDependencies() []*Dependency {
128-
res := []*Dependency{}
128+
func (indexLib *indexRelease) extractDependencies() []*libraries.Dependency {
129+
res := []*libraries.Dependency{}
129130
if len(indexLib.Dependencies) == 0 {
130131
return res
131132
}
@@ -135,13 +136,13 @@ func (indexLib *indexRelease) extractDependencies() []*Dependency {
135136
return res
136137
}
137138

138-
func (indexDep *indexDependency) extractDependency() *Dependency {
139+
func (indexDep *indexDependency) extractDependency() *libraries.Dependency {
139140
var constraint semver.Constraint
140141
if c, err := semver.ParseConstraint(indexDep.Version); err == nil {
141142
constraint = c
142143
}
143144
// FIXME: else { report invalid constraint }
144-
return &Dependency{
145+
return &libraries.Dependency{
145146
Name: indexDep.Name,
146147
VersionConstraint: constraint,
147148
}

Diff for: internal/arduino/libraries/loader.go

+38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package libraries
1818
import (
1919
"errors"
2020
"fmt"
21+
"regexp"
2122
"strings"
2223

2324
"github.com/arduino/arduino-cli/internal/arduino/globals"
@@ -130,6 +131,12 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
130131
library.LDflags = strings.TrimSpace(libProperties.Get("ldflags"))
131132
library.Properties = libProperties
132133
library.InDevelopment = libraryDir.Join(".development").Exist()
134+
135+
dependencies, err := extractDependenciesList(libProperties.Get("depends"))
136+
if err != nil {
137+
return nil, fmt.Errorf("%s: %w", i18n.Tr("scanning library dependencies"), err)
138+
}
139+
library.Dependencies = dependencies
133140
return library, nil
134141
}
135142

@@ -220,3 +227,34 @@ func filterExamplesDirs(dir *paths.Path) bool {
220227
}
221228
return false
222229
}
230+
231+
var libraryDependsRegex = regexp.MustCompile(`^([^()]+?) *(?: \((.*)\))?$`)
232+
233+
// extractDependenciesList extracts dependencies from the "depends" field of library.properties
234+
func extractDependenciesList(depends string) ([]*Dependency, error) {
235+
deps := []*Dependency{}
236+
depends = strings.TrimSpace(depends)
237+
if depends == "" {
238+
return deps, nil
239+
}
240+
for dep := range strings.SplitSeq(depends, ",") {
241+
dep = strings.TrimSpace(dep)
242+
if dep == "" {
243+
return nil, fmt.Errorf("invalid dep: %s", dep)
244+
}
245+
matches := libraryDependsRegex.FindAllStringSubmatch(dep, -1)
246+
if matches == nil {
247+
return nil, fmt.Errorf("invalid dep: %s", dep)
248+
}
249+
250+
depConstraint, err := semver.ParseConstraint(matches[0][2])
251+
if err != nil {
252+
return nil, fmt.Errorf("invalid dep constraint: %s", matches[0][2])
253+
}
254+
deps = append(deps, &Dependency{
255+
Name: matches[0][1],
256+
VersionConstraint: depConstraint,
257+
})
258+
}
259+
return deps, nil
260+
}

Diff for: internal/arduino/libraries/loader_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package libraries
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
)
23+
24+
func TestDependencyExtract(t *testing.T) {
25+
check := func(depDefinition string, name []string, ver []string) {
26+
dep, err := extractDependenciesList(depDefinition)
27+
require.NoError(t, err)
28+
require.NotNil(t, dep)
29+
require.Len(t, dep, len(name))
30+
for i := range name {
31+
require.Equal(t, name[i], dep[i].Name, depDefinition)
32+
require.Equal(t, ver[i], dep[i].VersionConstraint.String(), depDefinition)
33+
}
34+
}
35+
invalid := func(depends string) {
36+
dep, err := extractDependenciesList(depends)
37+
require.Nil(t, dep)
38+
require.Error(t, err)
39+
}
40+
check("ciao", []string{"ciao"}, []string{""})
41+
check("MyLib (>1.2.3)", []string{"MyLib"}, []string{">1.2.3"})
42+
check("MyLib (>=1.2.3)", []string{"MyLib"}, []string{">=1.2.3"})
43+
check("MyLib (<1.2.3)", []string{"MyLib"}, []string{"<1.2.3"})
44+
check("MyLib (<=1.2.3)", []string{"MyLib"}, []string{"<=1.2.3"})
45+
check("MyLib (!=1.2.3)", []string{"MyLib"}, []string{"!(=1.2.3)"})
46+
check("MyLib (>1.0.0 && <2.1.0)", []string{"MyLib"}, []string{"(>1.0.0 && <2.1.0)"})
47+
check("MyLib (<1.0.0 || >2.0.0)", []string{"MyLib"}, []string{"(<1.0.0 || >2.0.0)"})
48+
check("MyLib ((>0.1.0 && <2.0.0) || >2.1.0)", []string{"MyLib"}, []string{"((>0.1.0 && <2.0.0) || >2.1.0)"})
49+
check("MyLib ()", []string{"MyLib"}, []string{""})
50+
check("MyLib (>=1.2.3),AnotherLib, YetAnotherLib (=1.0.0)",
51+
[]string{"MyLib", "AnotherLib", "YetAnotherLib"},
52+
[]string{">=1.2.3", "", "=1.0.0"})
53+
invalid("MyLib,,AnotherLib")
54+
invalid("(MyLib)")
55+
invalid("MyLib(=1.2.3)")
56+
check("Arduino Uno WiFi Dev Ed Library, LoRa Node (^2.1.2)",
57+
[]string{"Arduino Uno WiFi Dev Ed Library", "LoRa Node"},
58+
[]string{"", "^2.1.2"})
59+
check("Arduino Uno WiFi Dev Ed Library , LoRa Node (^2.1.2)",
60+
[]string{"Arduino Uno WiFi Dev Ed Library", "LoRa Node"},
61+
[]string{"", "^2.1.2"})
62+
check("Arduino_OAuth, ArduinoHttpClient (<0.3.0), NonExistentLib",
63+
[]string{"Arduino_OAuth", "ArduinoHttpClient", "NonExistentLib"},
64+
[]string{"", "<0.3.0", ""})
65+
check("", []string{}, []string{})
66+
}

Diff for: internal/cli/feedback/result/rpc.go

+2
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ type Library struct {
234234
ProvidesIncludes []string `json:"provides_includes,omitempty"`
235235
CompatibleWith orderedmap.Map[string, bool] `json:"compatible_with,omitempty"`
236236
InDevelopment bool `json:"in_development,omitempty"`
237+
Dependencies []*LibraryDependency `json:"dependencies,omitempty"`
237238
}
238239

239240
func NewLibrary(l *rpc.Library) *Library {
@@ -279,6 +280,7 @@ func NewLibrary(l *rpc.Library) *Library {
279280
ProvidesIncludes: l.GetProvidesIncludes(),
280281
CompatibleWith: libraryCompatibleWithMap,
281282
InDevelopment: l.GetInDevelopment(),
283+
Dependencies: NewLibraryDependencies(l.GetDependencies()),
282284
}
283285
}
284286

Diff for: internal/integrationtest/lib/lib_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -1756,3 +1756,41 @@ func TestDependencyResolverNoOverwrite(t *testing.T) {
17561756
_, _, err = cli.Run("lib", "install", "[email protected]", "--no-overwrite")
17571757
require.NoError(t, err)
17581758
}
1759+
1760+
func TestLibListContainsDependenciesField(t *testing.T) {
1761+
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
1762+
defer env.CleanUp()
1763+
1764+
_, _, err := cli.Run("lib", "update-index")
1765+
require.NoError(t, err)
1766+
1767+
_, _, err = cli.Run("lib", "install", "[email protected]")
1768+
require.NoError(t, err)
1769+
stdOut, _, err := cli.Run("lib", "list", "--json")
1770+
require.NoError(t, err)
1771+
requirejson.Contains(t, stdOut, `{"installed_libraries": [ { "library": {
1772+
"name":"Arduino_ConnectionHandler",
1773+
"version": "0.6.6",
1774+
"dependencies": [
1775+
{"name": "Arduino_DebugUtils"},
1776+
{"name": "WiFi101"},
1777+
{"name": "WiFiNINA"},
1778+
{"name": "MKRGSM"},
1779+
{"name": "MKRNB"},
1780+
{"name": "MKRWAN"}
1781+
]
1782+
} } ]}`)
1783+
1784+
_, _, err = cli.Run("lib", "install", "[email protected]")
1785+
require.NoError(t, err)
1786+
stdOut, _, err = cli.Run("lib", "list", "--json")
1787+
require.NoError(t, err)
1788+
requirejson.Contains(t, stdOut, `{"installed_libraries": [ { "library": {
1789+
"name":"DebugLog",
1790+
"version": "0.8.4",
1791+
"dependencies": [
1792+
{"name": "ArxContainer", "version_constraint": ">=0.6.0"},
1793+
{"name": "ArxTypeTraits"}
1794+
]
1795+
} } ]}`)
1796+
}

0 commit comments

Comments
 (0)