Skip to content

Commit f954d7a

Browse files
authored
Implemented json output in 'compile' command (#1065)
* Added Compile json output * Factored out methods to convert Libraries into their rpc counterpart * Give some more info in compiler json output * Added simple smoke-test for compile --format json * Output in json even in case of error * Added binary size to compile json output * lib list: always output rows in table output, even if the "library.Release" field is not set
1 parent e8889eb commit f954d7a

File tree

11 files changed

+390
-153
lines changed

11 files changed

+390
-153
lines changed

Diff for: arduino/libraries/libraries.go

+44
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020

2121
"github.com/arduino/arduino-cli/arduino/cores"
22+
rpc "github.com/arduino/arduino-cli/rpc/commands"
2223
paths "github.com/arduino/go-paths-helper"
2324
properties "github.com/arduino/go-properties-orderedmap"
2425
semver "go.bug.st/relaxed-semver"
@@ -85,6 +86,49 @@ func (library *Library) String() string {
8586
return library.Name + "@" + library.Version.String()
8687
}
8788

89+
// ToRPCLibrary converts this library into an rpc.Library
90+
func (library *Library) ToRPCLibrary() *rpc.Library {
91+
pathOrEmpty := func(p *paths.Path) string {
92+
if p == nil {
93+
return ""
94+
}
95+
return p.String()
96+
}
97+
platformOrEmpty := func(p *cores.PlatformRelease) string {
98+
if p == nil {
99+
return ""
100+
}
101+
return p.String()
102+
}
103+
return &rpc.Library{
104+
Name: library.Name,
105+
Author: library.Author,
106+
Maintainer: library.Maintainer,
107+
Sentence: library.Sentence,
108+
Paragraph: library.Paragraph,
109+
Website: library.Website,
110+
Category: library.Category,
111+
Architectures: library.Architectures,
112+
Types: library.Types,
113+
InstallDir: pathOrEmpty(library.InstallDir),
114+
SourceDir: pathOrEmpty(library.SourceDir),
115+
UtilityDir: pathOrEmpty(library.UtilityDir),
116+
Location: library.Location.ToRPCLibraryLocation(),
117+
ContainerPlatform: platformOrEmpty(library.ContainerPlatform),
118+
Layout: library.Layout.ToRPCLibraryLayout(),
119+
RealName: library.RealName,
120+
DotALinkage: library.DotALinkage,
121+
Precompiled: library.Precompiled,
122+
LdFlags: library.LDflags,
123+
IsLegacy: library.IsLegacy,
124+
Version: library.Version.String(),
125+
License: library.License,
126+
Examples: library.Examples.AsStrings(),
127+
ProvidesIncludes: library.DeclaredHeaders(),
128+
CompatibleWith: library.CompatibleWith,
129+
}
130+
}
131+
88132
// SupportsAnyArchitectureIn returns true if any of the following is true:
89133
// - the library supports at least one of the given architectures
90134
// - the library is architecture independent

Diff for: arduino/libraries/librariesindex/index.go

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"github.com/arduino/arduino-cli/arduino/libraries"
2222
"github.com/arduino/arduino-cli/arduino/resources"
23+
rpc "github.com/arduino/arduino-cli/rpc/commands"
2324
semver "go.bug.st/relaxed-semver"
2425
)
2526

@@ -58,6 +59,21 @@ type Release struct {
5859
Library *Library `json:"-"`
5960
}
6061

62+
// ToRPCLibraryRelease transform this Release into a rpc.LibraryRelease
63+
func (r *Release) ToRPCLibraryRelease() *rpc.LibraryRelease {
64+
return &rpc.LibraryRelease{
65+
Author: r.Author,
66+
Version: r.Version.String(),
67+
Maintainer: r.Maintainer,
68+
Sentence: r.Sentence,
69+
Paragraph: r.Paragraph,
70+
Website: r.Website,
71+
Category: r.Category,
72+
Architectures: r.Architectures,
73+
Types: r.Types,
74+
}
75+
}
76+
6177
// GetName returns the name of this library.
6278
func (r *Release) GetName() string {
6379
return r.Library.Name

Diff for: cli/compile/compile.go

+51-10
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
package compile
1717

1818
import (
19+
"bytes"
1920
"context"
2021
"os"
2122

2223
"github.com/arduino/arduino-cli/cli/feedback"
24+
"github.com/arduino/arduino-cli/cli/output"
2325
"github.com/arduino/arduino-cli/configuration"
2426

2527
"github.com/arduino/arduino-cli/cli/errorcodes"
@@ -124,7 +126,7 @@ func run(cmd *cobra.Command, args []string) {
124126
// the config file and the env vars.
125127
exportBinaries = configuration.Settings.GetBool("sketch.always_export_binaries")
126128

127-
_, err = compile.Compile(context.Background(), &rpc.CompileReq{
129+
compileReq := &rpc.CompileReq{
128130
Instance: inst,
129131
Fqbn: fqbn,
130132
SketchPath: sketchPath.String(),
@@ -142,15 +144,19 @@ func run(cmd *cobra.Command, args []string) {
142144
OptimizeForDebug: optimizeForDebug,
143145
Clean: clean,
144146
ExportBinaries: exportBinaries,
145-
}, os.Stdout, os.Stderr, configuration.Settings.GetString("logging.level") == "debug")
146-
147-
if err != nil {
148-
feedback.Errorf("Error during build: %v", err)
149-
os.Exit(errorcodes.ErrGeneric)
147+
}
148+
compileOut := new(bytes.Buffer)
149+
compileErr := new(bytes.Buffer)
150+
verboseCompile := configuration.Settings.GetString("logging.level") == "debug"
151+
var compileRes *rpc.CompileResp
152+
if output.OutputFormat == "json" {
153+
compileRes, err = compile.Compile(context.Background(), compileReq, compileOut, compileErr, verboseCompile)
154+
} else {
155+
compileRes, err = compile.Compile(context.Background(), compileReq, os.Stdout, os.Stderr, verboseCompile)
150156
}
151157

152-
if uploadAfterCompile {
153-
_, err := upload.Upload(context.Background(), &rpc.UploadReq{
158+
if err == nil && uploadAfterCompile {
159+
uploadReq := &rpc.UploadReq{
154160
Instance: inst,
155161
Fqbn: fqbn,
156162
SketchPath: sketchPath.String(),
@@ -159,13 +165,32 @@ func run(cmd *cobra.Command, args []string) {
159165
Verify: verify,
160166
ImportDir: buildPath,
161167
Programmer: programmer,
162-
}, os.Stdout, os.Stderr)
163-
168+
}
169+
var err error
170+
if output.OutputFormat == "json" {
171+
// TODO: do not print upload output in json mode
172+
uploadOut := new(bytes.Buffer)
173+
uploadErr := new(bytes.Buffer)
174+
_, err = upload.Upload(context.Background(), uploadReq, uploadOut, uploadErr)
175+
} else {
176+
_, err = upload.Upload(context.Background(), uploadReq, os.Stdout, os.Stderr)
177+
}
164178
if err != nil {
165179
feedback.Errorf("Error during Upload: %v", err)
166180
os.Exit(errorcodes.ErrGeneric)
167181
}
168182
}
183+
184+
feedback.PrintResult(&compileResult{
185+
CompileOut: compileOut.String(),
186+
CompileErr: compileErr.String(),
187+
BuilderResult: compileRes,
188+
Success: err == nil,
189+
})
190+
if err != nil && output.OutputFormat != "json" {
191+
feedback.Errorf("Error during build: %v", err)
192+
os.Exit(errorcodes.ErrGeneric)
193+
}
169194
}
170195

171196
// initSketchPath returns the current working directory
@@ -182,3 +207,19 @@ func initSketchPath(sketchPath *paths.Path) *paths.Path {
182207
logrus.Infof("Reading sketch from dir: %s", wd)
183208
return wd
184209
}
210+
211+
type compileResult struct {
212+
CompileOut string `json:"compiler_out"`
213+
CompileErr string `json:"compiler_err"`
214+
BuilderResult *rpc.CompileResp `json:"builder_result"`
215+
Success bool `json:"success"`
216+
}
217+
218+
func (r *compileResult) Data() interface{} {
219+
return r
220+
}
221+
222+
func (r *compileResult) String() string {
223+
// The output is already printed via os.Stdout/os.Stdin
224+
return ""
225+
}

Diff for: cli/lib/list.go

+14-11
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,22 @@ func (ir installedResult) String() string {
140140
location = lib.GetContainerPlatform()
141141
}
142142

143+
available := ""
144+
sentence := ""
143145
if libMeta.GetRelease() != nil {
144-
available := libMeta.GetRelease().GetVersion()
145-
if available == "" {
146-
available = "-"
147-
}
148-
sentence := lib.Sentence
149-
if sentence == "" {
150-
sentence = "-"
151-
} else if len(sentence) > 40 {
152-
sentence = sentence[:37] + "..."
153-
}
154-
t.AddRow(name, lib.Version, available, location, sentence)
146+
available = libMeta.GetRelease().GetVersion()
147+
sentence = lib.Sentence
148+
}
149+
150+
if available == "" {
151+
available = "-"
152+
}
153+
if sentence == "" {
154+
sentence = "-"
155+
} else if len(sentence) > 40 {
156+
sentence = sentence[:37] + "..."
155157
}
158+
t.AddRow(name, lib.Version, available, location, sentence)
156159
}
157160

158161
return t.Render()

Diff for: commands/compile/compile.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W
189189
builderCtx.SetLogger(i18n.LoggerToCustomStreams{Stdout: outStream, Stderr: errStream})
190190
builderCtx.Clean = req.GetClean()
191191

192+
// Use defer() to create an rpc.CompileResp with all the information available at the
193+
// moment of return.
194+
defer func() {
195+
if r != nil {
196+
importedLibs := []*rpc.Library{}
197+
for _, lib := range builderCtx.ImportedLibraries {
198+
importedLibs = append(importedLibs, lib.ToRPCLibrary())
199+
}
200+
201+
r.BuildPath = builderCtx.BuildPath.String()
202+
r.UsedLibraries = importedLibs
203+
r.ExecutableSectionsSize = builderCtx.ExecutableSectionsSize.ToRPCExecutableSectionSizeArray()
204+
}
205+
}()
206+
192207
// if --preprocess or --show-properties were passed, we can stop here
193208
if req.GetShowProperties() {
194209
return &rpc.CompileResp{}, builder.RunParseHardwareAndDumpBuildProperties(builderCtx)
@@ -198,7 +213,7 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W
198213

199214
// if it's a regular build, go on...
200215
if err := builder.RunBuilder(builderCtx); err != nil {
201-
return nil, err
216+
return &rpc.CompileResp{}, err
202217
}
203218

204219
// If the export directory is set we assume you want to export the binaries

Diff for: commands/lib/list.go

+4-71
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,12 @@ func LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryList
9999
if nameFilter != "" && strings.ToLower(lib.Library.Name) != nameFilter {
100100
continue
101101
}
102-
libtmp, err := GetOutputLibrary(lib.Library)
103-
if err != nil {
104-
return nil, err
102+
var release *rpc.LibraryRelease
103+
if lib.Available != nil {
104+
release = lib.Available.ToRPCLibraryRelease()
105105
}
106-
release := GetOutputRelease(lib.Available)
107106
instaledLib = append(instaledLib, &rpc.InstalledLibrary{
108-
Library: libtmp,
107+
Library: lib.Library.ToRPCLibrary(),
109108
Release: release,
110109
})
111110
}
@@ -136,69 +135,3 @@ func listLibraries(lm *librariesmanager.LibrariesManager, updatable bool, all bo
136135
}
137136
return res
138137
}
139-
140-
// GetOutputLibrary FIXMEDOC
141-
func GetOutputLibrary(lib *libraries.Library) (*rpc.Library, error) {
142-
insdir := ""
143-
if lib.InstallDir != nil {
144-
insdir = lib.InstallDir.String()
145-
}
146-
srcdir := ""
147-
if lib.SourceDir != nil {
148-
srcdir = lib.SourceDir.String()
149-
}
150-
utldir := ""
151-
if lib.UtilityDir != nil {
152-
utldir = lib.UtilityDir.String()
153-
}
154-
cntplat := ""
155-
if lib.ContainerPlatform != nil {
156-
cntplat = lib.ContainerPlatform.String()
157-
}
158-
159-
return &rpc.Library{
160-
Name: lib.Name,
161-
Author: lib.Author,
162-
Maintainer: lib.Maintainer,
163-
Sentence: lib.Sentence,
164-
Paragraph: lib.Paragraph,
165-
Website: lib.Website,
166-
Category: lib.Category,
167-
Architectures: lib.Architectures,
168-
Types: lib.Types,
169-
InstallDir: insdir,
170-
SourceDir: srcdir,
171-
UtilityDir: utldir,
172-
Location: lib.Location.ToRPCLibraryLocation(),
173-
ContainerPlatform: cntplat,
174-
Layout: lib.Layout.ToRPCLibraryLayout(),
175-
RealName: lib.RealName,
176-
DotALinkage: lib.DotALinkage,
177-
Precompiled: lib.Precompiled,
178-
LdFlags: lib.LDflags,
179-
IsLegacy: lib.IsLegacy,
180-
Version: lib.Version.String(),
181-
License: lib.License,
182-
Examples: lib.Examples.AsStrings(),
183-
ProvidesIncludes: lib.DeclaredHeaders(),
184-
CompatibleWith: lib.CompatibleWith,
185-
}, nil
186-
}
187-
188-
// GetOutputRelease FIXMEDOC
189-
func GetOutputRelease(lib *librariesindex.Release) *rpc.LibraryRelease { //
190-
if lib != nil {
191-
return &rpc.LibraryRelease{
192-
Author: lib.Author,
193-
Version: lib.Version.String(),
194-
Maintainer: lib.Maintainer,
195-
Sentence: lib.Sentence,
196-
Paragraph: lib.Paragraph,
197-
Website: lib.Website,
198-
Category: lib.Category,
199-
Architectures: lib.Architectures,
200-
Types: lib.Types,
201-
}
202-
}
203-
return &rpc.LibraryRelease{}
204-
}

Diff for: legacy/builder/phases/sizer.go

+15
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error {
8888
}
8989
}
9090

91+
ctx.ExecutableSectionsSize = []types.ExecutableSectionSize{
92+
{
93+
Name: "text",
94+
Size: textSize,
95+
MaxSize: maxTextSize,
96+
},
97+
}
98+
if maxDataSize > 0 {
99+
ctx.ExecutableSectionsSize = append(ctx.ExecutableSectionsSize, types.ExecutableSectionSize{
100+
Name: "data",
101+
Size: dataSize,
102+
MaxSize: maxDataSize,
103+
})
104+
}
105+
91106
if textSize > maxTextSize {
92107
logger.Println(constants.LOG_LEVEL_ERROR, constants.MSG_SIZER_TEXT_TOO_BIG)
93108
return errors.New("text section exceeds available space in board")

0 commit comments

Comments
 (0)