diff --git a/arduino/cores/packagemanager/download.go b/arduino/cores/packagemanager/download.go index f3a5877c8a6..5baf34d2b9c 100644 --- a/arduino/cores/packagemanager/download.go +++ b/arduino/cores/packagemanager/download.go @@ -43,12 +43,12 @@ func (platform *PlatformReference) String() string { // FindPlatform returns the Platform matching the PlatformReference or nil if not found. // The PlatformVersion field of the reference is ignored. -func (pme *Explorer) FindPlatform(ref *PlatformReference) *cores.Platform { - targetPackage, ok := pme.packages[ref.Package] +func (pme *Explorer) FindPlatform(platformPackage, platformArchitecture string) *cores.Platform { + targetPackage, ok := pme.packages[platformPackage] if !ok { return nil } - platform, ok := targetPackage.Platforms[ref.PlatformArchitecture] + platform, ok := targetPackage.Platforms[platformArchitecture] if !ok { return nil } @@ -57,7 +57,7 @@ func (pme *Explorer) FindPlatform(ref *PlatformReference) *cores.Platform { // FindPlatformRelease returns the PlatformRelease matching the PlatformReference or nil if not found func (pme *Explorer) FindPlatformRelease(ref *PlatformReference) *cores.PlatformRelease { - platform := pme.FindPlatform(ref) + platform := pme.FindPlatform(ref.Package, ref.PlatformArchitecture) if platform == nil { return nil } diff --git a/arduino/cores/packagemanager/install_uninstall.go b/arduino/cores/packagemanager/install_uninstall.go index 73a75c7b00d..93ef233322e 100644 --- a/arduino/cores/packagemanager/install_uninstall.go +++ b/arduino/cores/packagemanager/install_uninstall.go @@ -44,7 +44,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades( } // Search the latest version for all specified platforms - platform := pme.FindPlatform(platformRef) + platform := pme.FindPlatform(platformRef.Package, platformRef.PlatformArchitecture) if platform == nil { return nil, &arduino.PlatformNotFoundError{Platform: platformRef.String()} } diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go index 7a6d8994004..e847d37ac31 100644 --- a/arduino/cores/packagemanager/package_manager_test.go +++ b/arduino/cores/packagemanager/package_manager_test.go @@ -387,10 +387,7 @@ func TestBoardOrdering(t *testing.T) { pme, release := pm.NewExplorer() defer release() - pl := pme.FindPlatform(&PlatformReference{ - Package: "arduino", - PlatformArchitecture: "avr", - }) + pl := pme.FindPlatform("arduino", "avr") require.NotNil(t, pl) plReleases := pl.GetAllInstalled() require.NotEmpty(t, plReleases) diff --git a/commands/core/uninstall.go b/commands/core/uninstall.go index 165b1fc16ea..da8cb786e5e 100644 --- a/commands/core/uninstall.go +++ b/commands/core/uninstall.go @@ -48,7 +48,7 @@ func platformUninstall(ctx context.Context, req *rpc.PlatformUninstallRequest, t PlatformArchitecture: req.Architecture, } if ref.PlatformVersion == nil { - platform := pme.FindPlatform(ref) + platform := pme.FindPlatform(ref.Package, ref.PlatformArchitecture) if platform == nil { return &arduino.PlatformNotFoundError{Platform: ref.String()} } diff --git a/internal/cli/monitor/term.go b/commands/sketch/warn_deprecated.go similarity index 54% rename from internal/cli/monitor/term.go rename to commands/sketch/warn_deprecated.go index 34e444b5591..a78f7156973 100644 --- a/internal/cli/monitor/term.go +++ b/commands/sketch/warn_deprecated.go @@ -13,39 +13,24 @@ // Arduino software without disclosing the source code of your own applications. // To purchase a commercial license, send an email to license@arduino.cc. -package monitor +package sketch import ( - "io" + "fmt" - "github.com/arduino/arduino-cli/internal/cli/feedback" + "github.com/arduino/arduino-cli/arduino/sketch" + paths "github.com/arduino/go-paths-helper" ) -type stdInOut struct { - in io.Reader - out io.Writer -} - -func newStdInOutTerminal() (*stdInOut, error) { - in, out, err := feedback.InteractiveStreams() - if err != nil { - return nil, err +// WarnDeprecatedFiles warns the user that a type of sketch files are deprecated +func WarnDeprecatedFiles(sketchPath *paths.Path) string { + // .pde files are still supported but deprecated, this warning urges the user to rename them + if files := sketch.CheckForPdeFiles(sketchPath); len(files) > 0 { + msg := tr("Sketches with .pde extension are deprecated, please rename the following files to .ino:") + for _, f := range files { + msg += fmt.Sprintf("\n - %s", f) + } + return msg } - - return &stdInOut{ - in: in, - out: out, - }, nil -} - -func (n *stdInOut) Close() error { - return nil -} - -func (n *stdInOut) Read(buff []byte) (int, error) { - return n.in.Read(buff) -} - -func (n *stdInOut) Write(buff []byte) (int, error) { - return n.out.Write(buff) + return "" } diff --git a/commands/upload/upload.go b/commands/upload/upload.go index aa74023b0e3..40cbb0bb018 100644 --- a/commands/upload/upload.go +++ b/commands/upload/upload.go @@ -285,10 +285,7 @@ func runProgramAction(pme *packagemanager.Explorer, Property: fmt.Sprintf("%s.tool.%s", action, port.Protocol), // TODO: Can be done better, maybe inline getToolID(...) Value: uploadToolID} } else if len(split) == 2 { - p := pme.FindPlatform(&packagemanager.PlatformReference{ - Package: split[0], - PlatformArchitecture: boardPlatform.Platform.Architecture, - }) + p := pme.FindPlatform(split[0], boardPlatform.Platform.Architecture) if p == nil { return nil, &arduino.PlatformNotFoundError{Platform: split[0] + ":" + boardPlatform.Platform.Architecture} } diff --git a/internal/cli/arguments/completion.go b/internal/cli/arguments/completion.go index f3348899785..5b25b2773bc 100644 --- a/internal/cli/arguments/completion.go +++ b/internal/cli/arguments/completion.go @@ -18,11 +18,11 @@ package arguments import ( "context" - "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/board" "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/commands/lib" + "github.com/arduino/arduino-cli/commands/upload" "github.com/arduino/arduino-cli/internal/cli/instance" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" ) @@ -103,10 +103,12 @@ func GetInstalledProgrammers() []string { installedProgrammers := make(map[string]string) for _, board := range list.Boards { - fqbn, _ := cores.ParseFQBN(board.Fqbn) - _, boardPlatform, _, _, _, _ := pme.ResolveFQBN(fqbn) - for programmerID, programmer := range boardPlatform.Programmers { - installedProgrammers[programmerID] = programmer.Name + programmers, _ := upload.ListProgrammersAvailableForUpload(context.Background(), &rpc.ListProgrammersAvailableForUploadRequest{ + Instance: inst, + Fqbn: board.Fqbn, + }) + for _, programmer := range programmers.GetProgrammers() { + installedProgrammers[programmer.GetId()] = programmer.GetName() } } diff --git a/internal/cli/arguments/sketch.go b/internal/cli/arguments/sketch.go index 6c167ad23dd..4e670bab6ac 100644 --- a/internal/cli/arguments/sketch.go +++ b/internal/cli/arguments/sketch.go @@ -16,9 +16,7 @@ package arguments import ( - "fmt" - - "github.com/arduino/arduino-cli/arduino/sketch" + sk "github.com/arduino/arduino-cli/commands/sketch" "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" @@ -38,18 +36,8 @@ func InitSketchPath(path string) (sketchPath *paths.Path) { logrus.Infof("Reading sketch from dir: %s", wd) sketchPath = wd } - WarnDeprecatedFiles(sketchPath) - return sketchPath -} - -// WarnDeprecatedFiles warns the user that a type of sketch files are deprecated -func WarnDeprecatedFiles(sketchPath *paths.Path) { - // .pde files are still supported but deprecated, this warning urges the user to rename them - if files := sketch.CheckForPdeFiles(sketchPath); len(files) > 0 { - msg := tr("Sketches with .pde extension are deprecated, please rename the following files to .ino:") - for _, f := range files { - msg += fmt.Sprintf("\n - %s", f) - } + if msg := sk.WarnDeprecatedFiles(sketchPath); msg != "" { feedback.Warning(msg) } + return sketchPath } diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go index dfd5d4c70ba..fbb6ef6cb1a 100644 --- a/internal/cli/compile/compile.go +++ b/internal/cli/compile/compile.go @@ -25,7 +25,6 @@ import ( "strings" "github.com/arduino/arduino-cli/arduino" - "github.com/arduino/arduino-cli/arduino/cores/packagemanager" "github.com/arduino/arduino-cli/commands" "github.com/arduino/arduino-cli/commands/compile" "github.com/arduino/arduino-cli/commands/sketch" @@ -363,12 +362,8 @@ func runCompileCommand(cmd *cobra.Command, args []string) { panic(tr("Platform ID is not correct")) } - // FIXME: Here we should not access PackageManager... pme, release := commands.GetPackageManagerExplorer(compileRequest) - platform := pme.FindPlatform(&packagemanager.PlatformReference{ - Package: split[0], - PlatformArchitecture: split[1], - }) + platform := pme.FindPlatform(split[0], split[1]) release() if profileArg.String() == "" { diff --git a/internal/cli/feedback/terminal.go b/internal/cli/feedback/terminal.go index 5b645cbaf67..11c466bae8b 100644 --- a/internal/cli/feedback/terminal.go +++ b/internal/cli/feedback/terminal.go @@ -41,6 +41,28 @@ func InteractiveStreams() (io.Reader, io.Writer, error) { return os.Stdin, stdOut, nil } +var oldStateStdin *term.State + +// SetRawModeStdin sets the stdin stream in RAW mode (no buffering, echo disabled, +// no terminal escape codes nor signals interpreted) +func SetRawModeStdin() { + if oldStateStdin != nil { + panic("terminal already in RAW mode") + } + oldStateStdin, _ = term.MakeRaw(int(os.Stdin.Fd())) +} + +// RestoreModeStdin restore the terminal settings to the normal non-RAW state. This +// function must be called after SetRawModeStdin to not leave the terminal in an +// undefined state. +func RestoreModeStdin() { + if oldStateStdin == nil { + return + } + _ = term.Restore(int(os.Stdin.Fd()), oldStateStdin) + oldStateStdin = nil +} + func isTerminal() bool { return term.IsTerminal(int(os.Stdin.Fd())) } diff --git a/internal/cli/monitor/monitor.go b/internal/cli/monitor/monitor.go index 6fcf4a87de6..65b5c599a76 100644 --- a/internal/cli/monitor/monitor.go +++ b/internal/cli/monitor/monitor.go @@ -16,6 +16,7 @@ package monitor import ( + "bytes" "context" "errors" "fmt" @@ -35,19 +36,21 @@ import ( "github.com/fatih/color" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "go.bug.st/cleanup" ) -var ( - portArgs arguments.Port - describe bool - configs []string - quiet bool - fqbn arguments.Fqbn - tr = i18n.Tr -) +var tr = i18n.Tr // NewCommand created a new `monitor` command func NewCommand() *cobra.Command { + var ( + raw bool + portArgs arguments.Port + describe bool + configs []string + quiet bool + fqbn arguments.Fqbn + ) monitorCommand := &cobra.Command{ Use: "monitor", Short: tr("Open a communication port with a board."), @@ -55,9 +58,12 @@ func NewCommand() *cobra.Command { Example: "" + " " + os.Args[0] + " monitor -p /dev/ttyACM0\n" + " " + os.Args[0] + " monitor -p /dev/ttyACM0 --describe", - Run: runMonitorCmd, + Run: func(cmd *cobra.Command, args []string) { + runMonitorCmd(&portArgs, &fqbn, configs, describe, quiet, raw) + }, } portArgs.AddToCommand(monitorCommand) + monitorCommand.Flags().BoolVar(&raw, "raw", false, tr("Set terminal in raw mode (unbuffered).")) monitorCommand.Flags().BoolVar(&describe, "describe", false, tr("Show all the settings of the communication port.")) monitorCommand.Flags().StringSliceVarP(&configs, "config", "c", []string{}, tr("Configure communication port settings. The format is =[,=]...")) monitorCommand.Flags().BoolVarP(&quiet, "quiet", "q", false, tr("Run in silent mode, show only monitor input and output.")) @@ -66,7 +72,7 @@ func NewCommand() *cobra.Command { return monitorCommand } -func runMonitorCmd(cmd *cobra.Command, args []string) { +func runMonitorCmd(portArgs *arguments.Port, fqbn *arguments.Fqbn, configs []string, describe, quiet, raw bool) { instance := instance.CreateAndInit() logrus.Info("Executing `arduino-cli monitor`") @@ -93,12 +99,6 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { return } - tty, err := newStdInOutTerminal() - if err != nil { - feedback.FatalError(err, feedback.ErrGeneric) - } - defer tty.Close() - configuration := &rpc.MonitorPortConfiguration{} if len(configs) > 0 { for _, config := range configs { @@ -151,9 +151,33 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { } defer portProxy.Close() - ctx, cancel := context.WithCancel(context.Background()) + if !quiet { + feedback.Print(tr("Connected to %s! Press CTRL-C to exit.", portAddress)) + } + + ttyIn, ttyOut, err := feedback.InteractiveStreams() + if err != nil { + feedback.FatalError(err, feedback.ErrGeneric) + } + + ctx, cancel := cleanup.InterruptableContext(context.Background()) + if raw { + feedback.SetRawModeStdin() + defer func() { + feedback.RestoreModeStdin() + }() + + // In RAW mode CTRL-C is not converted into an Interrupt by + // the terminal, we must intercept ASCII 3 (CTRL-C) on our own... + ctrlCDetector := &charDetectorWriter{ + callback: cancel, + detectedChar: 3, // CTRL-C + } + ttyIn = io.TeeReader(ttyIn, ctrlCDetector) + } + go func() { - _, err := io.Copy(tty, portProxy) + _, err := io.Copy(ttyOut, portProxy) if err != nil && !errors.Is(err, io.EOF) { if !quiet { feedback.Print(tr("Port closed: %v", err)) @@ -162,7 +186,7 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { cancel() }() go func() { - _, err := io.Copy(portProxy, tty) + _, err := io.Copy(portProxy, ttyIn) if err != nil && !errors.Is(err, io.EOF) { if !quiet { feedback.Print(tr("Port closed: %v", err)) @@ -171,14 +195,22 @@ func runMonitorCmd(cmd *cobra.Command, args []string) { cancel() }() - if !quiet { - feedback.Print(tr("Connected to %s! Press CTRL-C to exit.", portAddress)) - } - // Wait for port closed <-ctx.Done() } +type charDetectorWriter struct { + callback func() + detectedChar byte +} + +func (cd *charDetectorWriter) Write(buf []byte) (int, error) { + if bytes.IndexByte(buf, cd.detectedChar) != -1 { + cd.callback() + } + return len(buf), nil +} + type detailsResult struct { Settings []*rpc.MonitorPortSettingDescriptor `json:"settings"` } diff --git a/internal/cli/sketch/archive.go b/internal/cli/sketch/archive.go index 9410bb0d0c6..e21221aa3e7 100644 --- a/internal/cli/sketch/archive.go +++ b/internal/cli/sketch/archive.go @@ -21,7 +21,6 @@ import ( "os" sk "github.com/arduino/arduino-cli/commands/sketch" - "github.com/arduino/arduino-cli/internal/cli/arguments" "github.com/arduino/arduino-cli/internal/cli/feedback" rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" @@ -61,7 +60,9 @@ func runArchiveCommand(args []string, includeBuildDir bool, overwrite bool) { sketchPath = paths.New(args[0]) } - arguments.WarnDeprecatedFiles(sketchPath) + if msg := sk.WarnDeprecatedFiles(sketchPath); msg != "" { + feedback.Warning(msg) + } archivePath := "" if len(args) == 2 { diff --git a/internal/cli/upload/upload.go b/internal/cli/upload/upload.go index 0f2a798d979..a36c9acbf15 100644 --- a/internal/cli/upload/upload.go +++ b/internal/cli/upload/upload.go @@ -23,9 +23,9 @@ import ( "strings" "github.com/arduino/arduino-cli/arduino" - "github.com/arduino/arduino-cli/arduino/cores/packagemanager" "github.com/arduino/arduino-cli/arduino/sketch" "github.com/arduino/arduino-cli/commands" + sk "github.com/arduino/arduino-cli/commands/sketch" "github.com/arduino/arduino-cli/commands/upload" "github.com/arduino/arduino-cli/i18n" "github.com/arduino/arduino-cli/internal/cli/arguments" @@ -86,8 +86,8 @@ func runUploadCommand(command *cobra.Command, args []string) { } sketchPath := arguments.InitSketchPath(path) - if importDir == "" && importFile == "" { - arguments.WarnDeprecatedFiles(sketchPath) + if msg := sk.WarnDeprecatedFiles(sketchPath); importDir == "" && importFile == "" && msg != "" { + feedback.Warning(msg) } sk, err := sketch.New(sketchPath) @@ -129,12 +129,8 @@ func runUploadCommand(command *cobra.Command, args []string) { panic(tr("Platform ID is not correct")) } - // FIXME: Here we must not access package manager... pme, release := commands.GetPackageManagerExplorer(&rpc.UploadRequest{Instance: inst}) - platform := pme.FindPlatform(&packagemanager.PlatformReference{ - Package: split[0], - PlatformArchitecture: split[1], - }) + platform := pme.FindPlatform(split[0], split[1]) release() msg += "\n"