Skip to content
Merged
Changes from 5 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
23 changes: 23 additions & 0 deletions arduino/cores/fqbn.go
Original file line number Diff line number Diff line change
@@ -76,6 +76,29 @@ func (fqbn *FQBN) String() string {
return res
}

// Match check if the target FQBN corresponds to the receiver one.
// The core parts are checked for exact equality while board options are loosely
// matched: the set of boards options of the target must be fully contained within
// the one of the receiver and their values must be equal.
func (fqbn *FQBN) Match(target *FQBN) bool {
if fqbn.StringWithoutConfig() != target.StringWithoutConfig() {
return false
}
searchedProperties := target.Configs.Clone()
actualConfigs := fqbn.Configs.AsMap()
for neededKey, neededValue := range searchedProperties.AsMap() {
targetValue, hasKey := actualConfigs[neededKey]
if !hasKey {
return false
}
if targetValue != neededValue {
return false
}
}

return true
}

// StringWithoutConfig returns the FQBN without the Config part
func (fqbn *FQBN) StringWithoutConfig() string {
return fqbn.Package + ":" + fqbn.PlatformArch + ":" + fqbn.BoardID
34 changes: 34 additions & 0 deletions arduino/cores/fqbn_test.go
Original file line number Diff line number Diff line change
@@ -121,3 +121,37 @@ func TestFQBN(t *testing.T) {
"properties.Map{\n \"cpu\": \"atmega\",\n \"speed\": \"1000\",\n \"extra\": \"core=arduino\",\n}",
f.Configs.Dump())
}

func TestMatch(t *testing.T) {
expectedMatches := [][]string{
{"arduino:avr:uno", "arduino:avr:uno"},
{"arduino:avr:uno", "arduino:avr:uno:opt1=1,opt2=2"},
{"arduino:avr:uno:opt1=1", "arduino:avr:uno:opt1=1,opt2=2"},
{"arduino:avr:uno:opt1=1,opt2=2", "arduino:avr:uno:opt1=1,opt2=2"},
{"arduino:avr:uno:opt3=3,opt1=1,opt2=2", "arduino:avr:uno:opt2=2,opt3=3,opt1=1,opt4=4"},
}

for _, pair := range expectedMatches {
a, err := ParseFQBN(pair[0])
require.NoError(t, err)
b, err := ParseFQBN(pair[1])
require.NoError(t, err)
require.True(t, b.Match(a))
}

expectedMismatches := [][]string{
{"arduino:avr:uno", "arduino:avr:due"},
{"arduino:avr:uno", "arduino:avr:due:opt1=1,opt2=2"},
{"arduino:avr:uno:opt1=1", "arduino:avr:uno"},
{"arduino:avr:uno:opt1=1,opt2=", "arduino:avr:uno:opt1=1,opt2=3"},
{"arduino:avr:uno:opt1=1,opt2=2", "arduino:avr:uno:opt2=2"},
}

for _, pair := range expectedMismatches {
a, err := ParseFQBN(pair[0])
require.NoError(t, err)
b, err := ParseFQBN(pair[1])
require.NoError(t, err)
require.False(t, b.Match(a))
}
}
27 changes: 26 additions & 1 deletion commands/board/list.go
Original file line number Diff line number Diff line change
@@ -205,6 +205,15 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartError
}
defer release()

var fqbnFilter *cores.FQBN
if f := req.GetFqbn(); f != "" {
var err error
fqbnFilter, err = cores.ParseFQBN(f)
if err != nil {
return nil, nil, &arduino.InvalidFQBNError{Cause: err}
}
}

dm := pme.DiscoveryManager()
discoveryStartErrors = dm.Start()
time.Sleep(time.Duration(req.GetTimeout()) * time.Millisecond)
@@ -222,11 +231,27 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartError
Port: port.ToRPC(),
MatchingBoards: boards,
}
retVal = append(retVal, b)

if fqbnFilter == nil || hasMatchingBoard(b, fqbnFilter) {
retVal = append(retVal, b)
}
}
return retVal, discoveryStartErrors, nil
}

func hasMatchingBoard(b *rpc.DetectedPort, fqbnFilter *cores.FQBN) bool {
for _, detectedBoard := range b.MatchingBoards {
detectedFqbn, err := cores.ParseFQBN(detectedBoard.Fqbn)
if err != nil {
continue
}
if detectedFqbn.Match(fqbnFilter) {
return true
}
}
return false
}

// Watch returns a channel that receives boards connection and disconnection events.
// It also returns a callback function that must be used to stop and dispose the watch.
func Watch(req *rpc.BoardListWatchRequest) (<-chan *rpc.BoardListWatchResponse, func(), error) {
12 changes: 10 additions & 2 deletions internal/cli/board/list.go
Original file line number Diff line number Diff line change
@@ -16,10 +16,12 @@
package board

import (
"errors"
"fmt"
"os"
"sort"

"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/commands/board"
"github.com/arduino/arduino-cli/internal/cli/arguments"
@@ -47,6 +49,7 @@ func initListCommand() *cobra.Command {
}

timeoutArg.AddToCommand(listCommand)
fqbn.AddToCommand(listCommand)
listCommand.Flags().BoolVarP(&watch, "watch", "w", false, tr("Command keeps running and prints list of connected boards whenever there is a change."))

return listCommand
@@ -63,14 +66,19 @@ func runListCommand(cmd *cobra.Command, args []string) {
return
}

ports, discvoeryErrors, err := board.List(&rpc.BoardListRequest{
ports, discoveryErrors, err := board.List(&rpc.BoardListRequest{
Instance: inst,
Timeout: timeoutArg.Get().Milliseconds(),
Fqbn: fqbn.String(),
})
var invalidFQBNErr *arduino.InvalidFQBNError
if errors.As(err, &invalidFQBNErr) {
feedback.Fatal(tr(err.Error()), feedback.ErrBadArgument)
}
if err != nil {
feedback.Warning(tr("Error detecting boards: %v", err))
}
for _, err := range discvoeryErrors {
for _, err := range discoveryErrors {
feedback.Warning(tr("Error starting discovery: %v", err))
}
feedback.PrintResult(result{ports})
33 changes: 33 additions & 0 deletions internal/integrationtest/board/board_test.go
Original file line number Diff line number Diff line change
@@ -91,6 +91,39 @@ func TestBoardList(t *testing.T) {
MustBeEmpty()
}

func TestBoardListWithFqbnFilter(t *testing.T) {
if os.Getenv("CI") != "" {
t.Skip("VMs have no serial ports")
}

env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()

_, _, err := cli.Run("core", "update-index")
require.NoError(t, err)
stdout, _, err := cli.Run("board", "list", "-b", "foo:bar:baz", "--format", "json")
require.NoError(t, err)
// this is a bit of a passpartout test, it actually filters the "bluetooth boards" locally
// but it would succeed even if the filtering wasn't working properly
// TODO: find a way to simulate connected boards or create a unit test which
// mocks or initializes multiple components
requirejson.Parse(t, stdout).
MustBeEmpty()
}

func TestBoardListWithFqbnFilterInvalid(t *testing.T) {
if os.Getenv("CI") != "" {
t.Skip("VMs have no serial ports")
}

env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()

_, stderr, err := cli.Run("board", "list", "-b", "yadayada", "--format", "json")
require.Error(t, err)
requirejson.Query(t, stderr, ".error", `"Invalid FQBN: not an FQBN: yadayada"`)
}

func TestBoardListWithInvalidDiscovery(t *testing.T) {
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()
176 changes: 94 additions & 82 deletions rpc/cc/arduino/cli/commands/v1/board.pb.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions rpc/cc/arduino/cli/commands/v1/board.proto
Original file line number Diff line number Diff line change
@@ -152,6 +152,9 @@ message BoardListRequest {
Instance instance = 1;
// Search for boards for the given time (in milliseconds)
int64 timeout = 2;
// The fully qualified board name of the board you want information about
// (e.g., `arduino:avr:uno`).
string fqbn = 3;
}

message BoardListResponse {
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/commands.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/common.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/compile.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/core.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/lib.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/monitor.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/port.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/upload.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/debug/v1/debug.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/debug/v1/debug_grpc.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/settings/v1/settings.pb.go
2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/settings/v1/settings_grpc.pb.go