diff --git a/Taskfile.yml b/Taskfile.yml index c8f7db71ca3..2a9b20fd9ca 100755 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -137,7 +137,7 @@ tasks: desc: Add missing go license headers cmds: - go install github.com/google/addlicense@v1.1.1 - - addlicense -c "ARDUINO SA (http://www.arduino.cc/)" -f ./license_header.tpl **/*.go + - addlicense -c "ARDUINO SA (http://www.arduino.cc/)" -f ./license_header.tpl $(find . -name "*.go" -type f -print0 | xargs -0) # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-markdown-task/Taskfile.yml markdown:check-links: diff --git a/commands/internal/instances/instances.go b/commands/internal/instances/instances.go index 028e82e6738..019927a4967 100644 --- a/commands/internal/instances/instances.go +++ b/commands/internal/instances/instances.go @@ -1,3 +1,18 @@ +// This file is part of arduino-cli. +// +// Copyright 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + package instances import ( diff --git a/internal/arduino/builder/builder.go b/internal/arduino/builder/builder.go index 40ca74e44d2..303caaa2063 100644 --- a/internal/arduino/builder/builder.go +++ b/internal/arduino/builder/builder.go @@ -36,7 +36,6 @@ import ( rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" - "github.com/sirupsen/logrus" ) // ErrSketchCannotBeLocatedInBuildPath fixdoc @@ -94,11 +93,7 @@ type Builder struct { toolEnv []string - // This is a function used to parse the output of the compiler - // It is used to extract errors and warnings - compilerOutputParser diagnostics.CompilerOutputParserCB - // and here are the diagnostics parsed from the compiler - compilerDiagnostics diagnostics.Diagnostics + diagnosticStore *diagnostics.Store } // buildArtifacts contains the result of various build @@ -199,6 +194,7 @@ func NewBuilder( logger.Warn(string(verboseOut)) } + diagnosticStore := diagnostics.NewStore() b := &Builder{ sketch: sk, buildProperties: buildProperties, @@ -220,12 +216,6 @@ func NewBuilder( targetPlatform: targetPlatform, actualPlatform: actualPlatform, toolEnv: toolEnv, - libsDetector: detector.NewSketchLibrariesDetector( - libsManager, libsResolver, - useCachedLibrariesResolution, - onlyUpdateCompilationDatabase, - logger, - ), buildOptions: newBuildOptions( hardwareDirs, otherLibrariesDirs, builtInLibrariesDirs, buildPath, @@ -237,25 +227,15 @@ func NewBuilder( buildProperties.GetPath("runtime.platform.path"), buildProperties.GetPath("build.core.path"), // TODO can we buildCorePath ? ), + diagnosticStore: diagnosticStore, + libsDetector: detector.NewSketchLibrariesDetector( + libsManager, libsResolver, + useCachedLibrariesResolution, + onlyUpdateCompilationDatabase, + logger, + diagnosticStore, + ), } - - b.compilerOutputParser = func(cmdline []string, out []byte) { - compiler := diagnostics.DetectCompilerFromCommandLine( - cmdline, - false, // at the moment compiler-probing is not required - ) - if compiler == nil { - logrus.Warnf("Could not detect compiler from: %s", cmdline) - return - } - diags, err := diagnostics.ParseCompilerOutput(compiler, out) - if err != nil { - logrus.Warnf("Error parsing compiler output: %s", err) - return - } - b.compilerDiagnostics = append(b.compilerDiagnostics, diags...) - } - return b, nil } @@ -281,7 +261,7 @@ func (b *Builder) ImportedLibraries() libraries.List { // CompilerDiagnostics returns the parsed compiler diagnostics func (b *Builder) CompilerDiagnostics() diagnostics.Diagnostics { - return b.compilerDiagnostics + return b.diagnosticStore.Diagnostics() } // Preprocess fixdoc diff --git a/internal/arduino/builder/compilation.go b/internal/arduino/builder/compilation.go index d997c264bcf..69fa1c7974b 100644 --- a/internal/arduino/builder/compilation.go +++ b/internal/arduino/builder/compilation.go @@ -166,9 +166,9 @@ func (b *Builder) compileFileWithRecipe( b.logger.WriteStderr(commandStderr.Bytes()) // Parse the output of the compiler to gather errors and warnings... - if b.compilerOutputParser != nil { - b.compilerOutputParser(command.GetArgs(), commandStdout.Bytes()) - b.compilerOutputParser(command.GetArgs(), commandStderr.Bytes()) + if b.diagnosticStore != nil { + b.diagnosticStore.Parse(command.GetArgs(), commandStdout.Bytes()) + b.diagnosticStore.Parse(command.GetArgs(), commandStderr.Bytes()) } // ...and then return the error diff --git a/internal/arduino/builder/internal/detector/detector.go b/internal/arduino/builder/internal/detector/detector.go index 9285846fcf1..46fa991a132 100644 --- a/internal/arduino/builder/internal/detector/detector.go +++ b/internal/arduino/builder/internal/detector/detector.go @@ -26,6 +26,7 @@ import ( "strings" "time" + "github.com/arduino/arduino-cli/internal/arduino/builder/internal/diagnostics" "github.com/arduino/arduino-cli/internal/arduino/builder/internal/logger" "github.com/arduino/arduino-cli/internal/arduino/builder/internal/preprocessor" "github.com/arduino/arduino-cli/internal/arduino/builder/internal/utils" @@ -57,6 +58,7 @@ type SketchLibrariesDetector struct { librariesResolutionResults map[string]libraryResolutionResult includeFolders paths.PathList logger *logger.BuilderLogger + diagnosticStore *diagnostics.Store } // NewSketchLibrariesDetector todo @@ -66,6 +68,7 @@ func NewSketchLibrariesDetector( useCachedLibrariesResolution bool, onlyUpdateCompilationDatabase bool, logger *logger.BuilderLogger, + diagnosticStore *diagnostics.Store, ) *SketchLibrariesDetector { return &SketchLibrariesDetector{ librariesManager: lm, @@ -76,6 +79,7 @@ func NewSketchLibrariesDetector( includeFolders: paths.PathList{}, onlyUpdateCompilationDatabase: onlyUpdateCompilationDatabase, logger: logger, + diagnosticStore: diagnosticStore, } } @@ -337,7 +341,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone( } var preprocErr error - var preprocStderr []byte + var preprocFirstResult preprocessor.Result var missingIncludeH string if unchanged && cache.valid { @@ -346,21 +350,20 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone( l.logger.Info(tr("Using cached library dependencies for file: %[1]s", sourcePath)) } } else { - var preprocStdout []byte - preprocStdout, preprocStderr, preprocErr = preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties) + preprocFirstResult, preprocErr = preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties) if l.logger.Verbose() { - l.logger.WriteStdout(preprocStdout) + l.logger.WriteStdout(preprocFirstResult.Stdout()) } // Unwrap error and see if it is an ExitError. var exitErr *exec.ExitError if preprocErr == nil { // Preprocessor successful, done missingIncludeH = "" - } else if isExitErr := errors.As(preprocErr, &exitErr); !isExitErr || preprocStderr == nil { + } else if isExitErr := errors.As(preprocErr, &exitErr); !isExitErr || preprocFirstResult.Stderr() == nil { // Ignore ExitErrors (e.g. gcc returning non-zero status), but bail out on other errors return preprocErr } else { - missingIncludeH = IncludesFinderWithRegExp(string(preprocStderr)) + missingIncludeH = IncludesFinderWithRegExp(string(preprocFirstResult.Stderr())) if missingIncludeH == "" && l.logger.Verbose() { l.logger.Info(tr("Error while detecting libraries included by %[1]s", sourcePath)) } @@ -376,22 +379,25 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone( library := l.resolveLibrary(missingIncludeH, platformArch) if library == nil { // Library could not be resolved, show error - if preprocErr == nil || preprocStderr == nil { + if preprocErr == nil || preprocFirstResult.Stderr() == nil { // Filename came from cache, so run preprocessor to obtain error to show - var preprocStdout []byte - preprocStdout, preprocStderr, preprocErr = preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties) + result, err := preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties) if l.logger.Verbose() { - l.logger.WriteStdout(preprocStdout) + l.logger.WriteStdout(result.Stdout()) } - if preprocErr == nil { + if err == nil { // If there is a missing #include in the cache, but running // gcc does not reproduce that, there is something wrong. // Returning an error here will cause the cache to be // deleted, so hopefully the next compilation will succeed. return errors.New(tr("Internal error in cache")) } + l.diagnosticStore.Parse(result.Args(), result.Stderr()) + l.logger.WriteStderr(result.Stderr()) + return err } - l.logger.WriteStderr(preprocStderr) + l.diagnosticStore.Parse(preprocFirstResult.Args(), preprocFirstResult.Stderr()) + l.logger.WriteStderr(preprocFirstResult.Stderr()) return preprocErr } diff --git a/internal/arduino/builder/internal/diagnostics/store.go b/internal/arduino/builder/internal/diagnostics/store.go new file mode 100644 index 00000000000..dc8d9a8f882 --- /dev/null +++ b/internal/arduino/builder/internal/diagnostics/store.go @@ -0,0 +1,51 @@ +// This file is part of arduino-cli. +// +// Copyright 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package diagnostics + +import ( + "github.com/sirupsen/logrus" +) + +type Store struct { + results Diagnostics +} + +func NewStore() *Store { + return &Store{} +} + +// Parse parses the output coming from a compiler. It then stores the parsed +// diagnostics in a slice of Diagnostic. +func (m *Store) Parse(cmdline []string, out []byte) { + compiler := DetectCompilerFromCommandLine( + cmdline, + false, // at the moment compiler-probing is not required + ) + if compiler == nil { + logrus.Warnf("Could not detect compiler from: %s", cmdline) + return + } + diags, err := ParseCompilerOutput(compiler, out) + if err != nil { + logrus.Warnf("Error parsing compiler output: %s", err) + return + } + m.results = append(m.results, diags...) +} + +func (m *Store) Diagnostics() Diagnostics { + return m.results +} diff --git a/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go b/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go index 2c3d61c1397..da8cde2adfc 100644 --- a/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go +++ b/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go @@ -30,20 +30,23 @@ import ( // PreprocessSketchWithArduinoPreprocessor performs preprocessing of the arduino sketch // using arduino-preprocessor (https://github.com/arduino/arduino-preprocessor). -func PreprocessSketchWithArduinoPreprocessor(sk *sketch.Sketch, buildPath *paths.Path, includeFolders paths.PathList, lineOffset int, buildProperties *properties.Map, onlyUpdateCompilationDatabase bool) ([]byte, []byte, error) { +func PreprocessSketchWithArduinoPreprocessor( + sk *sketch.Sketch, buildPath *paths.Path, includeFolders paths.PathList, + lineOffset int, buildProperties *properties.Map, onlyUpdateCompilationDatabase bool, +) (*Result, error) { verboseOut := &bytes.Buffer{} normalOut := &bytes.Buffer{} if err := buildPath.Join("preproc").MkdirAll(); err != nil { - return nil, nil, err + return nil, err } sourceFile := buildPath.Join("sketch", sk.MainFile.Base()+".cpp") targetFile := buildPath.Join("preproc", "sketch_merged.cpp") - gccStdout, gccStderr, err := GCC(sourceFile, targetFile, includeFolders, buildProperties) - verboseOut.Write(gccStdout) - verboseOut.Write(gccStderr) + gccResult, err := GCC(sourceFile, targetFile, includeFolders, buildProperties) + verboseOut.Write(gccResult.Stdout()) + verboseOut.Write(gccResult.Stderr()) if err != nil { - return nil, nil, err + return nil, err } arduiniPreprocessorProperties := properties.NewMap() @@ -56,18 +59,18 @@ func PreprocessSketchWithArduinoPreprocessor(sk *sketch.Sketch, buildPath *paths arduiniPreprocessorProperties.SetPath("source_file", targetFile) pattern := arduiniPreprocessorProperties.Get("pattern") if pattern == "" { - return nil, nil, errors.New(tr("arduino-preprocessor pattern is missing")) + return nil, errors.New(tr("arduino-preprocessor pattern is missing")) } commandLine := arduiniPreprocessorProperties.ExpandPropsInString(pattern) parts, err := properties.SplitQuotedString(commandLine, `"'`, false) if err != nil { - return nil, nil, err + return nil, err } command, err := paths.NewProcess(nil, parts...) if err != nil { - return nil, nil, err + return nil, err } if runtime.GOOS == "windows" { // chdir in the uppermost directory to avoid UTF-8 bug in clang (https://github.com/arduino/arduino-preprocessor/issues/2) @@ -78,14 +81,13 @@ func PreprocessSketchWithArduinoPreprocessor(sk *sketch.Sketch, buildPath *paths commandStdOut, commandStdErr, err := command.RunAndCaptureOutput(context.Background()) verboseOut.Write(commandStdErr) if err != nil { - return normalOut.Bytes(), verboseOut.Bytes(), err + return &Result{args: gccResult.Args(), stdout: verboseOut.Bytes(), stderr: normalOut.Bytes()}, err } result := utils.NormalizeUTF8(commandStdOut) destFile := buildPath.Join(sk.MainFile.Base() + ".cpp") if err := destFile.WriteFile(result); err != nil { - return normalOut.Bytes(), verboseOut.Bytes(), err + return &Result{args: gccResult.Args(), stdout: verboseOut.Bytes(), stderr: normalOut.Bytes()}, err } - - return normalOut.Bytes(), verboseOut.Bytes(), err + return &Result{args: gccResult.Args(), stdout: verboseOut.Bytes(), stderr: normalOut.Bytes()}, err } diff --git a/internal/arduino/builder/internal/preprocessor/ctags.go b/internal/arduino/builder/internal/preprocessor/ctags.go index 53990492169..4a0cf783b45 100644 --- a/internal/arduino/builder/internal/preprocessor/ctags.go +++ b/internal/arduino/builder/internal/preprocessor/ctags.go @@ -40,52 +40,56 @@ var tr = i18n.Tr var DebugPreprocessor bool // PreprocessSketchWithCtags performs preprocessing of the arduino sketch using CTags. -func PreprocessSketchWithCtags(sketch *sketch.Sketch, buildPath *paths.Path, includes paths.PathList, lineOffset int, buildProperties *properties.Map, onlyUpdateCompilationDatabase bool) ([]byte, []byte, error) { +func PreprocessSketchWithCtags( + sketch *sketch.Sketch, buildPath *paths.Path, includes paths.PathList, + lineOffset int, buildProperties *properties.Map, + onlyUpdateCompilationDatabase, verbose bool, +) (*Result, error) { // Create a temporary working directory tmpDir, err := paths.MkTempDir("", "") if err != nil { - return nil, nil, err + return nil, err } defer tmpDir.RemoveAll() ctagsTarget := tmpDir.Join("sketch_merged.cpp") - normalOutput := &bytes.Buffer{} - verboseOutput := &bytes.Buffer{} + stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} // Run GCC preprocessor sourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp") - gccStdout, gccStderr, err := GCC(sourceFile, ctagsTarget, includes, buildProperties) - verboseOutput.Write(gccStdout) - verboseOutput.Write(gccStderr) - normalOutput.Write(gccStderr) + result, err := GCC(sourceFile, ctagsTarget, includes, buildProperties) + stdout.Write(result.Stdout()) + stderr.Write(result.Stderr()) if err != nil { if !onlyUpdateCompilationDatabase { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } // Do not bail out if we are generating the compile commands database - normalOutput.WriteString(fmt.Sprintf("%s: %s", + stderr.WriteString(fmt.Sprintf("%s: %s", tr("An error occurred adding prototypes"), tr("the compilation database may be incomplete or inaccurate"))) if err := sourceFile.CopyTo(ctagsTarget); err != nil { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } } if src, err := ctagsTarget.ReadFile(); err == nil { filteredSource := filterSketchSource(sketch, bytes.NewReader(src), false) if err := ctagsTarget.WriteFile([]byte(filteredSource)); err != nil { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } } else { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } // Run CTags on gcc-preprocessed source ctagsOutput, ctagsStdErr, err := RunCTags(ctagsTarget, buildProperties) - verboseOutput.Write(ctagsStdErr) + if verbose { + stderr.Write(ctagsStdErr) + } if err != nil { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } // Parse CTags output @@ -100,13 +104,13 @@ func PreprocessSketchWithCtags(sketch *sketch.Sketch, buildPath *paths.Path, inc if sourceData, err := sourceFile.ReadFile(); err == nil { source = string(sourceData) } else { - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } source = strings.ReplaceAll(source, "\r\n", "\n") source = strings.ReplaceAll(source, "\r", "\n") sourceRows := strings.Split(source, "\n") if isFirstFunctionOutsideOfSource(firstFunctionLine, sourceRows) { - return normalOutput.Bytes(), verboseOutput.Bytes(), nil + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, nil } insertionLine := firstFunctionLine + lineOffset - 1 @@ -132,7 +136,7 @@ func PreprocessSketchWithCtags(sketch *sketch.Sketch, buildPath *paths.Path, inc // Write back arduino-preprocess output to the sourceFile err = sourceFile.WriteFile([]byte(preprocessedSource)) - return normalOutput.Bytes(), verboseOutput.Bytes(), err + return &Result{args: result.Args(), stdout: stdout.Bytes(), stderr: stderr.Bytes()}, err } func composePrototypeSection(line int, prototypes []*ctags.Prototype) string { diff --git a/internal/arduino/builder/internal/preprocessor/gcc.go b/internal/arduino/builder/internal/preprocessor/gcc.go index 791a3366fba..d9cf1c446ea 100644 --- a/internal/arduino/builder/internal/preprocessor/gcc.go +++ b/internal/arduino/builder/internal/preprocessor/gcc.go @@ -29,7 +29,10 @@ import ( // GCC performs a run of the gcc preprocess (macro/includes expansion). The function outputs the result // to targetFilePath. Returns the stdout/stderr of gcc if any. -func GCC(sourceFilePath *paths.Path, targetFilePath *paths.Path, includes paths.PathList, buildProperties *properties.Map) ([]byte, []byte, error) { +func GCC( + sourceFilePath, targetFilePath *paths.Path, + includes paths.PathList, buildProperties *properties.Map, +) (Result, error) { gccBuildProperties := properties.NewMap() gccBuildProperties.Set("preproc.macros.flags", "-w -x c++ -E -CC") gccBuildProperties.Merge(buildProperties) @@ -54,14 +57,14 @@ func GCC(sourceFilePath *paths.Path, targetFilePath *paths.Path, includes paths. pattern := gccBuildProperties.Get(gccPreprocRecipeProperty) if pattern == "" { - return nil, nil, errors.New(tr("%s pattern is missing", gccPreprocRecipeProperty)) + return Result{}, errors.New(tr("%s pattern is missing", gccPreprocRecipeProperty)) } commandLine := gccBuildProperties.ExpandPropsInString(pattern) commandLine = properties.DeleteUnexpandedPropsFromString(commandLine) args, err := properties.SplitQuotedString(commandLine, `"'`, false) if err != nil { - return nil, nil, err + return Result{}, err } // Remove -MMD argument if present. Leaving it will make gcc try @@ -70,12 +73,12 @@ func GCC(sourceFilePath *paths.Path, targetFilePath *paths.Path, includes paths. proc, err := paths.NewProcess(nil, args...) if err != nil { - return nil, nil, err + return Result{}, err } stdout, stderr, err := proc.RunAndCaptureOutput(context.Background()) // Append gcc arguments to stdout stdout = append([]byte(fmt.Sprintln(strings.Join(args, " "))), stdout...) - return stdout, stderr, err + return Result{args: proc.GetArgs(), stdout: stdout, stderr: stderr}, err } diff --git a/internal/arduino/builder/internal/preprocessor/result.go b/internal/arduino/builder/internal/preprocessor/result.go new file mode 100644 index 00000000000..3fa45e40974 --- /dev/null +++ b/internal/arduino/builder/internal/preprocessor/result.go @@ -0,0 +1,34 @@ +// This file is part of arduino-cli. +// +// Copyright 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package preprocessor + +type Result struct { + args []string + stdout []byte + stderr []byte +} + +func (r Result) Args() []string { + return r.args +} + +func (r Result) Stdout() []byte { + return r.stdout +} + +func (r Result) Stderr() []byte { + return r.stderr +} diff --git a/internal/arduino/builder/preprocess_sketch.go b/internal/arduino/builder/preprocess_sketch.go index 106df3d7b40..d7fd6e32e72 100644 --- a/internal/arduino/builder/preprocess_sketch.go +++ b/internal/arduino/builder/preprocess_sketch.go @@ -23,14 +23,16 @@ import ( // preprocessSketch fixdoc func (b *Builder) preprocessSketch(includes paths.PathList) error { // In the future we might change the preprocessor - normalOutput, verboseOutput, err := preprocessor.PreprocessSketchWithCtags( + result, err := preprocessor.PreprocessSketchWithCtags( b.sketch, b.buildPath, includes, b.lineOffset, - b.buildProperties, b.onlyUpdateCompilationDatabase, + b.buildProperties, b.onlyUpdateCompilationDatabase, b.logger.Verbose(), ) - if b.logger.Verbose() { - b.logger.WriteStdout(verboseOutput) - } else { - b.logger.WriteStdout(normalOutput) + if result != nil { + if b.logger.Verbose() { + b.logger.WriteStdout(result.Stdout()) + } + b.logger.WriteStdout(result.Stderr()) + b.diagnosticStore.Parse(result.Args(), result.Stderr()) } return err diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go index a498ce5374f..1ba4f6f1cf6 100644 --- a/internal/cli/compile/compile.go +++ b/internal/cli/compile/compile.go @@ -355,7 +355,6 @@ func runCompileCommand(cmd *cobra.Command, args []string) { UpdatedUploadPort: result.NewPort(uploadRes.GetUpdatedUploadPort()), }, ProfileOut: profileOut, - Diagnostics: result.NewCompileDiagnostics(builderRes.GetDiagnostics()), Success: compileError == nil, showPropertiesMode: showProperties, hideStats: preprocess, @@ -399,14 +398,13 @@ type updatedUploadPortResult struct { } type compileResult struct { - CompilerOut string `json:"compiler_out"` - CompilerErr string `json:"compiler_err"` - BuilderResult *result.BuilderResult `json:"builder_result"` - UploadResult updatedUploadPortResult `json:"upload_result"` - Success bool `json:"success"` - ProfileOut string `json:"profile_out,omitempty"` - Error string `json:"error,omitempty"` - Diagnostics []*result.CompileDiagnostic `json:"diagnostics,omitempty"` + CompilerOut string `json:"compiler_out"` + CompilerErr string `json:"compiler_err"` + BuilderResult *result.BuilderResult `json:"builder_result"` + UploadResult updatedUploadPortResult `json:"upload_result"` + Success bool `json:"success"` + ProfileOut string `json:"profile_out,omitempty"` + Error string `json:"error,omitempty"` showPropertiesMode arguments.ShowPropertiesMode hideStats bool } diff --git a/internal/integrationtest/compile_3/compile_test.go b/internal/integrationtest/compile_3/compile_test.go index 2f9ba8e5306..076e55b4597 100644 --- a/internal/integrationtest/compile_3/compile_test.go +++ b/internal/integrationtest/compile_3/compile_test.go @@ -117,7 +117,7 @@ func TestCompilerErrOutput(t *testing.T) { require.Error(t, err) outJson := requirejson.Parse(t, out) outJson.Query(`.compiler_err`).MustContain(`"error"`) - outJson.Query(`.diagnostics`).MustContain(` + outJson.Query(`.builder_result.diagnostics`).MustContain(` [ { "severity": "ERROR", @@ -128,6 +128,55 @@ func TestCompilerErrOutput(t *testing.T) { ]`) } + // Test the preprocessor errors are present in the diagnostics + { + // prepare sketch + sketch, err := paths.New("testdata", "blink_with_wrong_include").Abs() + require.NoError(t, err) + + // Run compile and catch err stream + out, _, err := cli.Run("compile", "-b", "arduino:avr:uno", "-v", "--format", "json", sketch.String()) + require.Error(t, err) + outJson := requirejson.Parse(t, out) + outJson.Query(`.success`).MustContain(`false`) + outJson.Query(`.builder_result.diagnostics`).MustContain(` + [ + { + "severity": "ERROR", + "line": 1, + "column": 2, + "message": "invalid preprocessing directive #wrong\n #wrong\n ^~~~~", + } + ]`) + } + + // Test the preprocessor errors are present in the diagnostics. + // In case we have 2 libraries: + // 1. one is missing + // 2. the other one is missing only from the first GCC run + // The diagnostics should report only 1 missing library. + { + // prepare sketch + sketch, err := paths.New("testdata", "using_Wire_with_missing_lib").Abs() + require.NoError(t, err) + + // Run compile and catch err stream + out, _, err := cli.Run("compile", "-b", "arduino:avr:uno", "-v", "--format", "json", sketch.String()) + require.Error(t, err) + outJson := requirejson.Parse(t, out) + outJson.Query(`.success`).MustContain(`false`) + outJson.Query(`.builder_result.diagnostics | length`).MustEqual("1") + outJson.Query(`.builder_result.diagnostics`).MustContain(` + [ + { + "severity": "FATAL", + "message": "MissingWire.h: No such file or directory\n #include \u003cMissingWire.h\u003e\n ^~~~~~~~~~~~~~~", + "line": 2, + "column": 10, + } + ]`) + } + // Check that library discover do not generate false errors // https://github.com/arduino/arduino-cli/issues/2263 { diff --git a/internal/integrationtest/compile_3/testdata/blink_with_wrong_include/blink_with_wrong_include.ino b/internal/integrationtest/compile_3/testdata/blink_with_wrong_include/blink_with_wrong_include.ino new file mode 100644 index 00000000000..35887d391c9 --- /dev/null +++ b/internal/integrationtest/compile_3/testdata/blink_with_wrong_include/blink_with_wrong_include.ino @@ -0,0 +1,3 @@ +#wrong +void setup() {} +void loop() {} diff --git a/internal/integrationtest/compile_3/testdata/printenv/main.go b/internal/integrationtest/compile_3/testdata/printenv/main.go index 0701e4f1594..fc132a150eb 100644 --- a/internal/integrationtest/compile_3/testdata/printenv/main.go +++ b/internal/integrationtest/compile_3/testdata/printenv/main.go @@ -1,3 +1,18 @@ +// This file is part of arduino-cli. +// +// Copyright 2024 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + package main import ( diff --git a/internal/integrationtest/compile_3/testdata/using_Wire_with_missing_lib/using_Wire_with_missing_lib.ino b/internal/integrationtest/compile_3/testdata/using_Wire_with_missing_lib/using_Wire_with_missing_lib.ino new file mode 100644 index 00000000000..a461189885d --- /dev/null +++ b/internal/integrationtest/compile_3/testdata/using_Wire_with_missing_lib/using_Wire_with_missing_lib.ino @@ -0,0 +1,5 @@ +#include +#include + +void setup() {} +void loop() {}