diff --git a/arduino/cores/board.go b/arduino/cores/board.go index a9ccef20323..6125bcd5cb6 100644 --- a/arduino/cores/board.go +++ b/arduino/cores/board.go @@ -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 +} diff --git a/arduino/cores/board_test.go b/arduino/cores/board_test.go index 63dac6dfa51..3eeb35d16a2 100644 --- a/arduino/cores/board_test.go +++ b/arduino/cores/board_test.go @@ -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", + }))) +} diff --git a/arduino/cores/cores.go b/arduino/cores/cores.go index 39002f16fef..724dac7c64a 100644 --- a/arduino/cores/cores.go +++ b/arduino/cores/cores.go @@ -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 diff --git a/arduino/cores/packagemanager/identify.go b/arduino/cores/packagemanager/identify.go index b84dc9804c8..6765ca66e21 100644 --- a/arduino/cores/packagemanager/identify.go +++ b/arduino/cores/packagemanager/identify.go @@ -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++ } } diff --git a/arduino/cores/packagemanager/loader.go b/arduino/cores/packagemanager/loader.go index 3b94fc3a76d..58af475c151 100644 --- a/arduino/cores/packagemanager/loader.go +++ b/arduino/cores/packagemanager/loader.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "github.com/arduino/arduino-cli/arduino/cores" @@ -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 @@ -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") @@ -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) diff --git a/arduino/cores/packagemanager/loader_test.go b/arduino/cores/packagemanager/loader_test.go new file mode 100644 index 00000000000..ea53db96395 --- /dev/null +++ b/arduino/cores/packagemanager/loader_test.go @@ -0,0 +1,105 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 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 license@arduino.cc. + +package packagemanager + +import ( + "testing" + + "github.com/arduino/go-properties-orderedmap" + "github.com/stretchr/testify/require" +) + +func TestVidPidConvertionToPluggableDiscovery(t *testing.T) { + m, err := properties.LoadFromBytes([]byte(` +arduino_zero_edbg.name=Arduino Zero (Programming Port) +arduino_zero_edbg.vid.0=0x03eb +arduino_zero_edbg.pid.0=0x2157 +arduino_zero_edbg_2.name=Arduino Zero (Programming Port) +arduino_zero_edbg_2.vid=0x03eb +arduino_zero_edbg_2.pid=0x2157 +arduino_zero_edbg_3.name=Arduino Zero (Programming Port) +arduino_zero_edbg_3.vid=0x03eb +arduino_zero_edbg_3.pid=0x2157 +arduino_zero_edbg_3.vid.0=0x03ea +arduino_zero_edbg_3.pid.0=0x2157 +arduino_zero_native.name=Arduino Zero (Native USB Port) +arduino_zero_native.vid.0=0x2341 +arduino_zero_native.pid.0=0x804d +arduino_zero_native.vid.1=0x2341 +arduino_zero_native.pid.1=0x004d +arduino_zero_native.vid.2=0x2341 +arduino_zero_native.pid.2=0x824d +arduino_zero_native.vid.3=0x2341 +arduino_zero_native.pid.3=0x024d +`)) + require.NoError(t, err) + + zero := m.SubTree("arduino_zero_edbg") + convertVidPidIdentificationPropertiesToPluggableDiscovery(zero) + require.Equal(t, `properties.Map{ + "name": "Arduino Zero (Programming Port)", + "vid.0": "0x03eb", + "pid.0": "0x2157", + "upload_port.0.vid": "0x03eb", + "upload_port.0.pid": "0x2157", +}`, zero.Dump()) + + zero2 := m.SubTree("arduino_zero_edbg_2") + convertVidPidIdentificationPropertiesToPluggableDiscovery(zero2) + require.Equal(t, `properties.Map{ + "name": "Arduino Zero (Programming Port)", + "vid": "0x03eb", + "pid": "0x2157", + "upload_port.0.vid": "0x03eb", + "upload_port.0.pid": "0x2157", +}`, zero2.Dump()) + + zero3 := m.SubTree("arduino_zero_edbg_3") + convertVidPidIdentificationPropertiesToPluggableDiscovery(zero3) + require.Equal(t, `properties.Map{ + "name": "Arduino Zero (Programming Port)", + "vid": "0x03eb", + "pid": "0x2157", + "vid.0": "0x03ea", + "pid.0": "0x2157", + "upload_port.0.vid": "0x03eb", + "upload_port.0.pid": "0x2157", + "upload_port.1.vid": "0x03ea", + "upload_port.1.pid": "0x2157", +}`, zero3.Dump()) + + zero4 := m.SubTree("arduino_zero_native") + convertVidPidIdentificationPropertiesToPluggableDiscovery(zero4) + require.Equal(t, `properties.Map{ + "name": "Arduino Zero (Native USB Port)", + "vid.0": "0x2341", + "pid.0": "0x804d", + "vid.1": "0x2341", + "pid.1": "0x004d", + "vid.2": "0x2341", + "pid.2": "0x824d", + "vid.3": "0x2341", + "pid.3": "0x024d", + "upload_port.0.vid": "0x2341", + "upload_port.0.pid": "0x804d", + "upload_port.1.vid": "0x2341", + "upload_port.1.pid": "0x004d", + "upload_port.2.vid": "0x2341", + "upload_port.2.pid": "0x824d", + "upload_port.3.vid": "0x2341", + "upload_port.3.pid": "0x024d", +}`, zero4.Dump()) +} diff --git a/commands/board/list_test.go b/commands/board/list_test.go index 02212c4e119..773036cb815 100644 --- a/commands/board/list_test.go +++ b/commands/board/list_test.go @@ -61,7 +61,7 @@ func TestGetByVidPid(t *testing.T) { require.Equal(t, "0XF069", res[0].Pid) // wrong vid (too long), wrong pid (not an hex value) - res, err = apiByVidPid("0xfffff", "0xDEFG") + _, err = apiByVidPid("0xfffff", "0xDEFG") require.NotNil(t, err) } @@ -130,11 +130,11 @@ func TestBoardIdentifySorting(t *testing.T) { platformRelease := platform.GetOrCreateRelease(semver.MustParse("0.0.0")) platformRelease.InstallDir = dataDir board := platformRelease.GetOrCreateBoard("boardA") - board.Properties.Set("vid", "0x0000") - board.Properties.Set("pid", "0x0000") + board.Properties.Set("upload_port.vid", "0x0000") + board.Properties.Set("upload_port.pid", "0x0000") board = platformRelease.GetOrCreateBoard("boardB") - board.Properties.Set("vid", "0x0000") - board.Properties.Set("pid", "0x0000") + board.Properties.Set("upload_port.vid", "0x0000") + board.Properties.Set("upload_port.pid", "0x0000") // Create some Arduino boards with same VID:PID combination as boards created previously pack = pm.Packages.GetOrCreatePackage("arduino") @@ -143,11 +143,11 @@ func TestBoardIdentifySorting(t *testing.T) { platformRelease = platform.GetOrCreateRelease(semver.MustParse("0.0.0")) platformRelease.InstallDir = dataDir board = platformRelease.GetOrCreateBoard("nessuno") - board.Properties.Set("vid", "0x0000") - board.Properties.Set("pid", "0x0000") + board.Properties.Set("upload_port.vid", "0x0000") + board.Properties.Set("upload_port.pid", "0x0000") board = platformRelease.GetOrCreateBoard("assurdo") - board.Properties.Set("vid", "0x0000") - board.Properties.Set("pid", "0x0000") + board.Properties.Set("upload_port.vid", "0x0000") + board.Properties.Set("upload_port.pid", "0x0000") idPrefs := properties.NewMap() idPrefs.Set("vid", "0x0000")