Skip to content

[breaking] Refactoring of board autodetection #1717

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

Merged
merged 12 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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
24 changes: 21 additions & 3 deletions arduino/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"fmt"
"strings"

"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -125,16 +124,35 @@ func (e *InvalidVersionError) Unwrap() error {
return e.Cause
}

// NoBoardsDetectedError is returned when detecting the FQBN of a board
// does not produce any result.
type NoBoardsDetectedError struct {
Port *rpc.Port
}

func (e *NoBoardsDetectedError) Error() string {
return tr(
"Please specify an FQBN. The board on port %[1]s with protocol %[2]s can't be identified",
e.Port.Address,
e.Port.Protocol,
)
}

// ToRPCStatus converts the error into a *status.Status
func (e *NoBoardsDetectedError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// MultipleBoardsDetectedError is returned when trying to detect
// the FQBN of a board connected to a port fails because that
// are multiple possible boards detected.
type MultipleBoardsDetectedError struct {
Port *discovery.Port
Port *rpc.Port
}

func (e *MultipleBoardsDetectedError) Error() string {
return tr(
"Please specify an FQBN. Multiple possible ports detected on port %s with protocol %s",
"Please specify an FQBN. Multiple possible boards detected on port %[1]s with protocol %[2]s",
e.Port.Address,
e.Port.Protocol,
)
Expand Down
47 changes: 47 additions & 0 deletions cli/arguments/fqbn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@
package arguments

import (
"os"
"strings"

"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/sketch"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -54,3 +60,44 @@ func (f *Fqbn) String() string {
func (f *Fqbn) Set(fqbn string) {
f.fqbn = fqbn
}

// CalculateFQBNAndPort calculate the FQBN and Port metadata based on
// parameters provided by the user.
// This determine the FQBN based on:
// - the value of the FQBN flag if explicitly specified, otherwise
// - the FQBN value in sketch.json if available, otherwise
// - it tries to autodetect the board connected to the given port flags
// If all above methods fails, it returns the empty string.
// The Port metadata are always returned except if:
// - the port is not found, in this case nil is returned
// - the FQBN autodetection fail, in this case the function prints an error and
// terminates the execution
func CalculateFQBNAndPort(portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, sk *sketch.Sketch) (string, *rpc.Port) {
// TODO: REMOVE sketch.Sketch from here

fqbn := fqbnArg.String()
if fqbn == "" && sk != nil && sk.Metadata != nil {
// If the user didn't specify an FQBN and a sketch.json file is present
// read it from there.
fqbn = sk.Metadata.CPU.Fqbn
}
if fqbn == "" {
if portArgs == nil || portArgs.address == "" {
feedback.Error(&arduino.MissingFQBNError{})
os.Exit(errorcodes.ErrGeneric)
}
fqbn, port := portArgs.DetectFQBN(instance)
if fqbn == "" {
feedback.Error(&arduino.MissingFQBNError{})
os.Exit(errorcodes.ErrGeneric)
}
return fqbn, port
}

port, err := portArgs.GetPort(instance, sk)
if err != nil {
feedback.Errorf(tr("Error getting port metadata: %v", err))
os.Exit(errorcodes.ErrGeneric)
}
return fqbn, port.ToRPC()
}
32 changes: 27 additions & 5 deletions cli/arguments/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import (
"os"
"time"

"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/arduino/sketch"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/commands/board"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -152,12 +154,32 @@ func (p *Port) GetSearchTimeout() time.Duration {
return p.timeout
}

// GetDiscoveryPort is a helper function useful to get the port and handle possible errors
func (p *Port) GetDiscoveryPort(instance *rpc.Instance, sk *sketch.Sketch) *discovery.Port {
discoveryPort, err := p.GetPort(instance, sk)
// DetectFQBN tries to identify the board connected to the port and returns the
// discovered Port object together with the FQBN. If the port does not match
// exactly 1 board,
func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) {
detectedPorts, err := board.List(&rpc.BoardListRequest{Instance: inst})
if err != nil {
feedback.Errorf(tr("Error discovering port: %v"), err)
feedback.Errorf(tr("Error during FQBN detection: %v", err))
os.Exit(errorcodes.ErrGeneric)
}
return discoveryPort
for _, detectedPort := range detectedPorts {
port := detectedPort.GetPort()
if p.address != port.GetAddress() {
continue
}
if p.protocol != "" && p.protocol != port.GetProtocol() {
continue
}
if len(detectedPort.MatchingBoards) > 1 {
feedback.Error(&arduino.MultipleBoardsDetectedError{Port: port})
os.Exit(errorcodes.ErrBadArgument)
}
if len(detectedPort.MatchingBoards) == 0 {
feedback.Error(&arduino.NoBoardsDetectedError{Port: port})
os.Exit(errorcodes.ErrBadArgument)
}
return detectedPort.MatchingBoards[0].Fqbn, port
}
return "", nil
}
54 changes: 14 additions & 40 deletions cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import (

"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/arduino/sketch"
"github.com/arduino/arduino-cli/cli/arguments"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/globals"
Expand All @@ -47,7 +45,7 @@ import (
)

var (
fqbn arguments.Fqbn // Fully Qualified Board Name, e.g.: arduino:avr:uno.
fqbnArg arguments.Fqbn // Fully Qualified Board Name, e.g.: arduino:avr:uno.
showProperties bool // Show all build preferences used instead of compiling.
preprocess bool // Print preprocessed code to stdout.
buildCachePath string // Builds of 'core.a' are saved into this path to be cached and reused.
Expand All @@ -61,7 +59,7 @@ var (
quiet bool // Suppresses almost every output.
vidPid string // VID/PID specific build properties.
uploadAfterCompile bool // Upload the binary after the compilation.
port arguments.Port // Upload port, e.g.: COM10 or /dev/ttyACM0.
portArgs arguments.Port // Upload port, e.g.: COM10 or /dev/ttyACM0.
verify bool // Upload, verify uploaded binary after the upload.
exportDir string // The compiled binary is written to this file
optimizeForDebug bool // Optimize compile output for debug, not for release
Expand Down Expand Up @@ -92,7 +90,7 @@ func NewCommand() *cobra.Command {
Run: runCompileCommand,
}

fqbn.AddToCommand(compileCommand)
fqbnArg.AddToCommand(compileCommand)
compileCommand.Flags().BoolVar(&showProperties, "show-properties", false, tr("Show all build properties used instead of compiling."))
compileCommand.Flags().BoolVar(&preprocess, "preprocess", false, tr("Print preprocessed code to stdout instead of compiling."))
compileCommand.Flags().StringVar(&buildCachePath, "build-cache-path", "", tr("Builds of 'core.a' are saved into this path to be cached and reused."))
Expand All @@ -114,7 +112,7 @@ func NewCommand() *cobra.Command {
compileCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Optional, turns on verbose mode."))
compileCommand.Flags().BoolVar(&quiet, "quiet", false, tr("Optional, suppresses almost every output."))
compileCommand.Flags().BoolVarP(&uploadAfterCompile, "upload", "u", false, tr("Upload the binary after the compilation."))
port.AddToCommand(compileCommand)
portArgs.AddToCommand(compileCommand)
compileCommand.Flags().BoolVarP(&verify, "verify", "t", false, tr("Verify uploaded binary after the upload."))
compileCommand.Flags().StringVar(&vidPid, "vid-pid", "", tr("When specified, VID/PID specific build properties are used, if board supports them."))
compileCommand.Flags().StringSliceVar(&library, "library", []string{},
Expand Down Expand Up @@ -150,6 +148,8 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
}

sketchPath := arguments.InitSketchPath(path)
sk := arguments.NewSketch(sketchPath)
fqbn, port := arguments.CalculateFQBNAndPort(&portArgs, &fqbnArg, inst, sk)

if keysKeychain != "" || signKey != "" || encryptKey != "" {
arguments.CheckFlagsMandatory(cmd, "keys-keychain", "sign-key", "encrypt-key")
Expand All @@ -172,28 +172,9 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
overrides = o.Overrides
}

detectedFqbn := fqbn.String()
var sk *sketch.Sketch
var discoveryPort *discovery.Port
// If the user didn't provide an FQBN it might either mean
// that she forgot or that is trying to compile and upload
// using board autodetection.
if detectedFqbn == "" && uploadAfterCompile {
sk = arguments.NewSketch(sketchPath)
discoveryPort = port.GetDiscoveryPort(inst, sk)
rpcPort := discoveryPort.ToRPC()
var err error
pm := commands.GetPackageManager(inst.Id)
detectedFqbn, err = upload.DetectConnectedBoard(pm, rpcPort.Address, rpcPort.Protocol)
if err != nil {
feedback.Errorf(tr("Error during FQBN detection: %v", err))
os.Exit(errorcodes.ErrGeneric)
}
}

compileRequest := &rpc.CompileRequest{
Instance: inst,
Fqbn: detectedFqbn,
Fqbn: fqbn,
SketchPath: sketchPath.String(),
ShowProperties: showProperties,
Preprocess: preprocess,
Expand Down Expand Up @@ -227,35 +208,28 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
}

if compileError == nil && uploadAfterCompile {
if sk == nil {
sk = arguments.NewSketch(sketchPath)
}
if discoveryPort == nil {
discoveryPort = port.GetDiscoveryPort(inst, sk)
}

userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
Instance: inst,
Fqbn: fqbn.String(),
Address: discoveryPort.Address,
Protocol: discoveryPort.Protocol,
Fqbn: fqbn,
Address: port.Address,
Protocol: port.Protocol,
})
if err != nil {
feedback.Errorf(tr("Error during Upload: %v"), err)
feedback.Errorf(tr("Error during Upload: %v", err))
os.Exit(errorcodes.ErrGeneric)
}

fields := map[string]string{}
if len(userFieldRes.UserFields) > 0 {
feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", discoveryPort.Protocol))
feedback.Print(tr("Uploading to specified board using %s protocol requires the following info:", port.Protocol))
fields = arguments.AskForUserFields(userFieldRes.UserFields)
}

uploadRequest := &rpc.UploadRequest{
Instance: inst,
Fqbn: detectedFqbn,
Fqbn: fqbn,
SketchPath: sketchPath.String(),
Port: discoveryPort.ToRPC(),
Port: port,
Verbose: verbose,
Verify: verify,
ImportDir: buildPath,
Expand Down
19 changes: 9 additions & 10 deletions cli/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import (
)

var (
fqbn arguments.Fqbn
port arguments.Port
fqbnArg arguments.Fqbn
portArgs arguments.Port
interpreter string
importDir string
printInfo bool
Expand All @@ -56,8 +56,8 @@ func NewCommand() *cobra.Command {
Run: runDebugCommand,
}

fqbn.AddToCommand(debugCommand)
port.AddToCommand(debugCommand)
fqbnArg.AddToCommand(debugCommand)
portArgs.AddToCommand(debugCommand)
programmer.AddToCommand(debugCommand)
debugCommand.Flags().StringVar(&interpreter, "interpreter", "console", tr("Debug interpreter e.g.: %s", "console, mi, mi1, mi2, mi3"))
debugCommand.Flags().StringVarP(&importDir, "input-dir", "", "", tr("Directory containing binaries for debug."))
Expand All @@ -77,13 +77,12 @@ func runDebugCommand(command *cobra.Command, args []string) {

sketchPath := arguments.InitSketchPath(path)
sk := arguments.NewSketch(sketchPath)
discoveryPort := port.GetDiscoveryPort(instance, sk)

fqbn, port := arguments.CalculateFQBNAndPort(&portArgs, &fqbnArg, instance, sk)
debugConfigRequested := &dbg.DebugConfigRequest{
Instance: instance,
Fqbn: fqbn.String(),
Fqbn: fqbn,
SketchPath: sketchPath.String(),
Port: discoveryPort.ToRPC(),
Port: port,
Interpreter: interpreter,
ImportDir: importDir,
Programmer: programmer.String(),
Expand All @@ -92,7 +91,7 @@ func runDebugCommand(command *cobra.Command, args []string) {
if printInfo {

if res, err := debug.GetDebugConfig(context.Background(), debugConfigRequested); err != nil {
feedback.Errorf(tr("Error getting Debug info: %v"), err)
feedback.Errorf(tr("Error getting Debug info: %v", err))
os.Exit(errorcodes.ErrBadArgument)
} else {
feedback.PrintResult(&debugInfoResult{res})
Expand All @@ -105,7 +104,7 @@ func runDebugCommand(command *cobra.Command, args []string) {
signal.Notify(ctrlc, os.Interrupt)

if _, err := debug.Debug(context.Background(), debugConfigRequested, os.Stdin, os.Stdout, ctrlc); err != nil {
feedback.Errorf(tr("Error during Debug: %v"), err)
feedback.Errorf(tr("Error during Debug: %v", err))
os.Exit(errorcodes.ErrGeneric)
}

Expand Down
Loading