Skip to content

Commit 6631b1d

Browse files
committed
Implemented a better include-cache
1 parent b1bdc03 commit 6631b1d

File tree

2 files changed

+141
-27
lines changed

2 files changed

+141
-27
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2024 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 license@arduino.cc.
15+
16+
package detector
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
22+
"github.com/arduino/go-paths-helper"
23+
)
24+
25+
type detectorCache struct {
26+
curr int
27+
entries []*detectorCacheEntry
28+
}
29+
30+
type detectorCacheEntry struct {
31+
AddedIncludePath *paths.Path `json:"added_include_path,omitempty"`
32+
CompilingSourcePath *paths.Path `json:"compiling_source_path,omitempty"`
33+
MissingIncludeH *string `json:"missing_include_h,omitempty"`
34+
}
35+
36+
func (e *detectorCacheEntry) String() string {
37+
if e.AddedIncludePath != nil {
38+
return "Added include path: " + e.AddedIncludePath.String()
39+
}
40+
if e.CompilingSourcePath != nil {
41+
return "Compiling source path: " + e.CompilingSourcePath.String()
42+
}
43+
if e.MissingIncludeH != nil {
44+
if *e.MissingIncludeH == "" {
45+
return "No missing include files detected"
46+
}
47+
return "Missing include file: " + *e.MissingIncludeH
48+
}
49+
return "No operation"
50+
}
51+
52+
func (e *detectorCacheEntry) Equals(entry *detectorCacheEntry) bool {
53+
return e.String() == entry.String()
54+
}
55+
56+
func newDetectorCache() *detectorCache {
57+
return &detectorCache{}
58+
}
59+
60+
func (c *detectorCache) String() string {
61+
res := ""
62+
for _, entry := range c.entries {
63+
res += fmt.Sprintln(entry)
64+
}
65+
return res
66+
}
67+
68+
// Load reads a saved cache from the given file.
69+
// If the file do not exists, it does nothing.
70+
func (c *detectorCache) Load(cacheFile *paths.Path) error {
71+
if exist, err := cacheFile.ExistCheck(); err != nil {
72+
return err
73+
} else if !exist {
74+
return nil
75+
}
76+
data, err := cacheFile.ReadFile()
77+
if err != nil {
78+
return err
79+
}
80+
var entries []*detectorCacheEntry
81+
if err := json.Unmarshal(data, &entries); err != nil {
82+
return err
83+
}
84+
c.curr = 0
85+
c.entries = entries
86+
return nil
87+
}
88+
89+
// Expect adds an entry to the cache and checks if it matches the next expected entry.
90+
func (c *detectorCache) Expect(entry *detectorCacheEntry) {
91+
if c.curr < len(c.entries) {
92+
if c.entries[c.curr].Equals(entry) {
93+
// Cache hit, move to the next entry
94+
c.curr++
95+
return
96+
}
97+
// Cache mismatch, invalidate and cut the remainder of the cache
98+
c.entries = c.entries[:c.curr]
99+
}
100+
c.curr++
101+
c.entries = append(c.entries, entry)
102+
}
103+
104+
// Peek returns the next cache entry to be expected or nil if the cache is fully consumed.
105+
func (c *detectorCache) Peek() *detectorCacheEntry {
106+
if c.curr < len(c.entries) {
107+
return c.entries[c.curr]
108+
}
109+
return nil
110+
}
111+
112+
// Save writes the current cache to the given file.
113+
func (c *detectorCache) Save(cacheFile *paths.Path) error {
114+
// Cut off the cache if it is not fully consumed
115+
c.entries = c.entries[:c.curr]
116+
117+
data, err := json.MarshalIndent(c.entries, "", " ")
118+
if err != nil {
119+
return err
120+
}
121+
return cacheFile.WriteFile(data)
122+
}

internal/arduino/builder/internal/detector/detector.go

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type libraryResolutionResult struct {
5151
type SketchLibrariesDetector struct {
5252
librariesResolver *librariesresolver.Cpp
5353
useCachedLibrariesResolution bool
54-
cache *includeCache
54+
cache *detectorCache
5555
onlyUpdateCompilationDatabase bool
5656
importedLibraries libraries.List
5757
librariesResolutionResults map[string]libraryResolutionResult
@@ -71,6 +71,7 @@ func NewSketchLibrariesDetector(
7171
return &SketchLibrariesDetector{
7272
librariesResolver: libsResolver,
7373
useCachedLibrariesResolution: useCachedLibrariesResolution,
74+
cache: newDetectorCache(),
7475
librariesResolutionResults: map[string]libraryResolutionResult{},
7576
importedLibraries: libraries.List{},
7677
includeFolders: paths.PathList{},
@@ -172,21 +173,10 @@ func (l *SketchLibrariesDetector) IncludeFolders() paths.PathList {
172173
return l.includeFolders
173174
}
174175

175-
// appendIncludeFolder todo should rename this, probably after refactoring the
176-
// container_find_includes command.
177-
// Original comment:
178-
// Append the given folder to the include path and match or append it to
179-
// the cache. sourceFilePath and include indicate the source of this
180-
// include (e.g. what #include line in what file it was resolved from)
181-
// and should be the empty string for the default include folders, like
182-
// the core or variant.
183-
func (l *SketchLibrariesDetector) appendIncludeFolder(
184-
sourceFilePath *paths.Path,
185-
include string,
186-
folder *paths.Path,
187-
) {
176+
// addIncludeFolder add the given folder to the include path.
177+
func (l *SketchLibrariesDetector) addIncludeFolder(folder *paths.Path) {
188178
l.includeFolders = append(l.includeFolders, folder)
189-
l.cache.ExpectEntry(sourceFilePath, include, folder)
179+
l.cache.Expect(&detectorCacheEntry{AddedIncludePath: folder})
190180
}
191181

192182
// FindIncludes todo
@@ -242,11 +232,13 @@ func (l *SketchLibrariesDetector) findIncludes(
242232
}
243233

244234
cachePath := buildPath.Join("includes.cache")
245-
l.cache = readCache(cachePath)
235+
if err := l.cache.Load(cachePath); err != nil {
236+
l.logger.Warn(i18n.Tr("Failed to load library discovery cache: %[1]s", err))
237+
}
246238

247-
l.appendIncludeFolder(nil, "", buildCorePath)
239+
l.addIncludeFolder(buildCorePath)
248240
if buildVariantPath != nil {
249-
l.appendIncludeFolder(nil, "", buildVariantPath)
241+
l.addIncludeFolder(buildVariantPath)
250242
}
251243

252244
sourceFileQueue := &uniqueSourceFileQueue{}
@@ -266,16 +258,15 @@ func (l *SketchLibrariesDetector) findIncludes(
266258
}
267259

268260
for !sourceFileQueue.Empty() {
269-
err := l.findIncludesUntilDone(ctx, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
261+
err := l.findMissingIncludesInCompilationUnit(ctx, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
270262
if err != nil {
271263
cachePath.Remove()
272264
return err
273265
}
274266
}
275267

276268
// Finalize the cache
277-
l.cache.ExpectEnd()
278-
if err := l.cache.write(cachePath); err != nil {
269+
if err := l.cache.Save(cachePath); err != nil {
279270
return err
280271
}
281272
}
@@ -293,7 +284,7 @@ func (l *SketchLibrariesDetector) findIncludes(
293284
return nil
294285
}
295286

296-
func (l *SketchLibrariesDetector) findIncludesUntilDone(
287+
func (l *SketchLibrariesDetector) findMissingIncludesInCompilationUnit(
297288
ctx context.Context,
298289
sourceFileQueue *uniqueSourceFileQueue,
299290
buildProperties *properties.Map,
@@ -325,7 +316,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
325316

326317
first := true
327318
for {
328-
l.cache.ExpectFile(sourcePath)
319+
l.cache.Expect(&detectorCacheEntry{CompilingSourcePath: sourcePath})
329320

330321
// Libraries may require the "utility" directory to be added to the include
331322
// search path, but only for the source code of the library, so we temporary
@@ -340,8 +331,8 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
340331
var preprocFirstResult *runner.Result
341332

342333
var missingIncludeH string
343-
if unchanged && l.cache.valid {
344-
missingIncludeH = l.cache.Next().Include
334+
if entry := l.cache.Peek(); unchanged && entry != nil && entry.MissingIncludeH != nil {
335+
missingIncludeH = *entry.MissingIncludeH
345336
if first && l.logger.Verbose() {
346337
l.logger.Info(i18n.Tr("Using cached library dependencies for file: %[1]s", sourcePath))
347338
}
@@ -367,9 +358,10 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
367358
}
368359
}
369360

361+
l.cache.Expect(&detectorCacheEntry{MissingIncludeH: &missingIncludeH})
362+
370363
if missingIncludeH == "" {
371364
// No missing includes found, we're done
372-
l.cache.ExpectEntry(sourcePath, "", nil)
373365
return nil
374366
}
375367

@@ -402,7 +394,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
402394
// include path and queue its source files for further
403395
// include scanning
404396
l.AppendImportedLibraries(library)
405-
l.appendIncludeFolder(sourcePath, missingIncludeH, library.SourceDir)
397+
l.addIncludeFolder(library.SourceDir)
406398

407399
if library.Precompiled && library.PrecompiledWithSources {
408400
// Fully precompiled libraries should have no dependencies to avoid ABI breakage

0 commit comments

Comments
 (0)