Skip to content

Commit 1128f3e

Browse files
committed
Added gcc output parser
1 parent 7df8ea2 commit 1128f3e

File tree

11 files changed

+1033
-0
lines changed

11 files changed

+1033
-0
lines changed
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+
}

Diff for: arduino/builder/internal/diagnostics/parser.go

+153
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,157 @@
1515

1616
package diagnostics
1717

18+
import (
19+
"fmt"
20+
"strings"
21+
22+
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
23+
)
24+
1825
type CompilerOutputParserCB func(cmdline []string, out []byte)
26+
27+
// Diagnostics represents a list of diagnostics
28+
type Diagnostics []*Diagnostic
29+
30+
// Diagnostic represents a diagnostic (a compiler error, warning, note, etc.)
31+
type Diagnostic struct {
32+
Severity Severity `json:"severity,omitempty"`
33+
Message string `json:"message"`
34+
File string `json:"file,omitempty"`
35+
Line int `json:"line,omitempty"`
36+
Column int `json:"col,omitempty"`
37+
Context FullContext `json:"context,omitempty"`
38+
Suggestions Notes `json:"suggestions,omitempty"`
39+
}
40+
41+
// Severity is a diagnostic severity
42+
type Severity string
43+
44+
const (
45+
// SeverityUnspecified is the undefined severity
46+
SeverityUnspecified Severity = ""
47+
// SeverityWarning is a warning
48+
SeverityWarning = "WARNING"
49+
// SeverityError is an error
50+
SeverityError = "ERROR"
51+
// SeverityFatal is a fatal error
52+
SeverityFatal = "FATAL"
53+
)
54+
55+
// Notes represents a list of Note
56+
type Notes []*Note
57+
58+
// Note represents a compiler annotation or suggestion
59+
type Note struct {
60+
Message string `json:"message"`
61+
File string `json:"file,omitempty"`
62+
Line int `json:"line,omitempty"`
63+
Column int `json:"col,omitempty"`
64+
}
65+
66+
// FullContext represents a list of Context
67+
type FullContext []*Context
68+
69+
// Context represents a context, i.e. a reference to a file, line and column
70+
// or a part of the code that a Diagnostic refers to.
71+
type Context struct {
72+
Message string `json:"message"`
73+
File string `json:"file,omitempty"`
74+
Line int `json:"line,omitempty"`
75+
Column int `json:"col,omitempty"`
76+
}
77+
78+
// ParseCompilerOutput parses the output of a compiler and returns a list of
79+
// diagnostics.
80+
func ParseCompilerOutput(compiler *DetectedCompiler, out []byte) ([]*Diagnostic, error) {
81+
lines := splitLines(out)
82+
switch compiler.Family {
83+
case "gcc":
84+
return parseGccOutput(lines)
85+
default:
86+
return nil, fmt.Errorf("unsupported compiler: %s", compiler)
87+
}
88+
}
89+
90+
func splitLines(in []byte) []string {
91+
res := strings.Split(string(in), "\n")
92+
for i, line := range res {
93+
res[i] = strings.TrimSuffix(line, "\r")
94+
}
95+
if l := len(res) - 1; res[l] == "" {
96+
res = res[:l]
97+
}
98+
return res
99+
}
100+
101+
// ToRPC converts a Diagnostics to a slice of rpc.CompileDiagnostic
102+
func (d Diagnostics) ToRPC() []*rpc.CompileDiagnostic {
103+
if len(d) == 0 {
104+
return nil
105+
}
106+
var res []*rpc.CompileDiagnostic
107+
for _, diag := range d {
108+
res = append(res, diag.ToRPC())
109+
}
110+
return res
111+
}
112+
113+
// ToRPC converts a Diagnostic to a rpc.CompileDiagnostic
114+
func (d *Diagnostic) ToRPC() *rpc.CompileDiagnostic {
115+
if d == nil {
116+
return nil
117+
}
118+
return &rpc.CompileDiagnostic{
119+
Severity: string(d.Severity),
120+
Message: d.Message,
121+
File: d.File,
122+
Line: int64(d.Line),
123+
Column: int64(d.Column),
124+
Context: d.Context.ToRPC(),
125+
Notes: d.Suggestions.ToRPC(),
126+
}
127+
}
128+
129+
// ToRPC converts a Notes to a slice of rpc.CompileDiagnosticNote
130+
func (s Notes) ToRPC() []*rpc.CompileDiagnosticNote {
131+
var res []*rpc.CompileDiagnosticNote
132+
for _, suggestion := range s {
133+
res = append(res, suggestion.ToRPC())
134+
}
135+
return res
136+
}
137+
138+
// ToRPC converts a Note to a rpc.CompileDiagnosticNote
139+
func (s *Note) ToRPC() *rpc.CompileDiagnosticNote {
140+
if s == nil {
141+
return nil
142+
}
143+
return &rpc.CompileDiagnosticNote{
144+
File: s.File,
145+
Line: int64(s.Line),
146+
Column: int64(s.Column),
147+
Message: s.Message,
148+
}
149+
}
150+
151+
// ToRPC converts a FullContext to a slice of rpc.CompileDiagnosticContext
152+
func (t FullContext) ToRPC() []*rpc.CompileDiagnosticContext {
153+
var res []*rpc.CompileDiagnosticContext
154+
for _, trace := range t {
155+
res = append(res, trace.ToRPC())
156+
}
157+
return res
158+
}
159+
160+
// ToRPC converts a Context to a rpc.CompileDiagnosticContext
161+
func (d *Context) ToRPC() *rpc.CompileDiagnosticContext {
162+
if d == nil {
163+
return nil
164+
}
165+
return &rpc.CompileDiagnosticContext{
166+
File: d.File,
167+
Line: int64(d.Line),
168+
Column: int64(d.Column),
169+
Message: d.Message,
170+
}
171+
}

0 commit comments

Comments
 (0)