Skip to content

Commit 6706ae6

Browse files
committed
Create a new logger for the web
This logger saves all the output in a variable to deliver everything at onc Signed-off-by: Matteo Suppo <matteo.suppo@gmail.com> Create a api endpoint for compiling a sketch remotely The api accepts a json body with the sketch and the fqbn It returns a json object with the base64 encoded .elf and .hex files as well as the output of the compilation Signed-off-by: Matteo Suppo <matteo.suppo@gmail.com> Return the correct files in the compile api It only shows the existing binaries, ignoring the ones that don't exist Signed-off-by: Matteo Suppo <matteo.suppo@gmail.com> Add documentation regarding the web api Signed-off-by: Matteo Suppo <matteo.suppo@gmail.com> Use a build flag to choose wether to compile the api or not Compile with -flags 'api' will add 6MB to the binary size, which could be irritating if you don't need the api. Signed-off-by: Matteo Suppo <matteo.suppo@gmail.com>
1 parent f1e8c85 commit 6706ae6

File tree

7 files changed

+185
-15
lines changed

7 files changed

+185
-15
lines changed

README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This tool generates function prototypes and gathers library paths, providing `gc
99

1010
### Usage
1111

12-
* `-compile` or `-dump-prefs` or `-preprocess`: Optional. If omitted, defaults to `-compile`. `-dump-prefs` will just print all build preferences used, `-compile` will use those preferences to run the actual compiler, `-preprocess` will only print preprocessed code to stdout.
12+
* `-compile` or `-dump-prefs` or `-preprocess` or `-listen:3000`: Optional. If omitted, defaults to `-compile`. `-dump-prefs` will just print all build preferences used, `-compile` will use those preferences to run the actual compiler, `-preprocess` will only print preprocessed code to stdout, `-listen:3000` opens a web server on the specified port. See the section web api below.
1313

1414
* `-hardware`: Mandatory. Folder containing Arduino platforms. An example is the `hardware` folder shipped with the Arduino IDE, or the `packages` folder created by Arduino Boards Manager. Can be specified multiple times. If conflicting hardware definitions are specified, the last one wins.
1515

@@ -41,7 +41,7 @@ This tool generates function prototypes and gathers library paths, providing `gc
4141

4242
* `-vid-pid`: when specified, VID/PID specific build properties are used, if boards supports them.
4343

44-
Final mandatory parameter is the sketch to compile (of course).
44+
Final mandatory parameter is the sketch to compile.
4545

4646
### What is and how to use build.options.json file
4747

@@ -72,6 +72,60 @@ go get golang.org/x/tools/cmd/vet
7272
go build
7373
```
7474

75+
### Web Api
76+
77+
You can choose to compile the builder with the -api option:
78+
79+
```bash
80+
go build -tags 'api'
81+
```
82+
83+
Then if you launch it with the option `listen=3000` the builder will open a web server listening for requests to the /compile endpoint.
84+
85+
Here's how to request the compilation of the simplest sketch possible:
86+
87+
```
88+
POST /compile HTTP/1.1
89+
Host: localhost:3000
90+
Content-Type: application/json
91+
92+
{
93+
"fqbn": "arduino:avr:uno",
94+
"sketch": {
95+
"main_file": {
96+
"name": "sketch.ino",
97+
"source": "void setup() {\n // initialize digital pin 13 as an output.\n pinMode(13, OUTPUT);\n}\n// the loop function runs over and over again forever\nvoid loop() {\n digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)\n delay(1000); // wait for a second\n digitalWrite(13, LOW); // turn the LED off by making the voltage LOW\n delay(1000); // wait for a second\n}"
98+
}
99+
}
100+
}
101+
```
102+
103+
And here's the response (the actual response will be much bigger, but the structure is the same):
104+
105+
```
106+
{
107+
"binaries": {
108+
"elf": "f0VMRgEBAQAAAAAAAAAAAAIAUwABAAAAAAAAADQAAACILAAAhQAAAA...",
109+
"hex": "OjEwMDAwMDAwMEM5NDVDMDAwQzk0NkUwMDBDOTQ2RTAwMEM5NDZFMD..."
110+
},
111+
"out": [
112+
{
113+
"Level": "warn",
114+
"Message": "Board Intel:i586:izmir_fd doesn't define a 'build.board' preference. Auto-set to: I586_IZMIR_FD"
115+
},
116+
{
117+
"Level": "info",
118+
"Message": "\"/opt/tools/avr-gcc/4.8.1-arduino5/bin/avr-g++\" -c -g -Os -w -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10608 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR \"-I/opt/cores/arduino/avr/cores/arduino\" \"-I/opt/cores/arduino/avr/variants/standard\" \"/tmp/build/sketch/sketch.ino.cpp\" -o \"/dev/null\""
119+
},
120+
{
121+
"Level": "info",
122+
"Message": "\"/opt/tools/avr-gcc/4.8.1-arduino5/bin/avr-g++\" -c -g -Os -w -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10608 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR \"-I/opt/cores/arduino/avr/cores/arduino\" \"-I/opt/cores/arduino/avr/variants/standard\" \"/tmp/build/sketch/sketch.ino.cpp\" -o \"/dev/null\""
123+
},
124+
...
125+
]
126+
}
127+
```
128+
75129
### TDD
76130

77131
In order to run the tests, type:

api.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// +build api
2+
3+
package main
4+
5+
import (
6+
"io/ioutil"
7+
"path/filepath"
8+
9+
"arduino.cc/builder"
10+
"arduino.cc/builder/constants"
11+
"arduino.cc/builder/i18n"
12+
"arduino.cc/builder/types"
13+
"github.com/gin-gonic/gin"
14+
)
15+
16+
func listen(context map[string]interface{}) {
17+
router := gin.Default()
18+
19+
router.POST("/compile", func(c *gin.Context) {
20+
logger := i18n.WebLogger{}
21+
logger.Init()
22+
context[constants.CTX_LOGGER] = &logger
23+
24+
var json struct {
25+
Sketch types.Sketch `json:"sketch"`
26+
Fqbn string `json:"fqbn"`
27+
}
28+
29+
err := c.BindJSON(&json)
30+
31+
if err != nil {
32+
c.JSON(400, gin.H{"error": "Malformed JSON", "message": err.Error()})
33+
return
34+
}
35+
36+
if json.Fqbn == "" {
37+
c.JSON(400, gin.H{"error": "Malformed JSON", "message": "Missing fqbn property"})
38+
return
39+
}
40+
41+
context[constants.CTX_SKETCH] = &json.Sketch
42+
context[constants.CTX_FQBN] = json.Fqbn
43+
44+
err = builder.RunBuilder(context)
45+
46+
if err != nil {
47+
c.JSON(500, gin.H{"out": logger.Out(), "error": err.Error()})
48+
return
49+
}
50+
51+
binaries := struct {
52+
Elf []byte `json:"elf,omitempty"`
53+
Bin []byte `json:"bin,omitempty"`
54+
Hex []byte `json:"hex,omitempty"`
55+
}{}
56+
57+
elfPath := filepath.Join(*buildPathFlag, json.Sketch.MainFile.Name+".elf")
58+
binaries.Elf, _ = ioutil.ReadFile(elfPath)
59+
60+
binPath := filepath.Join(*buildPathFlag, json.Sketch.MainFile.Name+".bin")
61+
binaries.Bin, _ = ioutil.ReadFile(binPath)
62+
63+
hexPath := filepath.Join(*buildPathFlag, json.Sketch.MainFile.Name+".hex")
64+
binaries.Hex, _ = ioutil.ReadFile(hexPath)
65+
66+
c.JSON(200, gin.H{"out": logger.Out(), "binaries": binaries})
67+
})
68+
69+
router.Run(":" + *listenFlag)
70+
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const FLAG_LOGGER_HUMAN = "human"
7676
const FLAG_LOGGER_MACHINE = "machine"
7777
const FLAG_VERSION = "version"
7878
const FLAG_VID_PID = "vid-pid"
79+
const FLAG_LISTEN = "listen"
7980

8081
type slice []string
8182

@@ -119,6 +120,7 @@ var warningsLevelFlag *string
119120
var loggerFlag *string
120121
var versionFlag *bool
121122
var vidPidFlag *string
123+
var listenFlag *string
122124

123125
func init() {
124126
compileFlag = flag.Bool(FLAG_ACTION_COMPILE, false, "compiles the given sketch")
@@ -141,6 +143,7 @@ func init() {
141143
loggerFlag = flag.String(FLAG_LOGGER, FLAG_LOGGER_HUMAN, "Sets type of logger. Available values are '"+FLAG_LOGGER_HUMAN+"', '"+FLAG_LOGGER_MACHINE+"'")
142144
versionFlag = flag.Bool(FLAG_VERSION, false, "prints version and exits")
143145
vidPidFlag = flag.String(FLAG_VID_PID, "", "specify to use vid/pid specific build properties, as defined in boards.txt")
146+
listenFlag = flag.String(FLAG_LISTEN, "", "specify to listen for web request at a certain host")
144147
}
145148

146149
func main() {
@@ -310,6 +313,8 @@ func main() {
310313
err = builder.RunParseHardwareAndDumpBuildProperties(context)
311314
} else if *preprocessFlag {
312315
err = builder.RunPreprocess(context)
316+
} else if *listenFlag != "" {
317+
listen(context)
313318
} else {
314319
if flag.NArg() == 0 {
315320
fmt.Fprintln(os.Stderr, "Last parameter must be the sketch to compile")

not_api.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// +build !api
2+
3+
package main
4+
5+
import "fmt"
6+
7+
func listen(context map[string]interface{}) {
8+
fmt.Println("To use this feature you must compile the builder with the -api flag")
9+
}

src/arduino.cc/builder/builder_utils/utils.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@
3030
package builder_utils
3131

3232
import (
33-
"arduino.cc/builder/constants"
34-
"arduino.cc/builder/i18n"
35-
"arduino.cc/builder/props"
36-
"arduino.cc/builder/utils"
3733
"bytes"
38-
"fmt"
3934
"os"
4035
"os/exec"
4136
"path/filepath"
4237
"strings"
38+
39+
"arduino.cc/builder/constants"
40+
"arduino.cc/builder/i18n"
41+
"arduino.cc/builder/props"
42+
"arduino.cc/builder/utils"
4343
)
4444

4545
func CompileFilesRecursive(objectFiles []string, sourcePath string, buildPath string, buildProperties props.PropertiesMap, includes []string, verbose bool, warningsLevel string, logger i18n.Logger) ([]string, error) {
@@ -321,7 +321,7 @@ func PrepareCommandForRecipe(properties props.PropertiesMap, recipe string, remo
321321
}
322322

323323
if echoCommandLine {
324-
fmt.Println(commandLine)
324+
logger.Println("info", commandLine)
325325
}
326326

327327
return command, nil

src/arduino.cc/builder/i18n/i18n.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,37 @@ func (s MachineLogger) Name() string {
9898
return "machine"
9999
}
100100

101+
type message struct {
102+
Level string
103+
Message string
104+
}
105+
106+
// I tried keeping this inside the WebLogger but it didn't work
107+
var out = make([]message, 0)
108+
109+
type WebLogger struct {
110+
}
111+
112+
func (s WebLogger) Init() {
113+
out = make([]message, 0)
114+
}
115+
116+
func (s WebLogger) Out() []message {
117+
return out
118+
}
119+
120+
func (s WebLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
121+
out = append(out, message{Level: level, Message: Format(format, a...)})
122+
}
123+
124+
func (s WebLogger) Println(level string, format string, a ...interface{}) {
125+
out = append(out, message{Level: level, Message: Format(format, a...)})
126+
}
127+
128+
func (s WebLogger) Name() string {
129+
return "web"
130+
}
131+
101132
func FromJavaToGoSyntax(s string) string {
102133
submatches := PLACEHOLDER.FindAllStringSubmatch(s, -1)
103134
for _, submatch := range submatches {

src/arduino.cc/builder/types/types.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@
3030
package types
3131

3232
import (
33-
"arduino.cc/builder/constants"
34-
"arduino.cc/builder/props"
3533
"path/filepath"
3634
"strconv"
35+
36+
"arduino.cc/builder/constants"
37+
"arduino.cc/builder/props"
3738
)
3839

3940
type SketchFile struct {
40-
Name string
41-
Source string
41+
Name string `json:"name"`
42+
Source string `json:"source"`
4243
}
4344

4445
type SketchFileSortByName []SketchFile
@@ -56,9 +57,9 @@ func (s SketchFileSortByName) Less(i, j int) bool {
5657
}
5758

5859
type Sketch struct {
59-
MainFile SketchFile
60-
OtherSketchFiles []SketchFile
61-
AdditionalFiles []SketchFile
60+
MainFile SketchFile `json:"main_file"`
61+
OtherSketchFiles []SketchFile `json:"other_sketch_files"`
62+
AdditionalFiles []SketchFile `json:"additional_files"`
6263
}
6364

6465
type Packages struct {

0 commit comments

Comments
 (0)