diff --git a/src/arduino.cc/builder/add_additional_entries_to_context.go b/src/arduino.cc/builder/add_additional_entries_to_context.go index a4250c88..91bc2a0e 100644 --- a/src/arduino.cc/builder/add_additional_entries_to_context.go +++ b/src/arduino.cc/builder/add_additional_entries_to_context.go @@ -68,8 +68,7 @@ func (s *AddAdditionalEntriesToContext) Run(ctx *types.Context) error { ctx.WarningsLevel = DEFAULT_WARNINGS_LEVEL } - ctx.CollectedSourceFiles = &types.UniqueStringQueue{} - ctx.FoldersWithSourceFiles = &types.UniqueSourceFolderQueue{} + ctx.CollectedSourceFiles = &types.UniqueSourceFileQueue{} ctx.LibrariesResolutionResults = make(map[string]types.LibraryResolutionResult) ctx.HardwareRewriteResults = make(map[*types.Platform][]types.PlatforKeyRewrite) diff --git a/src/arduino.cc/builder/collect_all_source_files_from_folders_with_sources.go b/src/arduino.cc/builder/collect_all_source_files_from_folders_with_sources.go deleted file mode 100644 index b9bc589f..00000000 --- a/src/arduino.cc/builder/collect_all_source_files_from_folders_with_sources.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of Arduino Builder. - * - * Arduino Builder is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * As a special exception, you may use this file as part of a free software - * library without restriction. Specifically, if other files instantiate - * templates or use macros or inline functions from this file, or you compile - * this file and link it with other files to produce an executable, this - * file does not by itself cause the resulting executable to be covered by - * the GNU General Public License. This exception does not however - * invalidate any other reasons why the executable file might be covered by - * the GNU General Public License. - * - * Copyright 2015 Arduino LLC (http://www.arduino.cc/) - */ - -package builder - -import ( - "arduino.cc/builder/i18n" - "arduino.cc/builder/types" - "arduino.cc/builder/utils" -) - -type CollectAllSourceFilesFromFoldersWithSources struct{} - -func (s *CollectAllSourceFilesFromFoldersWithSources) Run(ctx *types.Context) error { - foldersWithSources := ctx.FoldersWithSourceFiles - sourceFiles := ctx.CollectedSourceFiles - extensions := func(ext string) bool { return ADDITIONAL_FILE_VALID_EXTENSIONS_NO_HEADERS[ext] } - - filePaths := []string{} - for !foldersWithSources.Empty() { - sourceFolder := foldersWithSources.Pop().(types.SourceFolder) - err := utils.FindFilesInFolder(&filePaths, sourceFolder.Folder, extensions, sourceFolder.Recurse) - if err != nil { - return i18n.WrapError(err) - } - } - - for _, filePath := range filePaths { - sourceFiles.Push(filePath) - } - - return nil -} diff --git a/src/arduino.cc/builder/constants/constants.go b/src/arduino.cc/builder/constants/constants.go index bf5cfc35..62147d62 100644 --- a/src/arduino.cc/builder/constants/constants.go +++ b/src/arduino.cc/builder/constants/constants.go @@ -84,6 +84,7 @@ const FILE_PLATFORM_KEYS_REWRITE_TXT = "platform.keys.rewrite.txt" const FILE_PLATFORM_LOCAL_TXT = "platform.local.txt" const FILE_PLATFORM_TXT = "platform.txt" const FILE_PROGRAMMERS_TXT = "programmers.txt" +const FILE_INCLUDES_CACHE = "includes.cache" const FOLDER_BOOTLOADERS = "bootloaders" const FOLDER_CORE = "core" const FOLDER_CORES = "cores" @@ -180,6 +181,7 @@ const MSG_USING_LIBRARY = "Using library {0} in folder: {1} {2}" const MSG_USING_BOARD = "Using board '{0}' from platform in folder: {1}" const MSG_USING_CORE = "Using core '{0}' from platform in folder: {1}" const MSG_USING_PREVIOUS_COMPILED_FILE = "Using previously compiled file: {0}" +const MSG_USING_CACHED_INCLUDES = "Using cached library dependencies for file: {0}" const MSG_WARNING_LIB_INVALID_CATEGORY = "WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}'" const MSG_WARNING_PLATFORM_MISSING_VALUE = "Warning: platform.txt from core '{0}' misses property '{1}', using default value '{2}'. Consider upgrading this core." const MSG_WARNING_PLATFORM_OLD_VALUES = "Warning: platform.txt from core '{0}' contains deprecated {1}, automatically converted to {2}. Consider upgrading this core." diff --git a/src/arduino.cc/builder/container_add_prototypes.go b/src/arduino.cc/builder/container_add_prototypes.go index 8a138b44..378a0689 100644 --- a/src/arduino.cc/builder/container_add_prototypes.go +++ b/src/arduino.cc/builder/container_add_prototypes.go @@ -30,6 +30,8 @@ package builder import ( + "path/filepath" + "arduino.cc/builder/constants" "arduino.cc/builder/ctags" "arduino.cc/builder/i18n" @@ -39,8 +41,9 @@ import ( type ContainerAddPrototypes struct{} func (s *ContainerAddPrototypes) Run(ctx *types.Context) error { + sourceFile := filepath.Join(ctx.SketchBuildPath, filepath.Base(ctx.Sketch.MainFile.Name)+".cpp") commands := []types.Command{ - &GCCPreprocRunner{TargetFileName: constants.FILE_CTAGS_TARGET_FOR_GCC_MINUS_E}, + &GCCPreprocRunner{SourceFilePath: sourceFile, TargetFileName: constants.FILE_CTAGS_TARGET_FOR_GCC_MINUS_E, Includes: ctx.IncludeFolders}, &ReadFileAndStoreInContext{Target: &ctx.SourceGccMinusE}, &FilterSketchSource{Source: &ctx.SourceGccMinusE}, &CTagsTargetFileSaver{Source: &ctx.SourceGccMinusE, TargetFileName: constants.FILE_CTAGS_TARGET_FOR_GCC_MINUS_E}, diff --git a/src/arduino.cc/builder/container_find_includes.go b/src/arduino.cc/builder/container_find_includes.go index 7d2e05c5..096e4a60 100644 --- a/src/arduino.cc/builder/container_find_includes.go +++ b/src/arduino.cc/builder/container_find_includes.go @@ -27,12 +27,93 @@ * Copyright 2015 Arduino LLC (http://www.arduino.cc/) */ +/* + +Include detection + +This code is responsible for figuring out what libraries the current +sketch needs an populates both Context.ImportedLibraries with a list of +Library objects, and Context.IncludeFolders with a list of folders to +put on the include path. + +Simply put, every #include in a source file pulls in the library that +provides that source file. This includes source files in the selected +libraries, so libraries can recursively include other libraries as well. + +To implement this, the gcc preprocessor is used. A queue is created +containing, at first, the source files in the sketch. Each of the files +in the queue is processed in turn by running the preprocessor on it. If +the preprocessor provides an error, the output is examined to see if the +error is a missing header file originating from a #include directive. + +The filename is extracted from that #include directive, and a library is +found that provides it. If multiple libraries provide the same file, one +is slected (how this selection works is not described here, see the +ResolveLibrary function for that). The library selected in this way is +added to the include path through Context.IncludeFolders and the list of +libraries to include in the link through Context.ImportedLibraries. + +Furthermore, all of the library source files are added to the queue, to +be processed as well. When the preprocessor completes without showing an +#include error, processing of the file is complete and it advances to +the next. When no library can be found for a included filename, an error +is shown and the process is aborted. + +Caching + +Since this process is fairly slow (requiring at least one invocation of +the preprocessor per source file), its results are cached. + +Just caching the complete result (i.e. the resulting list of imported +libraries) seems obvious, but such a cache is hard to invalidate. Making +a list of all the source and header files used to create the list and +check if any of them changed is probably feasible, but this would also +require caching the full list of libraries to invalidate the cache when +the include to library resolution might have a different result. Another +downside of a complete cache is that any changes requires re-running +everything, even if no includes were actually changed. + +Instead, caching happens by keeping a sort of "journal" of the steps in +the include detection, essentially tracing each file processed and each +include path entry added. The cache is used by retracing these steps: +The include detection process is executed normally, except that instead +of running the preprocessor, the include filenames are (when possible) +read from the cache. Then, the include file to library resolution is +again executed normally. The results are checked against the cache and +as long as the results match, the cache is considered valid. + +When a source file (or any of the files it includes, as indicated by the +.d file) is changed, the preprocessor is executed as normal for the +file, ignoring any includes from the cache. This does not, however, +invalidate the cache: If the results from the preprocessor match the +entries in the cache, the cache remains valid and can again be used for +the next (unchanged) file. + +The cache file uses the JSON format and contains a list of entries. Each +entry represents a discovered library and contains: + - Sourcefile: The source file that the include was found in + - Include: The included filename found + - Includepath: The addition to the include path + +There are also some special entries: + - When adding the initial include path entries, such as for the core + and variant paths. These are not discovered, so the Sourcefile and + Include fields will be empty. + - When a file contains no (more) missing includes, an entry with an + empty Include and IncludePath is generated. + +*/ + package builder import ( + "encoding/json" + "io/ioutil" "os" "path/filepath" + "time" + "arduino.cc/builder/builder_utils" "arduino.cc/builder/constants" "arduino.cc/builder/i18n" "arduino.cc/builder/types" @@ -42,51 +123,43 @@ import ( type ContainerFindIncludes struct{} func (s *ContainerFindIncludes) Run(ctx *types.Context) error { - err := runCommand(ctx, &IncludesToIncludeFolders{}) - if err != nil { - return i18n.WrapError(err) + cachePath := filepath.Join(ctx.BuildPath, constants.FILE_INCLUDES_CACHE) + cache := readCache(cachePath) + + appendIncludeFolder(ctx, cache, "", "", ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_CORE_PATH]) + if ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH] != constants.EMPTY_STRING { + appendIncludeFolder(ctx, cache, "", "", ctx.BuildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH]) } - sketchBuildPath := ctx.SketchBuildPath sketch := ctx.Sketch - err = findIncludesUntilDone(ctx, filepath.Join(sketchBuildPath, filepath.Base(sketch.MainFile.Name)+".cpp")) + mergedfile, err := types.MakeSourceFile(ctx, sketch, filepath.Base(sketch.MainFile.Name)+".cpp") if err != nil { return i18n.WrapError(err) } + ctx.CollectedSourceFiles.Push(mergedfile) - foldersWithSources := ctx.FoldersWithSourceFiles - foldersWithSources.Push(types.SourceFolder{Folder: ctx.SketchBuildPath, Recurse: false}) + sourceFilePaths := ctx.CollectedSourceFiles + queueSourceFilesFromFolder(ctx, sourceFilePaths, sketch, ctx.SketchBuildPath, /* recurse */ false) srcSubfolderPath := filepath.Join(ctx.SketchBuildPath, constants.SKETCH_FOLDER_SRC) if info, err := os.Stat(srcSubfolderPath); err == nil && info.IsDir() { - foldersWithSources.Push(types.SourceFolder{Folder: srcSubfolderPath, Recurse: true}) - } - if len(ctx.ImportedLibraries) > 0 { - for _, library := range ctx.ImportedLibraries { - sourceFolders := types.LibraryToSourceFolder(library) - for _, sourceFolder := range sourceFolders { - foldersWithSources.Push(sourceFolder) - } - } - } - - err = runCommand(ctx, &CollectAllSourceFilesFromFoldersWithSources{}) - if err != nil { - return i18n.WrapError(err) + queueSourceFilesFromFolder(ctx, sourceFilePaths, sketch, srcSubfolderPath, /* recurse */ true) } - sourceFilePaths := ctx.CollectedSourceFiles - for !sourceFilePaths.Empty() { - err = findIncludesUntilDone(ctx, sourceFilePaths.Pop().(string)) - if err != nil { - return i18n.WrapError(err) - } - err := runCommand(ctx, &CollectAllSourceFilesFromFoldersWithSources{}) + err := findIncludesUntilDone(ctx, cache, sourceFilePaths.Pop()) if err != nil { + os.Remove(cachePath) return i18n.WrapError(err) } } + // Finalize the cache + cache.ExpectEnd() + err = writeCache(cache, cachePath) + if err != nil { + return i18n.WrapError(err) + } + err = runCommand(ctx, &FailIfImportedLibraryIsWrong{}) if err != nil { return i18n.WrapError(err) @@ -95,6 +168,16 @@ func (s *ContainerFindIncludes) Run(ctx *types.Context) error { return nil } +// Append the given folder to the include path and match or append it to +// the cache. sourceFilePath and include indicate the source of this +// include (e.g. what #include line in what file it was resolved from) +// and should be the empty string for the default include folders, like +// the core or variant. +func appendIncludeFolder(ctx *types.Context, cache *includeCache, sourceFilePath string, include string, folder string) { + ctx.IncludeFolders = append(ctx.IncludeFolders, folder) + cache.ExpectEntry(sourceFilePath, include, folder) +} + func runCommand(ctx *types.Context, command types.Command) error { PrintRingNameIfDebug(ctx, command) err := command.Run(ctx) @@ -104,30 +187,197 @@ func runCommand(ctx *types.Context, command types.Command) error { return nil } -func findIncludesUntilDone(ctx *types.Context, sourceFilePath string) error { +type includeCacheEntry struct { + Sourcefile string + Include string + Includepath 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 +} + +// 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] +} + +// 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 string) { + if cache.valid && cache.next < len(cache.entries) && cache.Next().Sourcefile != sourcefile { + cache.valid = false + cache.entries = cache.entries[:cache.next] + } +} + +// 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 string, include string, librarypath string) { + entry := includeCacheEntry{Sourcefile: sourcefile, Include: include, Includepath: librarypath} + if cache.valid { + if cache.next < len(cache.entries) && cache.Next() == entry { + cache.next++ + } else { + cache.valid = false + cache.entries = cache.entries[:cache.next] + } + } + + if !cache.valid { + cache.entries = append(cache.entries, entry) + } +} + +// 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 string) *includeCache { + bytes, err := ioutil.ReadFile(path) + 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 string) 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 { + os.Chtimes(path, time.Now(), time.Now()) + } else { + bytes, err := json.MarshalIndent(cache.entries, "", " ") + if err != nil { + return i18n.WrapError(err) + } + err = utils.WriteFileBytes(path, bytes) + if err != nil { + return i18n.WrapError(err) + } + } + return nil +} + +func findIncludesUntilDone(ctx *types.Context, cache *includeCache, sourceFile types.SourceFile) error { + sourcePath := sourceFile.SourcePath(ctx) targetFilePath := utils.NULLFile() - importedLibraries := ctx.ImportedLibraries - done := false - for !done { - commands := []types.Command{ - &GCCPreprocRunnerForDiscoveringIncludes{SourceFilePath: sourceFilePath, TargetFilePath: targetFilePath}, - &IncludesFinderWithRegExp{Source: &ctx.SourceGccMinusE}, - &IncludesToIncludeFolders{}, + + // TODO: This should perhaps also compare against the + // include.cache file timestamp. Now, it only checks if the file + // changed after the object file was generated, but if it + // changed between generating the cache and the object file, + // this could show the file as unchanged when it really is + // changed. Changing files during a build isn't really + // supported, but any problems from it should at least be + // resolved when doing another build, which is not currently the + // case. + // TODO: This reads the dependency file, but the actual building + // does it again. Should the result be somehow cached? Perhaps + // remove the object file if it is found to be stale? + unchanged, err := builder_utils.ObjFileIsUpToDate(sourcePath, sourceFile.ObjectPath(ctx), sourceFile.DepfilePath(ctx)) + if err != nil { + return i18n.WrapError(err) + } + + first := true + for { + var include string + cache.ExpectFile(sourcePath) + + includes := ctx.IncludeFolders + if library, ok := sourceFile.Origin.(*types.Library); ok && library.UtilityFolder != "" { + includes = append(includes, library.UtilityFolder) } - for _, command := range commands { - err := runCommand(ctx, command) - if err != nil { - return i18n.WrapError(err) + if unchanged && cache.valid { + include = cache.Next().Include + if first && ctx.Verbose { + ctx.GetLogger().Println(constants.LOG_LEVEL_INFO, constants.MSG_USING_CACHED_INCLUDES, sourcePath) } + } else { + commands := []types.Command{ + &GCCPreprocRunnerForDiscoveringIncludes{SourceFilePath: sourcePath, TargetFilePath: targetFilePath, Includes: includes}, + &IncludesFinderWithRegExp{Source: &ctx.SourceGccMinusE}, + } + for _, command := range commands { + err := runCommand(ctx, command) + if err != nil { + return i18n.WrapError(err) + } + } + include = ctx.IncludeJustFound + } + + if include == "" { + // No missing includes found, we're done + cache.ExpectEntry(sourcePath, "", "") + return nil + } + + library := ResolveLibrary(ctx, include) + if library == nil { + // Library could not be resolved, show error + err := runCommand(ctx, &GCCPreprocRunner{SourceFilePath: sourcePath, TargetFileName: constants.FILE_CTAGS_TARGET_FOR_GCC_MINUS_E, Includes: includes}) + return i18n.WrapError(err) + } + + // Add this library to the list of libraries, the + // include path and queue its source files for further + // include scanning + ctx.ImportedLibraries = append(ctx.ImportedLibraries, library) + appendIncludeFolder(ctx, cache, sourcePath, include, library.SrcFolder) + sourceFolders := types.LibraryToSourceFolder(library) + for _, sourceFolder := range sourceFolders { + queueSourceFilesFromFolder(ctx, ctx.CollectedSourceFiles, library, sourceFolder.Folder, sourceFolder.Recurse) } - if len(ctx.IncludesJustFound) == 0 { - done = true - } else if len(ctx.ImportedLibraries) == len(importedLibraries) { - err := runCommand(ctx, &GCCPreprocRunner{TargetFileName: constants.FILE_CTAGS_TARGET_FOR_GCC_MINUS_E}) + first = false + } +} + +func queueSourceFilesFromFolder(ctx *types.Context, queue *types.UniqueSourceFileQueue, origin interface{}, folder string, recurse bool) error { + extensions := func(ext string) bool { return ADDITIONAL_FILE_VALID_EXTENSIONS_NO_HEADERS[ext] } + + filePaths := []string{} + err := utils.FindFilesInFolder(&filePaths, folder, extensions, recurse) + if err != nil { + return i18n.WrapError(err) + } + + for _, filePath := range filePaths { + sourceFile, err := types.MakeSourceFile(ctx, origin, filePath) + if (err != nil) { return i18n.WrapError(err) } - importedLibraries = ctx.ImportedLibraries - ctx.IncludesJustFound = []string{} + queue.Push(sourceFile) } + return nil } diff --git a/src/arduino.cc/builder/gcc_preproc_runner.go b/src/arduino.cc/builder/gcc_preproc_runner.go index 193aa59b..962df725 100644 --- a/src/arduino.cc/builder/gcc_preproc_runner.go +++ b/src/arduino.cc/builder/gcc_preproc_runner.go @@ -42,13 +42,13 @@ import ( ) type GCCPreprocRunner struct { + SourceFilePath string TargetFileName string + Includes []string } func (s *GCCPreprocRunner) Run(ctx *types.Context) error { - sketchBuildPath := ctx.SketchBuildPath - sketch := ctx.Sketch - properties, targetFilePath, err := prepareGCCPreprocRecipeProperties(ctx, filepath.Join(sketchBuildPath, filepath.Base(sketch.MainFile.Name)+".cpp"), s.TargetFileName) + properties, targetFilePath, err := prepareGCCPreprocRecipeProperties(ctx, s.SourceFilePath, s.TargetFileName, s.Includes) if err != nil { return i18n.WrapError(err) } @@ -73,10 +73,11 @@ func (s *GCCPreprocRunner) Run(ctx *types.Context) error { type GCCPreprocRunnerForDiscoveringIncludes struct { SourceFilePath string TargetFilePath string + Includes []string } func (s *GCCPreprocRunnerForDiscoveringIncludes) Run(ctx *types.Context) error { - properties, _, err := prepareGCCPreprocRecipeProperties(ctx, s.SourceFilePath, s.TargetFilePath) + properties, _, err := prepareGCCPreprocRecipeProperties(ctx, s.SourceFilePath, s.TargetFilePath, s.Includes) if err != nil { return i18n.WrapError(err) } @@ -99,7 +100,7 @@ func (s *GCCPreprocRunnerForDiscoveringIncludes) Run(ctx *types.Context) error { return nil } -func prepareGCCPreprocRecipeProperties(ctx *types.Context, sourceFilePath string, targetFilePath string) (properties.Map, string, error) { +func prepareGCCPreprocRecipeProperties(ctx *types.Context, sourceFilePath string, targetFilePath string, includes []string) (properties.Map, string, error) { if targetFilePath != utils.NULLFile() { preprocPath := ctx.PreprocPath err := utils.EnsureFolderExists(preprocPath) @@ -113,7 +114,6 @@ func prepareGCCPreprocRecipeProperties(ctx *types.Context, sourceFilePath string properties[constants.BUILD_PROPERTIES_SOURCE_FILE] = sourceFilePath properties[constants.BUILD_PROPERTIES_PREPROCESSED_FILE_PATH] = targetFilePath - includes := ctx.IncludeFolders includes = utils.Map(includes, utils.WrapWithHyphenI) properties[constants.BUILD_PROPERTIES_INCLUDES] = strings.Join(includes, constants.SPACE) builder_utils.RemoveHyphenMDDFlagFromGCCCommandLine(properties) diff --git a/src/arduino.cc/builder/includes_finder_with_regexp.go b/src/arduino.cc/builder/includes_finder_with_regexp.go index 6bc81882..f093759d 100644 --- a/src/arduino.cc/builder/includes_finder_with_regexp.go +++ b/src/arduino.cc/builder/includes_finder_with_regexp.go @@ -31,7 +31,6 @@ package builder import ( "arduino.cc/builder/types" - "arduino.cc/builder/utils" "regexp" "strings" ) @@ -45,24 +44,17 @@ type IncludesFinderWithRegExp struct { func (s *IncludesFinderWithRegExp) Run(ctx *types.Context) error { source := *s.Source - matches := INCLUDE_REGEXP.FindAllStringSubmatch(source, -1) - includes := []string{} - for _, match := range matches { - includes = append(includes, strings.TrimSpace(match[1])) - } - if len(includes) == 0 { - include := findIncludesForOldCompilers(source) - if include != "" { - includes = append(includes, include) - } + match := INCLUDE_REGEXP.FindStringSubmatch(source) + if match != nil { + ctx.IncludeJustFound = strings.TrimSpace(match[1]) + } else { + ctx.IncludeJustFound = findIncludeForOldCompilers(source) } - ctx.IncludesJustFound = includes - ctx.Includes = utils.AppendIfNotPresent(ctx.Includes, includes...) return nil } -func findIncludesForOldCompilers(source string) string { +func findIncludeForOldCompilers(source string) string { lines := strings.Split(source, "\n") for _, line := range lines { splittedLine := strings.Split(line, ":") diff --git a/src/arduino.cc/builder/libraries_loader.go b/src/arduino.cc/builder/libraries_loader.go index 1b3d3d23..ba2a0c7a 100644 --- a/src/arduino.cc/builder/libraries_loader.go +++ b/src/arduino.cc/builder/libraries_loader.go @@ -113,6 +113,14 @@ func makeLibrary(libraryFolder string, debugLevel int, logger i18n.Logger) (*typ return makeNewLibrary(libraryFolder, debugLevel, logger) } +func addUtilityFolder(library *types.Library) { + utilitySourcePath := filepath.Join(library.Folder, constants.LIBRARY_FOLDER_UTILITY) + stat, err := os.Stat(utilitySourcePath) + if err == nil && stat.IsDir() { + library.UtilityFolder = utilitySourcePath + } +} + func makeNewLibrary(libraryFolder string, debugLevel int, logger i18n.Logger) (*types.Library, error) { libProperties, err := properties.Load(filepath.Join(libraryFolder, constants.LIBRARY_PROPERTIES), logger) if err != nil { @@ -130,12 +138,14 @@ func makeNewLibrary(libraryFolder string, debugLevel int, logger i18n.Logger) (* } library := &types.Library{} + library.Folder = libraryFolder if stat, err := os.Stat(filepath.Join(libraryFolder, constants.LIBRARY_FOLDER_SRC)); err == nil && stat.IsDir() { library.Layout = types.LIBRARY_RECURSIVE library.SrcFolder = filepath.Join(libraryFolder, constants.LIBRARY_FOLDER_SRC) } else { library.Layout = types.LIBRARY_FLAT library.SrcFolder = libraryFolder + addUtilityFolder(library) } subFolders, err := utils.ReadDirFiltered(libraryFolder, utils.FilterDirs) @@ -173,7 +183,6 @@ func makeNewLibrary(libraryFolder string, debugLevel int, logger i18n.Logger) (* } library.License = libProperties[constants.LIBRARY_LICENSE] - library.Folder = libraryFolder library.Name = filepath.Base(libraryFolder) library.Version = strings.TrimSpace(libProperties[constants.LIBRARY_VERSION]) library.Author = strings.TrimSpace(libProperties[constants.LIBRARY_AUTHOR]) @@ -197,6 +206,7 @@ func makeLegacyLibrary(libraryFolder string) (*types.Library, error) { Archs: []string{constants.LIBRARY_ALL_ARCHS}, IsLegacy: true, } + addUtilityFolder(library) return library, nil } diff --git a/src/arduino.cc/builder/phases/libraries_builder.go b/src/arduino.cc/builder/phases/libraries_builder.go index 18785cfe..816c512f 100644 --- a/src/arduino.cc/builder/phases/libraries_builder.go +++ b/src/arduino.cc/builder/phases/libraries_builder.go @@ -30,7 +30,6 @@ package phases import ( - "os" "path/filepath" "arduino.cc/builder/builder_utils" @@ -107,20 +106,17 @@ func compileLibrary(library *types.Library, buildPath string, buildProperties pr objectFiles = []string{archiveFile} } } else { - utilitySourcePath := filepath.Join(library.SrcFolder, constants.LIBRARY_FOLDER_UTILITY) - stat, err := os.Stat(utilitySourcePath) - haveUtilityDir := err == nil && stat.IsDir() - if haveUtilityDir { - includes = append(includes, utils.WrapWithHyphenI(utilitySourcePath)) + if library.UtilityFolder != "" { + includes = append(includes, utils.WrapWithHyphenI(library.UtilityFolder)) } objectFiles, err = builder_utils.CompileFiles(objectFiles, library.SrcFolder, false, libraryBuildPath, buildProperties, includes, verbose, warningsLevel, logger) if err != nil { return nil, i18n.WrapError(err) } - if haveUtilityDir { + if library.UtilityFolder != "" { utilityBuildPath := filepath.Join(libraryBuildPath, constants.LIBRARY_FOLDER_UTILITY) - objectFiles, err = builder_utils.CompileFiles(objectFiles, utilitySourcePath, false, utilityBuildPath, buildProperties, includes, verbose, warningsLevel, logger) + objectFiles, err = builder_utils.CompileFiles(objectFiles, library.UtilityFolder, false, utilityBuildPath, buildProperties, includes, verbose, warningsLevel, logger) if err != nil { return nil, i18n.WrapError(err) } diff --git a/src/arduino.cc/builder/includes_to_include_folders.go b/src/arduino.cc/builder/resolve_library.go similarity index 75% rename from src/arduino.cc/builder/includes_to_include_folders.go rename to src/arduino.cc/builder/resolve_library.go index 3a3671ed..4ba46f82 100644 --- a/src/arduino.cc/builder/includes_to_include_folders.go +++ b/src/arduino.cc/builder/resolve_library.go @@ -34,92 +34,34 @@ import ( "strings" "arduino.cc/builder/constants" - "arduino.cc/builder/i18n" "arduino.cc/builder/types" "arduino.cc/builder/utils" - "arduino.cc/properties" ) -type IncludesToIncludeFolders struct{} - -func (s *IncludesToIncludeFolders) Run(ctx *types.Context) error { - includes := ctx.Includes +func ResolveLibrary(ctx *types.Context, header string) *types.Library { headerToLibraries := ctx.HeaderToLibraries - - platform := ctx.TargetPlatform - actualPlatform := ctx.ActualPlatform + platforms := []*types.Platform{ctx.ActualPlatform, ctx.TargetPlatform} libraryResolutionResults := ctx.LibrariesResolutionResults importedLibraries := ctx.ImportedLibraries - newlyImportedLibraries, err := resolveLibraries(includes, headerToLibraries, importedLibraries, []*types.Platform{actualPlatform, platform}, libraryResolutionResults) - if err != nil { - return i18n.WrapError(err) - } - - foldersWithSources := ctx.FoldersWithSourceFiles - - for _, newlyImportedLibrary := range newlyImportedLibraries { - if !sliceContainsLibrary(importedLibraries, newlyImportedLibrary) { - importedLibraries = append(importedLibraries, newlyImportedLibrary) - sourceFolders := types.LibraryToSourceFolder(newlyImportedLibrary) - for _, sourceFolder := range sourceFolders { - foldersWithSources.Push(sourceFolder) - } - } - } - - ctx.ImportedLibraries = importedLibraries - ctx.IncludeFolders = resolveIncludeFolders(newlyImportedLibraries, ctx.BuildProperties, ctx.Verbose) - - return nil -} - -func resolveIncludeFolders(importedLibraries []*types.Library, buildProperties properties.Map, verbose bool) []string { - var includeFolders []string - includeFolders = append(includeFolders, buildProperties[constants.BUILD_PROPERTIES_BUILD_CORE_PATH]) - if buildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH] != constants.EMPTY_STRING { - includeFolders = append(includeFolders, buildProperties[constants.BUILD_PROPERTIES_BUILD_VARIANT_PATH]) - } - - for _, library := range importedLibraries { - includeFolders = append(includeFolders, library.SrcFolder) - } - - return includeFolders -} - -//FIXME it's also resolving previously resolved libraries -func resolveLibraries(includes []string, headerToLibraries map[string][]*types.Library, importedLibraries []*types.Library, platforms []*types.Platform, libraryResolutionResults map[string]types.LibraryResolutionResult) ([]*types.Library, error) { markImportedLibrary := make(map[*types.Library]bool) for _, library := range importedLibraries { markImportedLibrary[library] = true } - for _, header := range includes { - resolveLibrary(header, headerToLibraries, markImportedLibrary, platforms, libraryResolutionResults) - } - - var newlyImportedLibraries []*types.Library - for library, _ := range markImportedLibrary { - newlyImportedLibraries = append(newlyImportedLibraries, library) - } - return newlyImportedLibraries, nil -} - -func resolveLibrary(header string, headerToLibraries map[string][]*types.Library, markImportedLibrary map[*types.Library]bool, platforms []*types.Platform, libraryResolutionResults map[string]types.LibraryResolutionResult) { libraries := append([]*types.Library{}, headerToLibraries[header]...) if libraries == nil || len(libraries) == 0 { - return + return nil } if len(libraries) == 1 { markImportedLibrary[libraries[0]] = true - return + return libraries[0] } if markImportedLibraryContainsOneOfCandidates(markImportedLibrary, libraries) { - return + return nil } reverse(libraries) @@ -151,6 +93,7 @@ func resolveLibrary(header string, headerToLibraries map[string][]*types.Library libraryResolutionResults[header] = types.LibraryResolutionResult{Library: library, NotUsedLibraries: filterOutLibraryFrom(libraries, library)} markImportedLibrary[library] = true + return library } //facepalm. sort.Reverse needs an Interface that implements Len/Less/Swap. It's a slice! What else for reversing it?!? diff --git a/src/arduino.cc/builder/test/add_additional_entries_to_context_test.go b/src/arduino.cc/builder/test/add_additional_entries_to_context_test.go index a2bfbfc6..3bb057d4 100644 --- a/src/arduino.cc/builder/test/add_additional_entries_to_context_test.go +++ b/src/arduino.cc/builder/test/add_additional_entries_to_context_test.go @@ -52,7 +52,6 @@ func TestAddAdditionalEntriesToContextNoBuildPath(t *testing.T) { require.NotNil(t, ctx.WarningsLevel) require.True(t, ctx.CollectedSourceFiles.Empty()) - require.True(t, ctx.FoldersWithSourceFiles.Empty()) require.Equal(t, 0, len(ctx.LibrariesResolutionResults)) } @@ -72,7 +71,6 @@ func TestAddAdditionalEntriesToContextWithBuildPath(t *testing.T) { require.NotNil(t, ctx.WarningsLevel) require.True(t, ctx.CollectedSourceFiles.Empty()) - require.True(t, ctx.FoldersWithSourceFiles.Empty()) require.Equal(t, 0, len(ctx.LibrariesResolutionResults)) } diff --git a/src/arduino.cc/builder/test/collect_all_source_files_from_folders_with_sources_test.go b/src/arduino.cc/builder/test/collect_all_source_files_from_folders_with_sources_test.go deleted file mode 100644 index ce9ab58f..00000000 --- a/src/arduino.cc/builder/test/collect_all_source_files_from_folders_with_sources_test.go +++ /dev/null @@ -1,127 +0,0 @@ -/* - * This file is part of Arduino Builder. - * - * Arduino Builder is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * As a special exception, you may use this file as part of a free software - * library without restriction. Specifically, if other files instantiate - * templates or use macros or inline functions from this file, or you compile - * this file and link it with other files to produce an executable, this - * file does not by itself cause the resulting executable to be covered by - * the GNU General Public License. This exception does not however - * invalidate any other reasons why the executable file might be covered by - * the GNU General Public License. - * - * Copyright 2015 Arduino LLC (http://www.arduino.cc/) - */ - -package test - -import ( - "arduino.cc/builder" - "arduino.cc/builder/types" - "github.com/stretchr/testify/require" - "path/filepath" - "sort" - "testing" -) - -func TestCollectAllSourceFilesFromFoldersWithSources(t *testing.T) { - ctx := &types.Context{} - - sourceFiles := &types.UniqueStringQueue{} - ctx.CollectedSourceFiles = sourceFiles - foldersWithSources := &types.UniqueSourceFolderQueue{} - foldersWithSources.Push(types.SourceFolder{Folder: Abs(t, "sketch_with_config"), Recurse: true}) - ctx.FoldersWithSourceFiles = foldersWithSources - - commands := []types.Command{ - &builder.CollectAllSourceFilesFromFoldersWithSources{}, - } - - for _, command := range commands { - err := command.Run(ctx) - NoError(t, err) - } - - require.Equal(t, 1, len(*sourceFiles)) - require.Equal(t, 0, len(*foldersWithSources)) - sort.Strings(*sourceFiles) - - require.Equal(t, Abs(t, filepath.Join("sketch_with_config", "src", "includes", "de bug.cpp")), sourceFiles.Pop()) - require.Equal(t, 0, len(*sourceFiles)) -} - -func TestCollectAllSourceFilesFromFoldersWithSourcesOfLibrary(t *testing.T) { - ctx := &types.Context{} - - sourceFiles := &types.UniqueStringQueue{} - ctx.CollectedSourceFiles = sourceFiles - foldersWithSources := &types.UniqueSourceFolderQueue{} - foldersWithSources.Push(types.SourceFolder{Folder: Abs(t, filepath.Join("downloaded_libraries", "Bridge")), Recurse: true}) - ctx.FoldersWithSourceFiles = foldersWithSources - - commands := []types.Command{ - &builder.CollectAllSourceFilesFromFoldersWithSources{}, - } - - for _, command := range commands { - err := command.Run(ctx) - NoError(t, err) - } - - require.Equal(t, 9, len(*sourceFiles)) - require.Equal(t, 0, len(*foldersWithSources)) - sort.Strings(*sourceFiles) - - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "Bridge.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "BridgeClient.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "BridgeServer.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "BridgeUdp.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "Console.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "FileIO.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "HttpClient.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "Mailbox.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("downloaded_libraries", "Bridge", "src", "Process.cpp")), sourceFiles.Pop()) - require.Equal(t, 0, len(*sourceFiles)) -} - -func TestCollectAllSourceFilesFromFoldersWithSourcesOfOldLibrary(t *testing.T) { - ctx := &types.Context{} - - sourceFiles := &types.UniqueStringQueue{} - ctx.CollectedSourceFiles = sourceFiles - foldersWithSources := &types.UniqueSourceFolderQueue{} - foldersWithSources.Push(types.SourceFolder{Folder: Abs(t, filepath.Join("libraries", "ShouldNotRecurseWithOldLibs")), Recurse: false}) - foldersWithSources.Push(types.SourceFolder{Folder: Abs(t, filepath.Join("libraries", "ShouldNotRecurseWithOldLibs", "utility")), Recurse: false}) - ctx.FoldersWithSourceFiles = foldersWithSources - - commands := []types.Command{ - &builder.CollectAllSourceFilesFromFoldersWithSources{}, - } - - for _, command := range commands { - err := command.Run(ctx) - NoError(t, err) - } - - require.Equal(t, 2, len(*sourceFiles)) - require.Equal(t, 0, len(*foldersWithSources)) - sort.Strings(*sourceFiles) - - require.Equal(t, Abs(t, filepath.Join("libraries", "ShouldNotRecurseWithOldLibs", "ShouldNotRecurseWithOldLibs.cpp")), sourceFiles.Pop()) - require.Equal(t, Abs(t, filepath.Join("libraries", "ShouldNotRecurseWithOldLibs", "utility", "utils.cpp")), sourceFiles.Pop()) - require.Equal(t, 0, len(*sourceFiles)) -} diff --git a/src/arduino.cc/builder/test/includes_finder_with_regexp_test.go b/src/arduino.cc/builder/test/includes_finder_with_regexp_test.go index 161416be..d6bc1553 100644 --- a/src/arduino.cc/builder/test/includes_finder_with_regexp_test.go +++ b/src/arduino.cc/builder/test/includes_finder_with_regexp_test.go @@ -33,7 +33,6 @@ import ( "arduino.cc/builder" "arduino.cc/builder/types" "github.com/stretchr/testify/require" - "sort" "testing" ) @@ -50,9 +49,7 @@ func TestIncludesFinderWithRegExp(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 1, len(includes)) - require.Equal(t, "SPI.h", includes[0]) + require.Equal(t, "SPI.h", ctx.IncludeJustFound) } func TestIncludesFinderWithRegExpEmptyOutput(t *testing.T) { @@ -66,31 +63,7 @@ func TestIncludesFinderWithRegExpEmptyOutput(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 0, len(includes)) -} - -func TestIncludesFinderWithRegExpPreviousIncludes(t *testing.T) { - ctx := &types.Context{ - Includes: []string{"test.h"}, - } - - output := "/some/path/sketch.ino:1:17: fatal error: SPI.h: No such file or directory\n" + - "#include \n" + - "^\n" + - "compilation terminated." - - ctx.Source = output - - parser := builder.IncludesFinderWithRegExp{Source: &ctx.Source} - err := parser.Run(ctx) - NoError(t, err) - - includes := ctx.Includes - require.Equal(t, 2, len(includes)) - sort.Strings(includes) - require.Equal(t, "SPI.h", includes[0]) - require.Equal(t, "test.h", includes[1]) + require.Equal(t, "", ctx.IncludeJustFound) } func TestIncludesFinderWithRegExpPaddedIncludes(t *testing.T) { @@ -106,10 +79,7 @@ func TestIncludesFinderWithRegExpPaddedIncludes(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 1, len(includes)) - sort.Strings(includes) - require.Equal(t, "Wire.h", includes[0]) + require.Equal(t, "Wire.h", ctx.IncludeJustFound) } func TestIncludesFinderWithRegExpPaddedIncludes2(t *testing.T) { @@ -125,10 +95,7 @@ func TestIncludesFinderWithRegExpPaddedIncludes2(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 1, len(includes)) - sort.Strings(includes) - require.Equal(t, "Wire.h", includes[0]) + require.Equal(t, "Wire.h", ctx.IncludeJustFound) } func TestIncludesFinderWithRegExpPaddedIncludes3(t *testing.T) { @@ -143,10 +110,7 @@ func TestIncludesFinderWithRegExpPaddedIncludes3(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 1, len(includes)) - sort.Strings(includes) - require.Equal(t, "SPI.h", includes[0]) + require.Equal(t, "SPI.h", ctx.IncludeJustFound) } func TestIncludesFinderWithRegExpPaddedIncludes4(t *testing.T) { @@ -161,8 +125,5 @@ func TestIncludesFinderWithRegExpPaddedIncludes4(t *testing.T) { err := parser.Run(ctx) NoError(t, err) - includes := ctx.Includes - require.Equal(t, 1, len(includes)) - sort.Strings(includes) - require.Equal(t, "register.h", includes[0]) + require.Equal(t, "register.h", ctx.IncludeJustFound) } diff --git a/src/arduino.cc/builder/types/accessories.go b/src/arduino.cc/builder/types/accessories.go index 1d7b7fc2..6601bbaa 100644 --- a/src/arduino.cc/builder/types/accessories.go +++ b/src/arduino.cc/builder/types/accessories.go @@ -52,25 +52,25 @@ func (queue *UniqueStringQueue) Empty() bool { return queue.Len() == 0 } -type UniqueSourceFolderQueue []SourceFolder +type UniqueSourceFileQueue []SourceFile -func (queue UniqueSourceFolderQueue) Len() int { return len(queue) } -func (queue UniqueSourceFolderQueue) Less(i, j int) bool { return false } -func (queue UniqueSourceFolderQueue) Swap(i, j int) { panic("Who called me?!?") } +func (queue UniqueSourceFileQueue) Len() int { return len(queue) } +func (queue UniqueSourceFileQueue) Less(i, j int) bool { return false } +func (queue UniqueSourceFileQueue) Swap(i, j int) { panic("Who called me?!?") } -func (queue *UniqueSourceFolderQueue) Push(value SourceFolder) { - if !sliceContainsSourceFolder(*queue, value) { +func (queue *UniqueSourceFileQueue) Push(value SourceFile) { + if !sliceContainsSourceFile(*queue, value) { *queue = append(*queue, value) } } -func (queue *UniqueSourceFolderQueue) Pop() interface{} { +func (queue *UniqueSourceFileQueue) Pop() SourceFile { old := *queue x := old[0] *queue = old[1:] return x } -func (queue *UniqueSourceFolderQueue) Empty() bool { +func (queue *UniqueSourceFileQueue) Empty() bool { return queue.Len() == 0 } diff --git a/src/arduino.cc/builder/types/context.go b/src/arduino.cc/builder/types/context.go index 75a052b2..6fb9f27b 100644 --- a/src/arduino.cc/builder/types/context.go +++ b/src/arduino.cc/builder/types/context.go @@ -46,8 +46,7 @@ type Context struct { PreprocPath string SketchObjectFiles []string - CollectedSourceFiles *UniqueStringQueue - FoldersWithSourceFiles *UniqueSourceFolderQueue + CollectedSourceFiles *UniqueSourceFileQueue Sketch *Sketch Source string @@ -56,12 +55,11 @@ type Context struct { WarningsLevel string // Libraries handling - Includes []string Libraries []*Library HeaderToLibraries map[string][]*Library ImportedLibraries []*Library LibrariesResolutionResults map[string]LibraryResolutionResult - IncludesJustFound []string + IncludeJustFound string IncludeFolders []string OutputGccMinusM string diff --git a/src/arduino.cc/builder/types/types.go b/src/arduino.cc/builder/types/types.go index 96f97955..842fc817 100644 --- a/src/arduino.cc/builder/types/types.go +++ b/src/arduino.cc/builder/types/types.go @@ -30,7 +30,7 @@ package types import ( - "os" + "fmt" "path/filepath" "strconv" @@ -38,6 +38,67 @@ import ( "arduino.cc/properties" ) +type SourceFile struct { + // Sketch or Library pointer that this source file lives in + Origin interface{} + // Path to the source file within the sketch/library root folder + RelativePath string +} + +// Create a SourceFile containing the given source file path within the +// given origin. The given path can be absolute, or relative within the +// origin's root source folder +func MakeSourceFile(ctx *Context, origin interface{}, path string) (SourceFile, error) { + if filepath.IsAbs(path) { + var err error + path, err = filepath.Rel(sourceRoot(ctx, origin), path) + if err != nil { + return SourceFile{}, err + } + } + return SourceFile{Origin: origin, RelativePath: path}, nil +} + +// Return the build root for the given origin, where build products will +// be placed. Any directories inside SourceFile.RelativePath will be +// appended here. +func buildRoot(ctx *Context, origin interface{}) string { + switch o := origin.(type) { + case *Sketch: + return ctx.SketchBuildPath + case *Library: + return filepath.Join(ctx.LibrariesBuildPath, o.Name) + default: + panic("Unexpected origin for SourceFile: " + fmt.Sprint(origin)) + } +} + +// Return the source root for the given origin, where its source files +// can be found. Prepending this to SourceFile.RelativePath will give +// the full path to that source file. +func sourceRoot(ctx *Context, origin interface{}) string { + switch o := origin.(type) { + case *Sketch: + return ctx.SketchBuildPath + case *Library: + return o.SrcFolder + default: + panic("Unexpected origin for SourceFile: " + fmt.Sprint(origin)) + } +} + +func (f *SourceFile) SourcePath(ctx *Context) string { + return filepath.Join(sourceRoot(ctx, f.Origin), f.RelativePath) +} + +func (f *SourceFile) ObjectPath(ctx *Context) string { + return filepath.Join(buildRoot(ctx, f.Origin), f.RelativePath + ".o") +} + +func (f *SourceFile) DepfilePath(ctx *Context) string { + return filepath.Join(buildRoot(ctx, f.Origin), f.RelativePath + ".d") +} + type SketchFile struct { Name string Source string @@ -104,6 +165,7 @@ const ( type Library struct { Folder string SrcFolder string + UtilityFolder string Layout LibraryLayout Name string Archs []string @@ -200,11 +262,8 @@ func LibraryToSourceFolder(library *Library) []SourceFolder { sourceFolders := []SourceFolder{} recurse := library.Layout == LIBRARY_RECURSIVE sourceFolders = append(sourceFolders, SourceFolder{Folder: library.SrcFolder, Recurse: recurse}) - if library.Layout == LIBRARY_FLAT { - utility := filepath.Join(library.SrcFolder, constants.LIBRARY_FOLDER_UTILITY) - if info, err := os.Stat(utility); err == nil && info.IsDir() { - sourceFolders = append(sourceFolders, SourceFolder{Folder: utility, Recurse: false}) - } + if library.UtilityFolder != "" { + sourceFolders = append(sourceFolders, SourceFolder{Folder: library.UtilityFolder, Recurse: false}) } return sourceFolders } diff --git a/src/arduino.cc/builder/types/utils.go b/src/arduino.cc/builder/types/utils.go index 1d26c93d..a7200f0e 100644 --- a/src/arduino.cc/builder/types/utils.go +++ b/src/arduino.cc/builder/types/utils.go @@ -39,9 +39,9 @@ func sliceContains(slice []string, target string) bool { return false } -func sliceContainsSourceFolder(slice []SourceFolder, target SourceFolder) bool { +func sliceContainsSourceFile(slice []SourceFile, target SourceFile) bool { for _, elem := range slice { - if elem.Folder == target.Folder { + if elem.Origin == target.Origin && elem.RelativePath == target.RelativePath { return true } }