Skip to content

Commit f43262c

Browse files
committed
Implemented a better include-cache
1 parent c571095 commit f43262c

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 [email protected].
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
@@ -52,7 +52,7 @@ type SketchLibrariesDetector struct {
5252
librariesManager *librariesmanager.LibrariesManager
5353
librariesResolver *librariesresolver.Cpp
5454
useCachedLibrariesResolution bool
55-
cache *includeCache
55+
cache *detectorCache
5656
onlyUpdateCompilationDatabase bool
5757
importedLibraries libraries.List
5858
librariesResolutionResults map[string]libraryResolutionResult
@@ -74,6 +74,7 @@ func NewSketchLibrariesDetector(
7474
librariesManager: lm,
7575
librariesResolver: libsResolver,
7676
useCachedLibrariesResolution: useCachedLibrariesResolution,
77+
cache: newDetectorCache(),
7778
librariesResolutionResults: map[string]libraryResolutionResult{},
7879
importedLibraries: libraries.List{},
7980
includeFolders: paths.PathList{},
@@ -175,21 +176,10 @@ func (l *SketchLibrariesDetector) IncludeFolders() paths.PathList {
175176
return l.includeFolders
176177
}
177178

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

195185
// FindIncludes todo
@@ -245,11 +235,13 @@ func (l *SketchLibrariesDetector) findIncludes(
245235
}
246236

247237
cachePath := buildPath.Join("includes.cache")
248-
l.cache = readCache(cachePath)
238+
if err := l.cache.Load(cachePath); err != nil {
239+
l.logger.Warn(i18n.Tr("Failed to load library discovery cache: %[1]s", err))
240+
}
249241

250-
l.appendIncludeFolder(nil, "", buildCorePath)
242+
l.addIncludeFolder(buildCorePath)
251243
if buildVariantPath != nil {
252-
l.appendIncludeFolder(nil, "", buildVariantPath)
244+
l.addIncludeFolder(buildVariantPath)
253245
}
254246

255247
sourceFileQueue := &uniqueSourceFileQueue{}
@@ -269,16 +261,15 @@ func (l *SketchLibrariesDetector) findIncludes(
269261
}
270262

271263
for !sourceFileQueue.Empty() {
272-
err := l.findIncludesUntilDone(ctx, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
264+
err := l.findMissingIncludesInCompilationUnit(ctx, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
273265
if err != nil {
274266
cachePath.Remove()
275267
return err
276268
}
277269
}
278270

279271
// Finalize the cache
280-
l.cache.ExpectEnd()
281-
if err := l.cache.write(cachePath); err != nil {
272+
if err := l.cache.Save(cachePath); err != nil {
282273
return err
283274
}
284275
}
@@ -296,7 +287,7 @@ func (l *SketchLibrariesDetector) findIncludes(
296287
return nil
297288
}
298289

299-
func (l *SketchLibrariesDetector) findIncludesUntilDone(
290+
func (l *SketchLibrariesDetector) findMissingIncludesInCompilationUnit(
300291
ctx context.Context,
301292
sourceFileQueue *uniqueSourceFileQueue,
302293
buildProperties *properties.Map,
@@ -328,7 +319,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
328319

329320
first := true
330321
for {
331-
l.cache.ExpectFile(sourcePath)
322+
l.cache.Expect(&detectorCacheEntry{CompilingSourcePath: sourcePath})
332323

333324
// Libraries may require the "utility" directory to be added to the include
334325
// search path, but only for the source code of the library, so we temporary
@@ -343,8 +334,8 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
343334
var preprocFirstResult *runner.Result
344335

345336
var missingIncludeH string
346-
if unchanged && l.cache.valid {
347-
missingIncludeH = l.cache.Next().Include
337+
if entry := l.cache.Peek(); unchanged && entry != nil && entry.MissingIncludeH != nil {
338+
missingIncludeH = *entry.MissingIncludeH
348339
if first && l.logger.VerbosityLevel() == logger.VerbosityVerbose {
349340
l.logger.Info(i18n.Tr("Using cached library dependencies for file: %[1]s", sourcePath))
350341
}
@@ -370,9 +361,10 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
370361
}
371362
}
372363

364+
l.cache.Expect(&detectorCacheEntry{MissingIncludeH: &missingIncludeH})
365+
373366
if missingIncludeH == "" {
374367
// No missing includes found, we're done
375-
l.cache.ExpectEntry(sourcePath, "", nil)
376368
return nil
377369
}
378370

@@ -405,7 +397,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
405397
// include path and queue its source files for further
406398
// include scanning
407399
l.AppendImportedLibraries(library)
408-
l.appendIncludeFolder(sourcePath, missingIncludeH, library.SourceDir)
400+
l.addIncludeFolder(library.SourceDir)
409401

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

0 commit comments

Comments
 (0)