Skip to content

Commit 3906685

Browse files
authored
Add first implementation of the tool (#1)
* `cobra init` * update .gitignore * add compile command * make fqbn flag required and cleanup * [WIP] add resultJson generation * use regex to get objectfile path ,added TODOs with notes * add check on compile success * remove regexp and implement some tests (they are failing) * `ParseObjFilePath` should be finished, tests are passing * `.o` file and `result.json` are saved in `build dir`, refactor some var names * don't use parsing from compilerOut but json field `build_path` instead the tool now supports multiple `.o` * simplify the result.json file aggregating `CoreInfo` & `LibInfo` structs * add packager to `coreInfo`, cleanup & fixes * fix path with spaces not being recognized as a valid platform isolate `parsePlatformLine` function * add README.md * remove horrible parsing of the the `CompilerOut` to get the platform This is possible thanks to arduino/arduino-cli#1608 It's mandatory to use a version of the cli that has that change > 0.20.2 * apply suggestions by @silvanocerza
1 parent 9b063b0 commit 3906685

File tree

7 files changed

+1022
-0
lines changed

7 files changed

+1022
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cslt-tool
2+
.vscode
3+
build/

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# cslt-tool
2+
3+
cslt-tool is a convenient wrapper of [arduino-cli](https://github.com/arduino/arduino-cli), it compiles Arduino sketches outputting object files and a json file in a `build/` directory
4+
The json contains information regarding libraries and core to use in order to build the sketch. The result is achieved by parsing the verbose output of `arduino-cli`.
5+
6+
## Requisites
7+
In order to run this tool you have to install first the [arduino-cli](https://github.com/arduino/arduino-cli) and have `arduino-cli` binary in your path, otherwise `cslt-tool` won't work.
8+
Please use a version of the cli that has [this](https://github.com/arduino/arduino-cli/pull/1608) change, version > 0.20.2
9+
10+
## Build it
11+
In order to build it just use `go build`
12+
13+
## Usage
14+
`./cslt-tool compile -b <fqbn> <sketch_path>`
15+
16+
This is an example execution:
17+
``` bash
18+
$ ./cslt-tool compile -b arduino:samd:mkrwan1310 /home/umberto/getdeveui
19+
INFO[0001] arduino-cli version: git-snapshot
20+
INFO[0001] running: arduino-cli compile -b arduino:samd:mkrwan1310 /home/umberto/getdeveui -v --format json
21+
INFO[0002] copied file to /home/umberto/Nextcloud/8tb/Lavoro/cslt-tool/build/getdeveui.ino.cpp.o
22+
INFO[0002] created new file in: /home/umberto/Nextcloud/8tb/Lavoro/cslt-tool/build/result.json
23+
```
24+
The structure of the `build` forder is the following:
25+
```
26+
build/
27+
├── getdeveui.ino.cpp.o
28+
└── result.json
29+
```
30+
And the content of `build/result.json` is:
31+
```json
32+
{
33+
"coreInfo": {
34+
"id": "arduino:samd",
35+
"version": "1.8.12"
36+
},
37+
"libsInfo": [
38+
{
39+
"name": "MKRWAN",
40+
"version": "1.1.0"
41+
}
42+
]
43+
}
44+
```

cmd/compile.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
3+
4+
*/
5+
package cmd
6+
7+
import (
8+
"encoding/json"
9+
"os"
10+
"os/exec"
11+
12+
"github.com/arduino/go-paths-helper"
13+
"github.com/sirupsen/logrus"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
var fqbn string
18+
19+
// compileOutput represents the json returned by the arduino-cli compile command
20+
type CompileOutput struct {
21+
CompilerErr string `json:"compiler_err"`
22+
BuilderResult *BuilderResult `json:"builder_result"`
23+
Success bool `json:"success"`
24+
}
25+
26+
type BuilderResult struct {
27+
BuildPath string `json:"build_path"`
28+
UsedLibraries []*UsedLibrary `json:"used_libraries"`
29+
BuildPlatform *BuildPlatform `json:"build_platform"`
30+
}
31+
32+
// UsedLibrary contains information regarding the library used during the compile process
33+
type UsedLibrary struct {
34+
Name string `json:"name"`
35+
Version string `json:"version"`
36+
}
37+
38+
// BuildPlatform contains information regarding the platform used during the compile process
39+
type BuildPlatform struct {
40+
Id string `json:"id"`
41+
Version string `json:"version"`
42+
}
43+
44+
// ResultJson contains information regarding the core and libraries used during the compile process
45+
type ResultJson struct {
46+
CoreInfo *BuildPlatform `json:"coreInfo"`
47+
LibsInfo []*UsedLibrary `json:"libsInfo"`
48+
}
49+
50+
// compileCmd represents the compile command
51+
var compileCmd = &cobra.Command{
52+
Use: "compile",
53+
Short: "Compiles Arduino sketches.",
54+
Long: `Compiles Arduino sketches outputting an object file and a json file in a build directory
55+
The json contains information regarding libraries and core to use in order to build the sketch`,
56+
Example: os.Args[0] + `compile -b arduino:avr:uno /home/umberto/Arduino/Blink`,
57+
Args: cobra.ExactArgs(1), // the path of the sketch to build
58+
Run: compileSketch,
59+
}
60+
61+
func init() {
62+
rootCmd.AddCommand(compileCmd)
63+
compileCmd.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:avr:uno")
64+
compileCmd.MarkFlagRequired("fqbn")
65+
}
66+
67+
func compileSketch(cmd *cobra.Command, args []string) {
68+
logrus.Debug("compile called")
69+
70+
// let's check the arduino-cli version
71+
cmdOutput, err := exec.Command("arduino-cli", "version", "--format", "json").Output()
72+
if err != nil {
73+
logrus.Warn("Before running this tool be sure to have arduino-cli installed in your $PATH")
74+
logrus.Fatal(err)
75+
}
76+
var unmarshalledOutput map[string]interface{}
77+
json.Unmarshal(cmdOutput, &unmarshalledOutput)
78+
logrus.Infof("arduino-cli version: %s", unmarshalledOutput["VersionString"])
79+
80+
// let's call arduino-cli compile and parse the verbose output
81+
logrus.Infof("running: arduino-cli compile -b %s %s -v --format json", fqbn, args[0])
82+
cmdOutput, err = exec.Command("arduino-cli", "compile", "-b", fqbn, args[0], "-v", "--format", "json").Output()
83+
if err != nil {
84+
logrus.Fatal(err)
85+
}
86+
objFilesPaths, returnJson := parseOutput(cmdOutput)
87+
88+
workingDir, err := paths.Getwd()
89+
if err != nil {
90+
logrus.Fatal(err)
91+
}
92+
buildDir := workingDir.Join("build")
93+
if !buildDir.Exist() {
94+
if err = buildDir.Mkdir(); err != nil {
95+
logrus.Fatal(err)
96+
}
97+
}
98+
99+
// Copy the object files from the `<tempdir>/arduino-sketch_stuff/sketch` folder
100+
for _, objFilePath := range objFilesPaths {
101+
destObjFilePath := buildDir.Join(objFilePath.Base())
102+
if err = objFilePath.CopyTo(destObjFilePath); err != nil {
103+
logrus.Errorf("error copying object file: %s", err)
104+
} else {
105+
logrus.Infof("copied file to %s", destObjFilePath)
106+
}
107+
}
108+
109+
// save the result.json in the build dir
110+
jsonFilePath := buildDir.Join("result.json")
111+
if jsonContents, err := json.MarshalIndent(returnJson, "", " "); err != nil {
112+
logrus.Errorf("error serializing json: %s", err)
113+
} else if err := jsonFilePath.WriteFile(jsonContents); err != nil {
114+
logrus.Errorf("error writing result.json: %s", err)
115+
} else {
116+
logrus.Infof("created new file in: %s", jsonFilePath)
117+
}
118+
}
119+
120+
// parseOutput function takes cmdOutToParse as argument,
121+
// cmdOutToParse is the json output captured from the command run
122+
// the function extracts and returns the paths of the .o files
123+
// (generated during the compile phase) and a ReturnJson object
124+
func parseOutput(cmdOutToParse []byte) ([]*paths.Path, *ResultJson) {
125+
var compileOutput CompileOutput
126+
err := json.Unmarshal(cmdOutToParse, &compileOutput)
127+
if err != nil {
128+
logrus.Fatal(err)
129+
} else if !compileOutput.Success {
130+
logrus.Fatalf("sketch compile was not successful: %s", compileOutput.CompilerErr)
131+
}
132+
133+
// this dir contains all the obj files we need (the sketch related ones and not the core or libs)
134+
sketchDir := paths.New(compileOutput.BuilderResult.BuildPath).Join("sketch")
135+
sketchFilesPaths, err := sketchDir.ReadDir()
136+
if err != nil {
137+
logrus.Fatal(err)
138+
} else if len(sketchFilesPaths) == 0 {
139+
logrus.Fatalf("empty directory: %s", sketchDir)
140+
}
141+
sketchFilesPaths.FilterSuffix(".o")
142+
143+
returnJson := ResultJson{
144+
CoreInfo: compileOutput.BuilderResult.BuildPlatform,
145+
LibsInfo: compileOutput.BuilderResult.UsedLibraries,
146+
}
147+
148+
return sketchFilesPaths, &returnJson
149+
}

cmd/root.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
3+
4+
*/
5+
package cmd
6+
7+
import (
8+
"os"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// rootCmd represents the base command when called without any subcommands
14+
var rootCmd = &cobra.Command{
15+
Use: "cslt-tool",
16+
Short: "cslt-tool is a command-line tool that uses the Arduino CLI to generate objectfiles and a json file with info regarding core and libraries used",
17+
}
18+
19+
// Execute adds all child commands to the root command and sets flags appropriately.
20+
// This is called by main.main(). It only needs to happen once to the rootCmd.
21+
func Execute() {
22+
err := rootCmd.Execute()
23+
if err != nil {
24+
os.Exit(1)
25+
}
26+
}

go.mod

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module cslt-tool
2+
3+
go 1.17
4+
5+
require (
6+
github.com/arduino/go-paths-helper v1.6.1
7+
github.com/spf13/cobra v1.3.0
8+
)
9+
10+
require (
11+
github.com/pkg/errors v0.9.1 // indirect
12+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
13+
)
14+
15+
require (
16+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
17+
github.com/sirupsen/logrus v1.8.1
18+
github.com/spf13/pflag v1.0.5 // indirect
19+
)

0 commit comments

Comments
 (0)