Skip to content

[draft] [breaking] Added support field in library.properties #2155

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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
37 changes: 37 additions & 0 deletions arduino/cores/fqbn.go
Original file line number Diff line number Diff line change
@@ -110,3 +110,40 @@ func (fqbn *FQBN) Match(target *FQBN) bool {
func (fqbn *FQBN) StringWithoutConfig() string {
return fqbn.Package + ":" + fqbn.PlatformArch + ":" + fqbn.BoardID
}

// FQBNMatcher contains a pattern to match an FQBN
type FQBNMatcher struct {
Package string
PlatformArch string
BoardID string
}

// ParseFQBNMatcher parse a formula for an FQBN pattern and returns the corresponding
// FQBNMatcher. In the formula is allowed the glob char `*`. The formula must contains
// the triple `PACKAGE:ARCHITECTURE:BOARDID`, some exaples are:
// - `arduino:avr:uno`
// - `*:avr:*`
// - `arduino:avr:mega*`
func ParseFQBNMatcher(formula string) (*FQBNMatcher, error) {
parts := strings.Split(strings.TrimSpace(formula), ":")
if len(parts) < 3 || len(parts) > 4 {
return nil, fmt.Errorf("invalid formula: %s", formula)
}
return &FQBNMatcher{
Package: parts[0],
PlatformArch: parts[1],
BoardID: parts[2],
}, nil
}

// Match checks if this FQBNMatcher matches the given fqbn
func (m *FQBNMatcher) Match(fqbn *FQBN) bool {
// TODO: allow in-fix syntax like `*name`
return (m.Package == fqbn.Package || m.Package == "*") &&
(m.PlatformArch == fqbn.PlatformArch || m.PlatformArch == "*") &&
(m.BoardID == fqbn.BoardID || m.BoardID == "*")
}

func (m *FQBNMatcher) String() string {
return m.Package + "." + m.PlatformArch + ":" + m.BoardID
}
20 changes: 16 additions & 4 deletions arduino/libraries/libraries.go
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ type Library struct {
Website string
Category string
Architectures []string
SupportedFQBN []*cores.FQBNMatcher

Types []string `json:"types,omitempty"`

@@ -182,10 +183,21 @@ func (library *Library) IsArchitectureIndependent() bool {
}

// IsCompatibleWith returns true if the library declares compatibility with
// the given architecture. If this function returns false, the library may still
// be compatible with the given architecture, but it's not explicitly declared.
func (library *Library) IsCompatibleWith(arch string) bool {
return library.IsArchitectureIndependent() || library.IsOptimizedForArchitecture(arch)
// the given FQBN. If this function returns false, the library may still
// be compatible with the given FQBN, but it's not explicitly declared.
func (library *Library) IsCompatibleWith(fqbn *cores.FQBN) bool {
// If the library does not specify compatibility use "architecture" field
if len(library.SupportedFQBN) == 0 {
return library.IsArchitectureIndependent() || library.IsOptimizedForArchitecture(fqbn.PlatformArch)
}

// otherwise check if the given FQBN is supported
for _, supported := range library.SupportedFQBN {
if supported.Match(fqbn) {
return true
}
}
return false
}

// SourceDir represents a source dir of a library
19 changes: 13 additions & 6 deletions arduino/libraries/librariesresolver/cpp.go
Original file line number Diff line number Diff line change
@@ -118,12 +118,12 @@ func (resolver *Cpp) AlternativesFor(header string) libraries.List {

// ResolveFor finds the most suitable library for the specified combination of
// header and architecture. If no libraries provides the requested header, nil is returned
func (resolver *Cpp) ResolveFor(header, architecture string) *libraries.Library {
logrus.Infof("Resolving include %s for arch %s", header, architecture)
func (resolver *Cpp) ResolveFor(header string, fqbn *cores.FQBN) *libraries.Library {
logrus.Infof("Resolving include %s for %s", header, fqbn)
var found libraries.List
var foundPriority int
for _, lib := range resolver.headers[header] {
libPriority := ComputePriority(lib, header, architecture)
libPriority := ComputePriority(lib, header, fqbn)
msg := " discarded"
if found == nil || foundPriority < libPriority {
found = libraries.List{}
@@ -167,16 +167,23 @@ func simplify(name string) string {
// ComputePriority returns an integer value representing the priority of the library
// for the specified header and architecture. The higher the value, the higher the
// priority.
func ComputePriority(lib *libraries.Library, header, arch string) int {
func ComputePriority(lib *libraries.Library, header string, fqbn *cores.FQBN) int {
header = strings.TrimSuffix(header, filepath.Ext(header))
header = simplify(header)
name := simplify(lib.Name)
dirName := simplify(lib.DirName)

priority := 0

// Bonus for core-optimized libraries
if lib.IsOptimizedForArchitecture(arch) {
if lib.IsCompatibleWith(fqbn) {
// Bonus for board-optimized libraries

// give a bonus for libraries that declares specific compatibliity with a board.
// (it is more important than Location but less important than Name)
priority += 1020
} else if lib.IsOptimizedForArchitecture(fqbn.PlatformArch) {
// Bonus for core-optimized libraries

// give a slightly better bonus for libraries that have specific optimization
// (it is more important than Location but less important than Name)
priority += 1010
32 changes: 19 additions & 13 deletions arduino/libraries/librariesresolver/cpp_test.go
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ package librariesresolver
import (
"testing"

"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/libraries"
"github.com/stretchr/testify/require"
)
@@ -31,12 +32,17 @@ var l6 = &libraries.Library{Name: "Calculus Unified Lib", Location: libraries.Us
var l7 = &libraries.Library{Name: "AnotherLib", Location: libraries.User}
var bundleServo = &libraries.Library{Name: "Servo", Location: libraries.IDEBuiltIn, Architectures: []string{"avr", "sam", "samd"}}

func mustParseFQBN(fqbn string) *cores.FQBN {
res, _ := cores.ParseFQBN(fqbn)
return res
}

func runResolver(include string, arch string, libs ...*libraries.Library) *libraries.Library {
libraryList := libraries.List{}
libraryList.Add(libs...)
resolver := NewCppResolver()
resolver.headers[include] = libraryList
return resolver.ResolveFor(include, arch)
return resolver.ResolveFor(include, mustParseFQBN("x:"+arch+":y"))
}

func TestArchitecturePriority(t *testing.T) {
@@ -96,19 +102,19 @@ func TestClosestMatchWithTotallyDifferentNames(t *testing.T) {
libraryList.Add(l7)
resolver := NewCppResolver()
resolver.headers["XYZ.h"] = libraryList
res := resolver.ResolveFor("XYZ.h", "xyz")
res := resolver.ResolveFor("XYZ.h", mustParseFQBN("arduino:xyz:uno"))
require.NotNil(t, res)
require.Equal(t, l7, res, "selected library")
}

func TestCppHeaderPriority(t *testing.T) {
r1 := ComputePriority(l1, "calculus_lib.h", "avr")
r2 := ComputePriority(l2, "calculus_lib.h", "avr")
r3 := ComputePriority(l3, "calculus_lib.h", "avr")
r4 := ComputePriority(l4, "calculus_lib.h", "avr")
r5 := ComputePriority(l5, "calculus_lib.h", "avr")
r6 := ComputePriority(l6, "calculus_lib.h", "avr")
r7 := ComputePriority(l7, "calculus_lib.h", "avr")
r1 := ComputePriority(l1, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r2 := ComputePriority(l2, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r3 := ComputePriority(l3, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r4 := ComputePriority(l4, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r5 := ComputePriority(l5, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r6 := ComputePriority(l6, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
r7 := ComputePriority(l7, "calculus_lib.h", mustParseFQBN("arduino:avr:uno"))
require.True(t, r1 > r2)
require.True(t, r2 > r3)
require.True(t, r3 > r4)
@@ -122,7 +128,7 @@ func TestCppHeaderResolverWithNilResult(t *testing.T) {
libraryList := libraries.List{}
libraryList.Add(l1)
resolver.headers["aaa.h"] = libraryList
require.Nil(t, resolver.ResolveFor("bbb.h", "avr"))
require.Nil(t, resolver.ResolveFor("bbb.h", mustParseFQBN("arduino:avr:uno")))
}

func TestCppHeaderResolver(t *testing.T) {
@@ -133,7 +139,7 @@ func TestCppHeaderResolver(t *testing.T) {
librarylist.Add(lib)
}
resolver.headers[header] = librarylist
return resolver.ResolveFor(header, "avr").Name
return resolver.ResolveFor(header, mustParseFQBN("arduino:avr:uno")).Name
}
require.Equal(t, "Calculus Lib", resolve("calculus_lib.h", l1, l2, l3, l4, l5, l6, l7))
require.Equal(t, "Calculus Lib-master", resolve("calculus_lib.h", l2, l3, l4, l5, l6, l7))
@@ -150,11 +156,11 @@ func TestCppHeaderResolverWithLibrariesInStrangeDirectoryNames(t *testing.T) {
librarylist.Add(&libraries.Library{DirName: "onewire_2_3_4", Name: "OneWire", Architectures: []string{"*"}})
librarylist.Add(&libraries.Library{DirName: "onewireng_2_3_4", Name: "OneWireNg", Architectures: []string{"avr"}})
resolver.headers["OneWire.h"] = librarylist
require.Equal(t, "onewire_2_3_4", resolver.ResolveFor("OneWire.h", "avr").DirName)
require.Equal(t, "onewire_2_3_4", resolver.ResolveFor("OneWire.h", mustParseFQBN("arduino:avr:uno")).DirName)

librarylist2 := libraries.List{}
librarylist2.Add(&libraries.Library{DirName: "OneWire", Name: "OneWire", Architectures: []string{"*"}})
librarylist2.Add(&libraries.Library{DirName: "onewire_2_3_4", Name: "OneWire", Architectures: []string{"avr"}})
resolver.headers["OneWire.h"] = librarylist2
require.Equal(t, "OneWire", resolver.ResolveFor("OneWire.h", "avr").DirName)
require.Equal(t, "OneWire", resolver.ResolveFor("OneWire.h", mustParseFQBN("arduino:avr:uno")).DirName)
}
11 changes: 10 additions & 1 deletion arduino/libraries/loader.go
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import (
"fmt"
"strings"

"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/globals"
"github.com/arduino/arduino-cli/arduino/sketch"
"github.com/arduino/go-paths-helper"
@@ -82,7 +83,15 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
libProperties.Set("architectures", "*")
}
library.Architectures = commaSeparatedToList(libProperties.Get("architectures"))

if supported := libProperties.Get("supported"); supported != "" {
for _, formula := range strings.Split(supported, ",") {
constraint, err := cores.ParseFQBNMatcher(formula)
if err != nil {
return nil, errors.New(tr("invalid value '%[1]s': %[2]s", formula, err))
}
library.SupportedFQBN = append(library.SupportedFQBN, constraint)
}
}
libProperties.Set("category", strings.TrimSpace(libProperties.Get("category")))
if !ValidCategories[libProperties.Get("category")] {
libProperties.Set("category", "Uncategorized")
6 changes: 3 additions & 3 deletions commands/lib/list.go
Original file line number Diff line number Diff line change
@@ -70,8 +70,8 @@ func LibraryList(ctx context.Context, req *rpc.LibraryListRequest) (*rpc.Library
}
}
if latest, has := filteredRes[lib.Library.Name]; has {
latestPriority := librariesresolver.ComputePriority(latest.Library, "", fqbn.PlatformArch)
libPriority := librariesresolver.ComputePriority(lib.Library, "", fqbn.PlatformArch)
latestPriority := librariesresolver.ComputePriority(latest.Library, "", fqbn)
libPriority := librariesresolver.ComputePriority(lib.Library, "", fqbn)
if latestPriority >= libPriority {
// Pick library with the best priority
continue
@@ -80,7 +80,7 @@ func LibraryList(ctx context.Context, req *rpc.LibraryListRequest) (*rpc.Library

// Check if library is compatible with board specified by FBQN
lib.Library.CompatibleWith = map[string]bool{
fqbnString: lib.Library.IsCompatibleWith(fqbn.PlatformArch),
fqbnString: lib.Library.IsCompatibleWith(fqbn),
}

filteredRes[lib.Library.Name] = lib
7 changes: 1 addition & 6 deletions internal/cli/lib/list.go
Original file line number Diff line number Diff line change
@@ -67,12 +67,7 @@ func List(instance *rpc.Instance, args []string, all bool, updatable bool) {
}

// GetList returns a list of installed libraries.
func GetList(
instance *rpc.Instance,
args []string,
all bool,
updatable bool,
) []*rpc.InstalledLibrary {
func GetList(instance *rpc.Instance, args []string, all bool, updatable bool) []*rpc.InstalledLibrary {
name := ""
if len(args) > 0 {
name = args[0]
4 changes: 2 additions & 2 deletions legacy/builder/resolve_library.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ func ResolveLibrary(ctx *types.Context, header string) *libraries.Library {
ctx.Info(fmt.Sprintf(" -> %s: %s", tr("candidates"), candidates))
}

if candidates == nil || len(candidates) == 0 {
if len(candidates) == 0 {
return nil
}

@@ -44,7 +44,7 @@ func ResolveLibrary(ctx *types.Context, header string) *libraries.Library {
}
}

selected := resolver.ResolveFor(header, ctx.TargetPlatform.Platform.Architecture)
selected := resolver.ResolveFor(header, ctx.FQBN)
if alreadyImported := importedLibraries.FindByName(selected.Name); alreadyImported != nil {
// Certain libraries might have the same name but be different.
// This usually happens when the user includes two or more custom libraries that have