Skip to content

Auto-import libraries based on sketch profile. #2951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Moved includeCache in his own file and made it a field of detector
  • Loading branch information
cmaglie committed Jul 7, 2025
commit 134bb28fe0787f6725129cfde5a5fe760b3f698e
121 changes: 13 additions & 108 deletions internal/arduino/builder/internal/detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type SketchLibrariesDetector struct {
librariesManager *librariesmanager.LibrariesManager
librariesResolver *librariesresolver.Cpp
useCachedLibrariesResolution bool
cache *includeCache
onlyUpdateCompilationDatabase bool
importedLibraries libraries.List
librariesResolutionResults map[string]libraryResolutionResult
Expand Down Expand Up @@ -184,13 +185,12 @@ func (l *SketchLibrariesDetector) IncludeFolders() paths.PathList {
// and should be the empty string for the default include folders, like
// the core or variant.
func (l *SketchLibrariesDetector) appendIncludeFolder(
cache *includeCache,
sourceFilePath *paths.Path,
include string,
folder *paths.Path,
) {
l.includeFolders = append(l.includeFolders, folder)
cache.ExpectEntry(sourceFilePath, include, folder)
l.cache.ExpectEntry(sourceFilePath, include, folder)
}

// FindIncludes todo
Expand Down Expand Up @@ -246,11 +246,11 @@ func (l *SketchLibrariesDetector) findIncludes(
}

cachePath := buildPath.Join("includes.cache")
cache := readCache(cachePath)
l.cache = readCache(cachePath)

l.appendIncludeFolder(cache, nil, "", buildCorePath)
l.appendIncludeFolder(nil, "", buildCorePath)
if buildVariantPath != nil {
l.appendIncludeFolder(cache, nil, "", buildVariantPath)
l.appendIncludeFolder(nil, "", buildVariantPath)
}

sourceFileQueue := &uniqueSourceFileQueue{}
Expand All @@ -270,16 +270,16 @@ func (l *SketchLibrariesDetector) findIncludes(
}

for !sourceFileQueue.Empty() {
err := l.findIncludesUntilDone(ctx, cache, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
err := l.findIncludesUntilDone(ctx, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
if err != nil {
cachePath.Remove()
return err
}
}

// Finalize the cache
cache.ExpectEnd()
if err := writeCache(cache, cachePath); err != nil {
l.cache.ExpectEnd()
if err := l.cache.write(cachePath); err != nil {
return err
}
}
Expand All @@ -299,7 +299,6 @@ func (l *SketchLibrariesDetector) findIncludes(

func (l *SketchLibrariesDetector) findIncludesUntilDone(
ctx context.Context,
cache *includeCache,
sourceFileQueue *uniqueSourceFileQueue,
buildProperties *properties.Map,
librariesBuildPath *paths.Path,
Expand Down Expand Up @@ -330,7 +329,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(

first := true
for {
cache.ExpectFile(sourcePath)
l.cache.ExpectFile(sourcePath)

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

var missingIncludeH string
if unchanged && cache.valid {
missingIncludeH = cache.Next().Include
if unchanged && l.cache.valid {
missingIncludeH = l.cache.Next().Include
if first && l.logger.VerbosityLevel() == logger.VerbosityVerbose {
l.logger.Info(i18n.Tr("Using cached library dependencies for file: %[1]s", sourcePath))
}
Expand All @@ -373,7 +372,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(

if missingIncludeH == "" {
// No missing includes found, we're done
cache.ExpectEntry(sourcePath, "", nil)
l.cache.ExpectEntry(sourcePath, "", nil)
return nil
}

Expand Down Expand Up @@ -406,7 +405,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
// include path and queue its source files for further
// include scanning
l.AppendImportedLibraries(library)
l.appendIncludeFolder(cache, sourcePath, missingIncludeH, library.SourceDir)
l.appendIncludeFolder(sourcePath, missingIncludeH, library.SourceDir)

if library.Precompiled && library.PrecompiledWithSources {
// Fully precompiled libraries should have no dependencies to avoid ABI breakage
Expand Down Expand Up @@ -595,97 +594,3 @@ func (entry *includeCacheEntry) String() string {
func (entry *includeCacheEntry) Equals(other *includeCacheEntry) bool {
return entry.String() == other.String()
}

type includeCache struct {
// Are the cache contents valid so far?
valid bool
// Index into entries of the next entry to be processed. Unused
// when the cache is invalid.
next int
entries []*includeCacheEntry
}

// Next Return the next cache entry. Should only be called when the cache is
// valid and a next entry is available (the latter can be checked with
// ExpectFile). Does not advance the cache.
func (cache *includeCache) Next() *includeCacheEntry {
return cache.entries[cache.next]
}

// ExpectFile check that the next cache entry is about the given file. If it is
// not, or no entry is available, the cache is invalidated. Does not
// advance the cache.
func (cache *includeCache) ExpectFile(sourcefile *paths.Path) {
if cache.valid && (cache.next >= len(cache.entries) || !cache.Next().Sourcefile.EqualsTo(sourcefile)) {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

// ExpectEntry check that the next entry matches the given values. If so, advance
// the cache. If not, the cache is invalidated. If the cache is
// invalidated, or was already invalid, an entry with the given values
// is appended.
func (cache *includeCache) ExpectEntry(sourcefile *paths.Path, include string, librarypath *paths.Path) {
entry := &includeCacheEntry{Sourcefile: sourcefile, Include: include, Includepath: librarypath}
if cache.valid {
if cache.next < len(cache.entries) && cache.Next().Equals(entry) {
cache.next++
} else {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

if !cache.valid {
cache.entries = append(cache.entries, entry)
}
}

// ExpectEnd check that the cache is completely consumed. If not, the cache is
// invalidated.
func (cache *includeCache) ExpectEnd() {
if cache.valid && cache.next < len(cache.entries) {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

// Read the cache from the given file
func readCache(path *paths.Path) *includeCache {
bytes, err := path.ReadFile()
if err != nil {
// Return an empty, invalid cache
return &includeCache{}
}
result := &includeCache{}
err = json.Unmarshal(bytes, &result.entries)
if err != nil {
// Return an empty, invalid cache
return &includeCache{}
}
result.valid = true
return result
}

// Write the given cache to the given file if it is invalidated. If the
// cache is still valid, just update the timestamps of the file.
func writeCache(cache *includeCache, path *paths.Path) error {
// If the cache was still valid all the way, just touch its file
// (in case any source file changed without influencing the
// includes). If it was invalidated, overwrite the cache with
// the new contents.
if cache.valid {
path.Chtimes(time.Now(), time.Now())
} else {
bytes, err := json.MarshalIndent(cache.entries, "", " ")
if err != nil {
return err
}
err = path.WriteFile(bytes)
if err != nil {
return err
}
}
return nil
}
117 changes: 117 additions & 0 deletions internal/arduino/builder/internal/detector/include_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// This file is part of arduino-cli.
//
// Copyright 2020 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 detector

import (
"encoding/json"
"time"

"github.com/arduino/go-paths-helper"
)

type includeCache struct {
// Are the cache contents valid so far?
valid bool
// Index into entries of the next entry to be processed. Unused
// when the cache is invalid.
next int
entries []*includeCacheEntry
}

// Next Return the next cache entry. Should only be called when the cache is
// valid and a next entry is available (the latter can be checked with
// ExpectFile). Does not advance the cache.
func (cache *includeCache) Next() *includeCacheEntry {
return cache.entries[cache.next]
}

// ExpectFile check that the next cache entry is about the given file. If it is
// not, or no entry is available, the cache is invalidated. Does not
// advance the cache.
func (cache *includeCache) ExpectFile(sourcefile *paths.Path) {
if cache.valid && (cache.next >= len(cache.entries) || !cache.Next().Sourcefile.EqualsTo(sourcefile)) {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

// ExpectEntry check that the next entry matches the given values. If so, advance
// the cache. If not, the cache is invalidated. If the cache is
// invalidated, or was already invalid, an entry with the given values
// is appended.
func (cache *includeCache) ExpectEntry(sourcefile *paths.Path, include string, librarypath *paths.Path) {
entry := &includeCacheEntry{Sourcefile: sourcefile, Include: include, Includepath: librarypath}
if cache.valid {
if cache.next < len(cache.entries) && cache.Next().Equals(entry) {
cache.next++
} else {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

if !cache.valid {
cache.entries = append(cache.entries, entry)
}
}

// ExpectEnd check that the cache is completely consumed. If not, the cache is
// invalidated.
func (cache *includeCache) ExpectEnd() {
if cache.valid && cache.next < len(cache.entries) {
cache.valid = false
cache.entries = cache.entries[:cache.next]
}
}

// Read the cache from the given file
func readCache(path *paths.Path) *includeCache {
bytes, err := path.ReadFile()
if err != nil {
// Return an empty, invalid cache
return &includeCache{}
}
result := &includeCache{}
err = json.Unmarshal(bytes, &result.entries)
if err != nil {
// Return an empty, invalid cache
return &includeCache{}
}
result.valid = true
return result
}

// Write the given cache to the given file if it is invalidated. If the
// cache is still valid, just update the timestamps of the file.
func (cache *includeCache) write(path *paths.Path) error {
// If the cache was still valid all the way, just touch its file
// (in case any source file changed without influencing the
// includes). If it was invalidated, overwrite the cache with
// the new contents.
if cache.valid {
path.Chtimes(time.Now(), time.Now())
} else {
bytes, err := json.MarshalIndent(cache.entries, "", " ")
if err != nil {
return err
}
err = path.WriteFile(bytes)
if err != nil {
return err
}
}
return nil
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy