Skip to content

Commit 0e5f629

Browse files
authored
Report compiler errors in machine readable format (#2182)
* Added gRPC protocol for compiler diagnostics reporting * Builder now support parsing of compiler output * Added gcc output parser * Implementation of compile output parser in gRPC command * Tell prettier to ignore testdata files * Added proper result structure for diagnostics * Added integration test * Fixed parser bug and added unit test
1 parent 7a9be52 commit 0e5f629

File tree

23 files changed

+1646
-47
lines changed

23 files changed

+1646
-47
lines changed

Diff for: .prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.vs/
55
.ionide/
66

7+
testdata
78
/arduino/libraries/librariesindex/testdata/invalid.json
89
/arduino/security/testdata/
910
/.licenses/

Diff for: arduino/builder/builder.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/arduino/arduino-cli/arduino/builder/internal/compilation"
2727
"github.com/arduino/arduino-cli/arduino/builder/internal/detector"
28+
"github.com/arduino/arduino-cli/arduino/builder/internal/diagnostics"
2829
"github.com/arduino/arduino-cli/arduino/builder/internal/logger"
2930
"github.com/arduino/arduino-cli/arduino/builder/internal/progress"
3031
"github.com/arduino/arduino-cli/arduino/builder/internal/utils"
@@ -36,6 +37,7 @@ import (
3637
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3738
"github.com/arduino/go-paths-helper"
3839
"github.com/arduino/go-properties-orderedmap"
40+
"github.com/sirupsen/logrus"
3941
)
4042

4143
// ErrSketchCannotBeLocatedInBuildPath fixdoc
@@ -90,6 +92,12 @@ type Builder struct {
9092
buildOptions *buildOptions
9193

9294
libsDetector *detector.SketchLibrariesDetector
95+
96+
// This is a function used to parse the output of the compiler
97+
// It is used to extract errors and warnings
98+
compilerOutputParser diagnostics.CompilerOutputParserCB
99+
// and here are the diagnostics parsed from the compiler
100+
compilerDiagnostics diagnostics.Diagnostics
93101
}
94102

95103
// buildArtifacts contains the result of various build
@@ -189,7 +197,7 @@ func NewBuilder(
189197
logger.Warn(string(verboseOut))
190198
}
191199

192-
return &Builder{
200+
b := &Builder{
193201
sketch: sk,
194202
buildProperties: buildProperties,
195203
buildPath: buildPath,
@@ -226,7 +234,26 @@ func NewBuilder(
226234
buildProperties.GetPath("runtime.platform.path"),
227235
buildProperties.GetPath("build.core.path"), // TODO can we buildCorePath ?
228236
),
229-
}, nil
237+
}
238+
239+
b.compilerOutputParser = func(cmdline []string, out []byte) {
240+
compiler := diagnostics.DetectCompilerFromCommandLine(
241+
cmdline,
242+
false, // at the moment compiler-probing is not required
243+
)
244+
if compiler == nil {
245+
logrus.Warnf("Could not detect compiler from: %s", cmdline)
246+
return
247+
}
248+
diags, err := diagnostics.ParseCompilerOutput(compiler, out)
249+
if err != nil {
250+
logrus.Warnf("Error parsing compiler output: %s", err)
251+
return
252+
}
253+
b.compilerDiagnostics = append(b.compilerDiagnostics, diags...)
254+
}
255+
256+
return b, nil
230257
}
231258

232259
// GetBuildProperties returns the build properties for running this build
@@ -249,6 +276,11 @@ func (b *Builder) ImportedLibraries() libraries.List {
249276
return b.libsDetector.ImportedLibraries()
250277
}
251278

279+
// CompilerDiagnostics returns the parsed compiler diagnostics
280+
func (b *Builder) CompilerDiagnostics() diagnostics.Diagnostics {
281+
return b.compilerDiagnostics
282+
}
283+
252284
// Preprocess fixdoc
253285
func (b *Builder) Preprocess() ([]byte, error) {
254286
b.Progress.AddSubSteps(6)

Diff for: arduino/builder/compilation.go

+6
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ func (b *Builder) compileFileWithRecipe(
166166
}
167167
b.logger.WriteStderr(commandStderr.Bytes())
168168

169+
// Parse the output of the compiler to gather errors and warnings...
170+
if b.compilerOutputParser != nil {
171+
b.compilerOutputParser(command.GetArgs(), commandStdout.Bytes())
172+
b.compilerOutputParser(command.GetArgs(), commandStderr.Bytes())
173+
}
174+
169175
// ...and then return the error
170176
if err != nil {
171177
return nil, errors.WithStack(err)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2023 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package diagnostics
17+
18+
import (
19+
"bytes"
20+
"path/filepath"
21+
"strings"
22+
23+
"github.com/arduino/arduino-cli/executils"
24+
semver "go.bug.st/relaxed-semver"
25+
)
26+
27+
// DetectedCompiler represents a compiler detected from a given command line
28+
type DetectedCompiler struct {
29+
Name string
30+
Family string
31+
Version *semver.Version
32+
DetailedVersion []string
33+
}
34+
35+
// This function is overridden for mocking unit tests
36+
var runProcess = func(args ...string) []string {
37+
if cmd, err := executils.NewProcess(nil, args...); err == nil {
38+
out := &bytes.Buffer{}
39+
cmd.RedirectStdoutTo(out)
40+
cmd.Run()
41+
return splitLines(out.Bytes())
42+
}
43+
return nil
44+
}
45+
46+
// DetectCompilerFromCommandLine tries to detect a compiler from a given command line.
47+
// If probeCompiler is true, the compiler may be executed with different flags to
48+
// infer the version or capabilities.
49+
func DetectCompilerFromCommandLine(args []string, probeCompiler bool) *DetectedCompiler {
50+
if len(args) == 0 {
51+
return nil
52+
}
53+
basename := filepath.Base(args[0])
54+
family := ""
55+
if strings.Contains(basename, "g++") || strings.Contains(basename, "gcc") {
56+
family = "gcc"
57+
}
58+
res := &DetectedCompiler{
59+
Name: basename,
60+
Family: family,
61+
}
62+
63+
if family == "gcc" && probeCompiler {
64+
// Run "gcc --version" to obtain more info
65+
res.DetailedVersion = runProcess(args[0], "--version")
66+
67+
// Usually on the first line we get the compiler architecture and
68+
// version (as last field), followed by the compiler license, for
69+
// example:
70+
//
71+
// g++ (Ubuntu 12.2.0-3ubuntu1) 12.2.0
72+
// Copyright (C) 2022 Free Software Foundation, Inc.
73+
// This is free software; see the source for copying conditions. There is NO
74+
// warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
75+
//
76+
if len(res.DetailedVersion) > 0 {
77+
split := strings.Split(res.DetailedVersion[0], " ")
78+
if len(split) >= 3 {
79+
res.Name = split[0]
80+
res.Version, _ = semver.Parse(split[len(split)-1])
81+
}
82+
}
83+
}
84+
return res
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2023 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package diagnostics
17+
18+
import (
19+
"strings"
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func init() {
26+
runProcess = mockedRunProcessToGetCompilerVersion
27+
}
28+
29+
func mockedRunProcessToGetCompilerVersion(args ...string) []string {
30+
if strings.HasSuffix(args[0], "7.3.0-atmel3.6.1-arduino7/bin/avr-g++") && args[1] == "--version" {
31+
return []string{
32+
"avr-g++ (GCC) 7.3.0",
33+
"Copyright (C) 2017 Free Software Foundation, Inc.",
34+
"This is free software; see the source for copying conditions. There is NO",
35+
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
36+
"",
37+
}
38+
}
39+
if strings.HasSuffix(args[0], "7.3.0-atmel3.6.1-arduino7/bin/avr-gcc") && args[1] == "--version" {
40+
return []string{
41+
"avr-gcc (GCC) 7.3.0",
42+
"Copyright (C) 2017 Free Software Foundation, Inc.",
43+
"This is free software; see the source for copying conditions. There is NO",
44+
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
45+
"",
46+
}
47+
}
48+
if strings.HasSuffix(args[0], "xtensa-esp32-elf-gcc/gcc8_4_0-esp-2021r2-patch3/bin/xtensa-esp32-elf-g++") && args[1] == "--version" {
49+
return []string{
50+
"xtensa-esp32-elf-g++ (crosstool-NG esp-2021r2-patch3) 8.4.0",
51+
"Copyright (C) 2018 Free Software Foundation, Inc.",
52+
"This is free software; see the source for copying conditions. There is NO",
53+
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
54+
"",
55+
}
56+
}
57+
58+
panic("missing mock for command line: " + strings.Join(args, " "))
59+
}
60+
61+
func TestCompilerDetection(t *testing.T) {
62+
comp := DetectCompilerFromCommandLine([]string{"~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-g++"}, true)
63+
require.NotNil(t, comp)
64+
require.Equal(t, "gcc", comp.Family)
65+
require.Equal(t, "avr-g++", comp.Name)
66+
require.Equal(t, "7.3.0", comp.Version.String())
67+
68+
comp = DetectCompilerFromCommandLine([]string{"~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-gcc"}, true)
69+
require.NotNil(t, comp)
70+
require.Equal(t, "gcc", comp.Family)
71+
require.Equal(t, "avr-gcc", comp.Name)
72+
require.Equal(t, "7.3.0", comp.Version.String())
73+
74+
comp = DetectCompilerFromCommandLine([]string{"/home/megabug/.arduino15/packages/esp32/tools/xtensa-esp32-elf-gcc/gcc8_4_0-esp-2021r2-patch3/bin/xtensa-esp32-elf-g++"}, true)
75+
require.NotNil(t, comp)
76+
require.Equal(t, "gcc", comp.Family)
77+
require.Equal(t, "xtensa-esp32-elf-g++", comp.Name)
78+
require.Equal(t, "8.4.0", comp.Version.String())
79+
}

0 commit comments

Comments
 (0)