From acea85a8138bb4e4d881c01a6af8ce0853a033af Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 29 Jul 2025 12:15:09 +0200 Subject: [PATCH 1/4] Avoid rebuilding the sketch if the sketch file is unchanged. --- .../builder/internal/preprocessor/ctags.go | 14 +++++++++++++- internal/arduino/builder/sketch.go | 18 +++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/internal/arduino/builder/internal/preprocessor/ctags.go b/internal/arduino/builder/internal/preprocessor/ctags.go index f66fbabf5cf..4cb453577b3 100644 --- a/internal/arduino/builder/internal/preprocessor/ctags.go +++ b/internal/arduino/builder/internal/preprocessor/ctags.go @@ -55,8 +55,20 @@ func PreprocessSketchWithCtags( stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} - // Run GCC preprocessor + // Check if the preprocessed file is already up-to-date + unpreprocessedSourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp.merged") sourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp") + if unpreprocessedStat, err := unpreprocessedSourceFile.Stat(); err != nil { + return nil, fmt.Errorf("%s: %w", i18n.Tr("unable to open unpreprocessed source file"), err) + } else if sourceStat, err := sourceFile.Stat(); err != nil { + return nil, fmt.Errorf("%s: %w", i18n.Tr("unable to open source file"), err) + } else if unpreprocessedStat.ModTime().Before(sourceStat.ModTime()) { + fmt.Fprintln(stdout, i18n.Tr("Sketch is unchanged, skipping preprocessing.")) + res := &runner.Result{Stdout: stdout.Bytes(), Stderr: stderr.Bytes()} + return res, nil + } + + // Run GCC preprocessor result := GCC(sourceFile, ctagsTarget, includes, buildProperties).Run(ctx) stdout.Write(result.Stdout) stderr.Write(result.Stderr) diff --git a/internal/arduino/builder/sketch.go b/internal/arduino/builder/sketch.go index 76e77f4c8e8..871cd421c7f 100644 --- a/internal/arduino/builder/sketch.go +++ b/internal/arduino/builder/sketch.go @@ -50,9 +50,21 @@ func (b *Builder) prepareSketchBuildPath() error { return err } - destFile := b.sketchBuildPath.Join(b.sketch.MainFile.Base() + ".cpp") - if err := destFile.WriteFile([]byte(mergedSource)); err != nil { - return err + // Save the unpreprocessed merged source to a file named sketch.cpp.merged. + destFileUnpreprocessed := b.sketchBuildPath.Join(b.sketch.MainFile.Base() + ".cpp.merged") + oldUnpreprocessedSource, _ := destFileUnpreprocessed.ReadFile() + + // If the merged source is unchanged, skip writing it. + // This avoids unnecessary rebuilds and keeps the build path clean. + if !bytes.Equal(oldUnpreprocessedSource, []byte(mergedSource)) { + if err := destFileUnpreprocessed.WriteFile([]byte(mergedSource)); err != nil { + return err + } + + destFile := b.sketchBuildPath.Join(b.sketch.MainFile.Base() + ".cpp") + if err := destFileUnpreprocessed.CopyTo(destFile); err != nil { + return err + } } if err := b.sketchCopyAdditionalFiles(b.sketchBuildPath, b.sourceOverrides); err != nil { From 9bae9e8ac6b9e4501c11925bd4ffaf971fd8c829 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 29 Jul 2025 12:15:42 +0200 Subject: [PATCH 2/4] Removed useless assignment --- internal/arduino/builder/internal/detector/detector.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/arduino/builder/internal/detector/detector.go b/internal/arduino/builder/internal/detector/detector.go index 0a724cfe948..8f75a05c68c 100644 --- a/internal/arduino/builder/internal/detector/detector.go +++ b/internal/arduino/builder/internal/detector/detector.go @@ -289,7 +289,6 @@ func (l *SketchLibrariesDetector) findIncludes( sourceFileQueue := &uniqueSourceFileQueue{} if !l.useCachedLibrariesResolution { - sketch := sketch mergedfile, err := makeSourceFile(sketchBuildPath, sketchBuildPath, paths.New(sketch.MainFile.Base()+".cpp")) if err != nil { return err From 2d449aa16749b14c7da69b83af0cd5c0853b6266 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 29 Jul 2025 12:16:28 +0200 Subject: [PATCH 3/4] Moved the result closer to the actual action --- internal/arduino/builder/sketch.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/arduino/builder/sketch.go b/internal/arduino/builder/sketch.go index 871cd421c7f..42ef3a2f759 100644 --- a/internal/arduino/builder/sketch.go +++ b/internal/arduino/builder/sketch.go @@ -49,6 +49,7 @@ func (b *Builder) prepareSketchBuildPath() error { if err != nil { return err } + b.lineOffset = offset // Save the unpreprocessed merged source to a file named sketch.cpp.merged. destFileUnpreprocessed := b.sketchBuildPath.Join(b.sketch.MainFile.Base() + ".cpp.merged") @@ -71,8 +72,6 @@ func (b *Builder) prepareSketchBuildPath() error { return err } - b.lineOffset = offset - return nil } From aa3337355e488e61be35d06b91414b3801dc9735 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 29 Jul 2025 17:33:37 +0200 Subject: [PATCH 4/4] Split composed sketch and preprocessed sketch into different files --- .../arduino/builder/internal/detector/detector.go | 2 +- .../arduino/builder/internal/preprocessor/ctags.go | 14 ++++++-------- internal/arduino/builder/sketch.go | 5 ----- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/internal/arduino/builder/internal/detector/detector.go b/internal/arduino/builder/internal/detector/detector.go index 8f75a05c68c..757e2c86cdb 100644 --- a/internal/arduino/builder/internal/detector/detector.go +++ b/internal/arduino/builder/internal/detector/detector.go @@ -289,7 +289,7 @@ func (l *SketchLibrariesDetector) findIncludes( sourceFileQueue := &uniqueSourceFileQueue{} if !l.useCachedLibrariesResolution { - mergedfile, err := makeSourceFile(sketchBuildPath, sketchBuildPath, paths.New(sketch.MainFile.Base()+".cpp")) + mergedfile, err := makeSourceFile(sketchBuildPath, sketchBuildPath, paths.New(sketch.MainFile.Base()+".cpp.merged")) if err != nil { return err } diff --git a/internal/arduino/builder/internal/preprocessor/ctags.go b/internal/arduino/builder/internal/preprocessor/ctags.go index 4cb453577b3..c8801aeb3df 100644 --- a/internal/arduino/builder/internal/preprocessor/ctags.go +++ b/internal/arduino/builder/internal/preprocessor/ctags.go @@ -57,19 +57,17 @@ func PreprocessSketchWithCtags( // Check if the preprocessed file is already up-to-date unpreprocessedSourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp.merged") - sourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp") + preprocessedSourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp") if unpreprocessedStat, err := unpreprocessedSourceFile.Stat(); err != nil { return nil, fmt.Errorf("%s: %w", i18n.Tr("unable to open unpreprocessed source file"), err) - } else if sourceStat, err := sourceFile.Stat(); err != nil { - return nil, fmt.Errorf("%s: %w", i18n.Tr("unable to open source file"), err) - } else if unpreprocessedStat.ModTime().Before(sourceStat.ModTime()) { + } else if sourceStat, err := preprocessedSourceFile.Stat(); err == nil && unpreprocessedStat.ModTime().Before(sourceStat.ModTime()) { fmt.Fprintln(stdout, i18n.Tr("Sketch is unchanged, skipping preprocessing.")) res := &runner.Result{Stdout: stdout.Bytes(), Stderr: stderr.Bytes()} return res, nil } // Run GCC preprocessor - result := GCC(sourceFile, ctagsTarget, includes, buildProperties).Run(ctx) + result := GCC(unpreprocessedSourceFile, ctagsTarget, includes, buildProperties).Run(ctx) stdout.Write(result.Stdout) stderr.Write(result.Stderr) if err := result.Error; err != nil { @@ -81,7 +79,7 @@ func PreprocessSketchWithCtags( fmt.Fprintf(stderr, "%s: %s", i18n.Tr("An error occurred adding prototypes"), i18n.Tr("the compilation database may be incomplete or inaccurate")) - if err := sourceFile.CopyTo(ctagsTarget); err != nil { + if err := unpreprocessedSourceFile.CopyTo(ctagsTarget); err != nil { return &runner.Result{Args: result.Args, Stdout: stdout.Bytes(), Stderr: stderr.Bytes()}, err } } @@ -114,7 +112,7 @@ func PreprocessSketchWithCtags( // Add prototypes to the original sketch source var source string - if sourceData, err := sourceFile.ReadFile(); err == nil { + if sourceData, err := unpreprocessedSourceFile.ReadFile(); err == nil { source = string(sourceData) } else { return &runner.Result{Args: result.Args, Stdout: stdout.Bytes(), Stderr: stderr.Bytes()}, err @@ -148,7 +146,7 @@ func PreprocessSketchWithCtags( } // Write back arduino-preprocess output to the sourceFile - err = sourceFile.WriteFile([]byte(preprocessedSource)) + err = preprocessedSourceFile.WriteFile([]byte(preprocessedSource)) return &runner.Result{Args: result.Args, Stdout: stdout.Bytes(), Stderr: stderr.Bytes()}, err } diff --git a/internal/arduino/builder/sketch.go b/internal/arduino/builder/sketch.go index 42ef3a2f759..2fea32f80e7 100644 --- a/internal/arduino/builder/sketch.go +++ b/internal/arduino/builder/sketch.go @@ -61,11 +61,6 @@ func (b *Builder) prepareSketchBuildPath() error { if err := destFileUnpreprocessed.WriteFile([]byte(mergedSource)); err != nil { return err } - - destFile := b.sketchBuildPath.Join(b.sketch.MainFile.Base() + ".cpp") - if err := destFileUnpreprocessed.CopyTo(destFile); err != nil { - return err - } } if err := b.sketchCopyAdditionalFiles(b.sketchBuildPath, b.sourceOverrides); err != nil {