Skip to content

Commit

Permalink
Implemented pluggable discovery board matching (#1330)
Browse files Browse the repository at this point in the history
* Implemented pluggable discovery board matching

Added also a compatibility layer for existing platforms.

* Fixed docs and linter warnings

* Better comments

* Added tests and fixed a bug
  • Loading branch information
cmaglie committed Jun 21, 2021
1 parent 2173c12 commit b780f9c
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 53 deletions.
43 changes: 43 additions & 0 deletions arduino/cores/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,46 @@ func (b *Board) GeneratePropertiesForConfiguration(config string) (*properties.M
}
return b.GetBuildProperties(fqbn.Configs)
}

// IsBoardMatchingIDProperties returns true if the board match the given
// identification properties
func (b *Board) IsBoardMatchingIDProperties(query *properties.Map) bool {
portIDPropsSet := b.Properties.SubTree("upload_port")
if portIDPropsSet.Size() == 0 {
return false
}

// check checks if the given set of properties p match the "query"
check := func(p *properties.Map) bool {
for k, v := range p.AsMap() {
if !strings.EqualFold(query.Get(k), v) {
return false
}
}
return true
}

// First check the identification properties with sub index "upload_port.N.xxx"
idx := 0
haveIndexedProperties := false
for {
idProps := portIDPropsSet.SubTree(fmt.Sprintf("%d", idx))
idx++
if idProps.Size() > 0 {
haveIndexedProperties = true
if check(idProps) {
return true
}
} else if idx > 1 {
// Always check sub-id 0 and 1 (https://github.com/arduino/arduino-cli/issues/456)
break
}
}

// if there are no subindexed then check the whole "upload_port.xxx"
if !haveIndexedProperties {
return check(portIDPropsSet)
}

return false
}
150 changes: 150 additions & 0 deletions arduino/cores/board_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,153 @@ func TestBoardOptions(t *testing.T) {
// require.NoError(t, err, "marshaling result")
// fmt.Print(string(data))
}

func TestBoardMatching(t *testing.T) {
brd01 := &Board{
Properties: properties.NewFromHashmap(map[string]string{
"upload_port.pid": "0x0010",
"upload_port.vid": "0x2341",
}),
}
require.True(t, brd01.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0010",
"vid": "0x2341",
})))
require.False(t, brd01.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "xxx",
"vid": "0x2341",
})))
require.False(t, brd01.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0010",
})))
// Extra port properties are OK
require.True(t, brd01.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0010",
"vid": "0x2341",
"serial": "942947289347893247",
})))

// Indexed identifications
brd02 := &Board{
Properties: properties.NewFromHashmap(map[string]string{
"upload_port.0.pid": "0x0010",
"upload_port.0.vid": "0x2341",
"upload_port.1.pid": "0x0042",
"upload_port.1.vid": "0x2341",
"upload_port.2.pid": "0x0010",
"upload_port.2.vid": "0x2A03",
"upload_port.3.pid": "0x0042",
"upload_port.3.vid": "0x2A03",
"upload_port.4.pid": "0x0210",
"upload_port.4.vid": "0x2341",
"upload_port.5.pid": "0x0242",
"upload_port.5.vid": "0x2341",
}),
}
require.True(t, brd02.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0242",
"vid": "0x2341",
})))
require.True(t, brd02.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0242",
"vid": "0x2341",
"serial": "897439287289347",
})))

// Indexed starting from 1
brd03 := &Board{
Properties: properties.NewFromHashmap(map[string]string{
"upload_port.1.pid": "0x0042",
"upload_port.1.vid": "0x2341",
"upload_port.2.pid": "0x0010",
"upload_port.2.vid": "0x2A03",
"upload_port.3.pid": "0x0042",
"upload_port.3.vid": "0x2A03",
"upload_port.4.pid": "0x0210",
"upload_port.4.vid": "0x2341",
"upload_port.5.pid": "0x0242",
"upload_port.5.vid": "0x2341",
}),
}
require.True(t, brd03.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0242",
"vid": "0x2341",
})))
require.True(t, brd03.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0242",
"vid": "0x2341",
"serial": "897439287289347",
})))

// Mixed indentificiations (not-permitted)
brd04 := &Board{
Properties: properties.NewFromHashmap(map[string]string{
"upload_port.pid": "0x2222",
"upload_port.vid": "0x3333",
"upload_port.0.pid": "0x0010",
"upload_port.0.vid": "0x2341",
"upload_port.1.pid": "0x0042",
"upload_port.1.vid": "0x2341",
"upload_port.2.pid": "0x0010",
"upload_port.2.vid": "0x2A03",
"upload_port.3.pid": "0x0042",
"upload_port.3.vid": "0x2A03",
}),
}
require.True(t, brd04.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0042",
"vid": "0x2341",
})))
require.True(t, brd04.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0042",
"vid": "0x2341",
"serial": "897439287289347",
})))
require.False(t, brd04.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x2222",
"vid": "0x3333",
})))
require.False(t, brd04.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x2222",
"vid": "0x3333",
"serial": "897439287289347",
})))

// Mixed protocols
brd05 := &Board{
Properties: properties.NewFromHashmap(map[string]string{
"upload_port.0.pid": "0x0010",
"upload_port.0.vid": "0x2341",
"upload_port.1.pears": "2",
"upload_port.1.apples": "3",
"upload_port.1.lemons": "X",
"upload_port.2.pears": "100",
"upload_port.3.mac": "0x0010",
"upload_port.3.vid": "0x2341",
}),
}
require.True(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pid": "0x0010",
"vid": "0x2341",
})))
require.True(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pears": "2",
"apples": "3",
"lemons": "X",
})))
require.True(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pears": "100",
})))
require.True(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"mac": "0x0010",
"vid": "0x2341",
})))
require.False(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pears": "2",
})))
require.True(t, brd05.IsBoardMatchingIDProperties(properties.NewFromHashmap(map[string]string{
"pears": "100",
"apples": "300",
"lemons": "XXX",
})))
}
27 changes: 14 additions & 13 deletions arduino/cores/cores.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,20 @@ type PlatformReleaseHelp struct {

// PlatformRelease represents a release of a plaform package.
type PlatformRelease struct {
Resource *resources.DownloadResource
Version *semver.Version
BoardsManifest []*BoardManifest
Dependencies ToolDependencies // The Dependency entries to load tools.
Help PlatformReleaseHelp `json:"-"`
Platform *Platform `json:"-"`
Properties *properties.Map `json:"-"`
Boards map[string]*Board `json:"-"`
Programmers map[string]*Programmer `json:"-"`
Menus *properties.Map `json:"-"`
InstallDir *paths.Path `json:"-"`
IsIDEBundled bool `json:"-"`
IsTrusted bool `json:"-"`
Resource *resources.DownloadResource
Version *semver.Version
BoardsManifest []*BoardManifest
Dependencies ToolDependencies // The Dependency entries to load tools.
Help PlatformReleaseHelp `json:"-"`
Platform *Platform `json:"-"`
Properties *properties.Map `json:"-"`
Boards map[string]*Board `json:"-"`
Programmers map[string]*Programmer `json:"-"`
Menus *properties.Map `json:"-"`
InstallDir *paths.Path `json:"-"`
IsIDEBundled bool `json:"-"`
IsTrusted bool `json:"-"`
PluggableDiscoveryAware bool `json:"-"` // true if the Platform supports pluggable discovery (no compatibility layer required)
}

// BoardManifest contains information about a board. These metadata are usually
Expand Down
34 changes: 3 additions & 31 deletions arduino/cores/packagemanager/identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,20 @@
package packagemanager

import (
"fmt"
"strings"

"github.com/arduino/arduino-cli/arduino/cores"
properties "github.com/arduino/go-properties-orderedmap"
)

// IdentifyBoard returns a list of boards matching the provided identification properties.
// IdentifyBoard returns a list of boards whose identification properties match the
// provided ones.
func (pm *PackageManager) IdentifyBoard(idProps *properties.Map) []*cores.Board {
if idProps.Size() == 0 {
return []*cores.Board{}
}

checkSuffix := func(props *properties.Map, s string) (present bool, matched bool) {
for k, v1 := range idProps.AsMap() {
v2, ok := props.GetOk(k + s)
if !ok {
return false, false
}
if !strings.EqualFold(v1, v2) {
return true, false
}
}
return false, true
}

foundBoards := []*cores.Board{}
for _, board := range pm.InstalledBoards() {
if _, matched := checkSuffix(board.Properties, ""); matched {
if board.IsBoardMatchingIDProperties(idProps) {
foundBoards = append(foundBoards, board)
continue
}
id := 0
for {
present, matched := checkSuffix(board.Properties, fmt.Sprintf(".%d", id))
if matched {
foundBoards = append(foundBoards, board)
}
if !present && id > 0 { // Always check id 0 and 1 (https://github.com/arduino/arduino-cli/issues/456)
break
}
id++
}
}

Expand Down
51 changes: 51 additions & 0 deletions arduino/cores/packagemanager/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/arduino/arduino-cli/arduino/cores"
Expand Down Expand Up @@ -319,6 +320,10 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
return fmt.Errorf("loading %s: %s", platformTxtLocalPath, err)
}

if platform.Properties.SubTree("discovery").Size() > 0 {
platform.PluggableDiscoveryAware = true
}

if platform.Platform.Name == "" {
if name, ok := platform.Properties.GetOk("name"); ok {
platform.Platform.Name = name
Expand Down Expand Up @@ -380,6 +385,14 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
platform.Menus = properties.NewMap()
}

if !platform.PluggableDiscoveryAware {
for _, boardProperties := range propertiesByBoard {
convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties)
}
}

platform.Menus = propertiesByBoard["menu"]

// This is not a board id so we remove it to correctly
// set all other boards properties
delete(propertiesByBoard, "menu")
Expand Down Expand Up @@ -420,6 +433,44 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
return nil
}

// Converts the old:
//
// - xxx.vid.N
// - xxx.pid.N
//
// properties into pluggable discovery compatible:
//
// - xxx.upload_port.N.vid
// - xxx.upload_port.N.pid
//
func convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties *properties.Map) {
n := 0
outputVidPid := func(vid, pid string) {
boardProperties.Set(fmt.Sprintf("upload_port.%d.vid", n), vid)
boardProperties.Set(fmt.Sprintf("upload_port.%d.pid", n), pid)
n++
}
if boardProperties.ContainsKey("vid") && boardProperties.ContainsKey("pid") {
outputVidPid(boardProperties.Get("vid"), boardProperties.Get("pid"))
}

for _, k := range boardProperties.Keys() {
if strings.HasPrefix(k, "vid.") {
idx, err := strconv.ParseUint(k[4:], 10, 64)
if err != nil {
continue
}
vidKey := fmt.Sprintf("vid.%d", idx)
pidKey := fmt.Sprintf("pid.%d", idx)
vid, vidOk := boardProperties.GetOk(vidKey)
pid, pidOk := boardProperties.GetOk(pidKey)
if vidOk && pidOk {
outputVidPid(vid, pid)
}
}
}
}

func (pm *PackageManager) loadToolsFromPackage(targetPackage *cores.Package, toolsPath *paths.Path) []*status.Status {
pm.Log.Infof("Loading tools from dir: %s", toolsPath)

Expand Down
Loading

0 comments on commit b780f9c

Please sign in to comment.