Skip to content

Commit 73fec49

Browse files
cmagliersora
authored andcommitted
Working stdio streaming
1 parent e11a1d8 commit 73fec49

File tree

3 files changed

+75
-238
lines changed

3 files changed

+75
-238
lines changed

client_example/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ package main
1818
import (
1919
"context"
2020
"fmt"
21+
2122
rpc "github.com/arduino/arduino-cli/rpc/commands"
2223
dbg "github.com/arduino/arduino-cli/rpc/debug"
2324

2425
"io"
2526
"io/ioutil"
2627
"log"
2728
"os"
29+
2830
// "path"
2931
"path/filepath"
3032
"time"
@@ -104,8 +106,8 @@ func main() {
104106

105107
// When an operation is ongoing you can get its output
106108
if resp := compResp.GetData(); resp != nil {
107-
fmt.Printf("%s", resp)
108-
if string(resp) == " (gdb) " {
109+
fmt.Printf(">>%s<<", resp)
110+
if string(resp) == "(gdb) " {
109111
break
110112
}
111113
}

commands/daemon/debug.go

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
package daemon
1717

1818
import (
19-
"context"
2019
"fmt"
20+
2121
cmd "github.com/arduino/arduino-cli/commands/debug"
2222
dbg "github.com/arduino/arduino-cli/rpc/debug"
23-
"io"
2423
)
2524

2625
// DebugService implements the `Debug` service
@@ -44,98 +43,11 @@ func (s *DebugService) StreamingOpen(stream dbg.Debug_StreamingOpenServer) error
4443
}
4544

4645
// launch debug recipe attaching stdin and out to grpc streaming
47-
cmd, err := cmd.Debug(context.Background(), req)
48-
if err != nil {
49-
return (err)
50-
}
51-
in, err := cmd.StdinPipe()
46+
resp, err := cmd.Debug(stream.Context(), req, stream, feedStream(func(data []byte) {
47+
stream.Send(&dbg.StreamingOpenResp{Data: data})
48+
}))
5249
if err != nil {
5350
return (err)
5451
}
55-
defer in.Close()
56-
57-
out, err := cmd.StdoutPipe()
58-
if err != nil {
59-
return (err)
60-
}
61-
defer out.Close()
62-
63-
err = cmd.Start()
64-
if err != nil {
65-
fmt.Println("%v\n", err)
66-
return err
67-
}
68-
69-
// we'll use these channels to communicate with the goroutines
70-
// handling the stream and the target respectively
71-
streamClosed := make(chan error)
72-
targetClosed := make(chan error)
73-
74-
// now we can read the other commands and re-route to the Debug Client...
75-
go func() {
76-
for {
77-
command, err := stream.Recv()
78-
if err == io.EOF {
79-
// stream was closed
80-
streamClosed <- nil
81-
break
82-
}
83-
84-
if err != nil {
85-
// error reading from stream
86-
streamClosed <- err
87-
break
88-
}
89-
90-
if _, err := in.Write(command.GetData()); err != nil {
91-
// error writing to target
92-
targetClosed <- err
93-
break
94-
}
95-
}
96-
}()
97-
98-
// ...and read from the Debug and forward to the output stream
99-
go func() {
100-
buf := make([]byte, 8)
101-
for {
102-
n, err := out.Read(buf)
103-
if err != nil {
104-
// error reading from target
105-
targetClosed <- err
106-
break
107-
}
108-
109-
if n == 0 {
110-
// target was closed
111-
targetClosed <- nil
112-
break
113-
}
114-
115-
err = stream.Send(&dbg.StreamingOpenResp{
116-
Data: buf[:n],
117-
})
118-
if err != nil {
119-
// error sending to stream
120-
streamClosed <- err
121-
break
122-
}
123-
}
124-
}()
125-
126-
// let goroutines route messages from/to the Debug
127-
// until either the client closes the stream or the
128-
// Debug target is closed
129-
for {
130-
select {
131-
case err := <-streamClosed:
132-
fmt.Println("streamClosed")
133-
cmd.Process.Kill()
134-
cmd.Wait()
135-
return err
136-
case err := <-targetClosed:
137-
fmt.Println("targetClosed")
138-
return err
139-
}
140-
}
52+
return stream.Send(resp)
14153
}

commands/debug/debug.go

Lines changed: 66 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -18,164 +18,87 @@ package debug
1818
import (
1919
"context"
2020
"fmt"
21-
"github.com/arduino/arduino-cli/arduino/cores"
22-
"github.com/arduino/arduino-cli/arduino/sketches"
23-
"github.com/arduino/arduino-cli/commands"
21+
"io"
22+
2423
"github.com/arduino/arduino-cli/executils"
2524
dbg "github.com/arduino/arduino-cli/rpc/debug"
26-
"github.com/arduino/go-paths-helper"
27-
"github.com/arduino/go-properties-orderedmap"
28-
"github.com/sirupsen/logrus"
29-
"os"
30-
"os/exec"
31-
"path/filepath"
32-
"strings"
3325
)
3426

3527
// Debug FIXMEDOC
36-
func Debug(ctx context.Context, req *dbg.DebugReq, in func(data []bytes), out func(data []bytes)) (*exec.Cmd, error) {
37-
//logrus.Tracef("Debug %s on %s started", req.GetSketchPath(), req.GetFqbn())
38-
39-
//// TODO: make a generic function to extract sketch from request
40-
// and remove duplication in commands/compile.go
41-
if req.GetSketchPath() == "" {
42-
return fmt.Errorf("missing sketchPath")
43-
}
44-
sketchPath := paths.New(req.GetSketchPath())
45-
sketch, err := sketches.NewSketchFromPath(sketchPath)
28+
func Debug(ctx context.Context, req *dbg.DebugReq, inStream dbg.Debug_StreamingOpenServer, out io.Writer) (*dbg.StreamingOpenResp, error) {
29+
cmdArgs := []string{"gdb"}
30+
// Run Tool
31+
cmd, err := executils.Command(cmdArgs)
4632
if err != nil {
47-
return fmt.Errorf("opening sketch: %s", err)
48-
}
49-
50-
// FIXME: make a specification on how a port is specified via command line
51-
port := req.GetPort()
52-
if port == "" {
53-
return fmt.Errorf("no upload port provided")
33+
return nil, fmt.Errorf("cannot execute upload tool: %s", err)
5434
}
5535

56-
fqbnIn := req.GetFqbn()
57-
if fqbnIn == "" && sketch != nil && sketch.Metadata != nil {
58-
fqbnIn = sketch.Metadata.CPU.Fqbn
59-
}
60-
if fqbnIn == "" {
61-
return fmt.Errorf("no Fully Qualified Board Name provided")
62-
}
63-
fqbn, err := cores.ParseFQBN(fqbnIn)
36+
in, err := cmd.StdinPipe()
6437
if err != nil {
65-
return fmt.Errorf("incorrect FQBN: %s", err)
38+
fmt.Println("%v\n", err)
39+
return &dbg.StreamingOpenResp{}, nil // TODO: send error in response
6640
}
41+
defer in.Close()
6742

68-
pm := commands.GetPackageManager(req.GetInstance().GetId())
43+
cmd.Stdout = out
6944

70-
// Find target board and board properties
71-
_, _, board, boardProperties, _, err := pm.ResolveFQBN(fqbn)
45+
err = cmd.Start()
7246
if err != nil {
73-
return fmt.Errorf("incorrect FQBN: %s", err)
74-
}
75-
76-
// Load programmer tool
77-
uploadToolPattern, have := boardProperties.GetOk("upload.tool")
78-
if !have || uploadToolPattern == "" {
79-
return fmt.Errorf("cannot get programmer tool: undefined 'upload.tool' property")
80-
}
81-
82-
var referencedPlatformRelease *cores.PlatformRelease
83-
if split := strings.Split(uploadToolPattern, ":"); len(split) > 2 {
84-
return fmt.Errorf("invalid 'upload.tool' property: %s", uploadToolPattern)
85-
} else if len(split) == 2 {
86-
referencedPackageName := split[0]
87-
uploadToolPattern = split[1]
88-
architecture := board.PlatformRelease.Platform.Architecture
89-
90-
if referencedPackage := pm.Packages[referencedPackageName]; referencedPackage == nil {
91-
return fmt.Errorf("required platform %s:%s not installed", referencedPackageName, architecture)
92-
} else if referencedPlatform := referencedPackage.Platforms[architecture]; referencedPlatform == nil {
93-
return fmt.Errorf("required platform %s:%s not installed", referencedPackageName, architecture)
94-
} else {
95-
referencedPlatformRelease = pm.GetInstalledPlatformRelease(referencedPlatform)
96-
}
97-
}
98-
99-
// Build configuration for upload
100-
uploadProperties := properties.NewMap()
101-
if referencedPlatformRelease != nil {
102-
uploadProperties.Merge(referencedPlatformRelease.Properties)
103-
}
104-
uploadProperties.Merge(board.PlatformRelease.Properties)
105-
uploadProperties.Merge(board.PlatformRelease.RuntimeProperties())
106-
uploadProperties.Merge(boardProperties)
107-
108-
uploadToolProperties := uploadProperties.SubTree("tools." + uploadToolPattern)
109-
uploadProperties.Merge(uploadToolProperties)
110-
111-
if requiredTools, err := pm.FindToolsRequiredForBoard(board); err == nil {
112-
for _, requiredTool := range requiredTools {
113-
logrus.WithField("tool", requiredTool).Info("Tool required for upload")
114-
uploadProperties.Merge(requiredTool.RuntimeProperties())
47+
fmt.Println("%v\n", err)
48+
return &dbg.StreamingOpenResp{}, nil // TODO: send error in response
49+
}
50+
51+
// we'll use these channels to communicate with the goroutines
52+
// handling the stream and the target respectively
53+
streamClosed := make(chan error)
54+
targetClosed := make(chan error)
55+
defer close(streamClosed)
56+
defer close(targetClosed)
57+
58+
// now we can read the other commands and re-route to the Debug Client...
59+
go func() {
60+
for {
61+
command, err := inStream.Recv()
62+
if err == io.EOF {
63+
// stream was closed
64+
streamClosed <- nil
65+
break
66+
}
67+
68+
if err != nil {
69+
// error reading from stream
70+
streamClosed <- err
71+
break
72+
}
73+
74+
if _, err := in.Write(command.GetData()); err != nil {
75+
// error writing to target
76+
targetClosed <- err
77+
break
78+
}
11579
}
116-
}
117-
118-
// Set properties for verbose upload
119-
Verbose := req.GetVerbose()
120-
if Verbose {
121-
if v, ok := uploadProperties.GetOk("upload.params.verbose"); ok {
122-
uploadProperties.Set("upload.verbose", v)
123-
}
124-
} else {
125-
if v, ok := uploadProperties.GetOk("upload.params.quiet"); ok {
126-
uploadProperties.Set("upload.verbose", v)
127-
}
128-
}
129-
130-
// Set path to compiled binary
131-
// Make the filename without the FQBN configs part
132-
fqbn.Configs = properties.NewMap()
133-
fqbnSuffix := strings.Replace(fqbn.String(), ":", ".", -1)
134-
135-
var importPath *paths.Path
136-
var importFile string
137-
if req.GetImportFile() == "" {
138-
importPath = sketch.FullPath
139-
importFile = sketch.Name + "." + fqbnSuffix
140-
} else {
141-
importPath = paths.New(req.GetImportFile()).Parent()
142-
importFile = paths.New(req.GetImportFile()).Base()
143-
}
144-
145-
outputTmpFile, ok := uploadProperties.GetOk("recipe.output.tmp_file")
146-
outputTmpFile = uploadProperties.ExpandPropsInString(outputTmpFile)
147-
if !ok {
148-
return fmt.Errorf("property 'recipe.output.tmp_file' not defined")
149-
}
150-
ext := filepath.Ext(outputTmpFile)
151-
if strings.HasSuffix(importFile, ext) {
152-
importFile = importFile[:len(importFile)-len(ext)]
153-
}
154-
155-
uploadProperties.SetPath("build.path", importPath)
156-
uploadProperties.Set("build.project_name", importFile)
157-
uploadFile := importPath.Join(importFile + ext)
158-
if _, err := uploadFile.Stat(); err != nil {
159-
if os.IsNotExist(err) {
160-
return fmt.Errorf("compiled sketch %s not found", uploadFile.String())
80+
}()
81+
82+
// let goroutines route messages from/to the Debug
83+
// until either the client closes the stream or the
84+
// Debug target is closed
85+
for {
86+
select {
87+
case <-ctx.Done():
88+
cmd.Process.Kill()
89+
cmd.Wait()
90+
case err := <-streamClosed:
91+
fmt.Println("streamClosed")
92+
cmd.Process.Kill()
93+
cmd.Wait()
94+
return &dbg.StreamingOpenResp{}, err // TODO: send error in response
95+
case err := <-targetClosed:
96+
fmt.Println("targetClosed")
97+
cmd.Process.Kill()
98+
cmd.Wait()
99+
return &dbg.StreamingOpenResp{}, err // TODO: send error in response
161100
}
162-
return fmt.Errorf("cannot open sketch: %s", err)
163-
}
164-
165-
// Build recipe for upload
166-
recipe := uploadProperties.Get("upload.pattern")
167-
cmdLine := uploadProperties.ExpandPropsInString(recipe)
168-
cmdArgs, err := properties.SplitQuotedString(cmdLine, `"'`, false)
169-
if err != nil {
170-
return fmt.Errorf("invalid recipe '%s': %s", recipe, err)
171-
}
172-
173-
cmdArgs := []string{"gdb"}
174-
// Run Tool
175-
cmd, err := executils.Command(cmdArgs)
176-
if err != nil {
177-
return nil, fmt.Errorf("cannot execute upload tool: %s", err)
178101
}
179102

180-
return cmd, nil
103+
return &dbg.StreamingOpenResp{}, nil
181104
}

0 commit comments

Comments
 (0)