diff --git a/arduino/builder/builder.go b/arduino/builder/builder.go index e8374b66e5a..70ea2ec4a07 100644 --- a/arduino/builder/builder.go +++ b/arduino/builder/builder.go @@ -15,7 +15,10 @@ package builder -import "github.com/arduino/arduino-cli/arduino/sketch" +import ( + "github.com/arduino/arduino-cli/arduino/sketch" + "github.com/arduino/go-paths-helper" +) // nolint const ( @@ -31,11 +34,15 @@ const ( // Builder is a Sketch builder. type Builder struct { sketch *sketch.Sketch + + // core related + coreBuildCachePath *paths.Path } // NewBuilder creates a sketch Builder. -func NewBuilder(sk *sketch.Sketch) *Builder { +func NewBuilder(sk *sketch.Sketch, coreBuildCachePath *paths.Path) *Builder { return &Builder{ - sketch: sk, + sketch: sk, + coreBuildCachePath: coreBuildCachePath, } } diff --git a/arduino/builder/core.go b/arduino/builder/core.go new file mode 100644 index 00000000000..0e605ac1b70 --- /dev/null +++ b/arduino/builder/core.go @@ -0,0 +1,7 @@ +package builder + +import "github.com/arduino/go-paths-helper" + +func (b *Builder) CoreBuildCachePath() *paths.Path { + return b.coreBuildCachePath +} diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 503dcc24f7c..aa81cbcbf75 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -155,7 +155,21 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream // cache is purged after compilation to not remove entries that might be required defer maybePurgeBuildCache() - sketchBuilder := bldr.NewBuilder(sk) + var coreBuildCachePath *paths.Path + if req.GetBuildCachePath() == "" { + coreBuildCachePath = paths.TempDir().Join("arduino", "cores") + } else { + buildCachePath, err := paths.New(req.GetBuildCachePath()).Abs() + if err != nil { + return nil, &arduino.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err} + } + if err := buildCachePath.MkdirAll(); err != nil { + return nil, &arduino.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err} + } + coreBuildCachePath = buildCachePath.Join("core") + } + + sketchBuilder := bldr.NewBuilder(sk, coreBuildCachePath) // Add build properites related to sketch data buildProperties = sketchBuilder.SetupBuildProperties(buildProperties, buildPath, req.GetOptimizeForDebug()) @@ -206,19 +220,6 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream builderCtx.WarningsLevel = builder.DEFAULT_WARNINGS_LEVEL } - if req.GetBuildCachePath() == "" { - builderCtx.CoreBuildCachePath = paths.TempDir().Join("arduino", "cores") - } else { - buildCachePath, err := paths.New(req.GetBuildCachePath()).Abs() - if err != nil { - return nil, &arduino.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err} - } - if err := buildCachePath.MkdirAll(); err != nil { - return nil, &arduino.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err} - } - builderCtx.CoreBuildCachePath = buildCachePath.Join("core") - } - builderCtx.BuiltInLibrariesDirs = configuration.IDEBuiltinLibrariesDir(configuration.Settings) builderCtx.Stdout = outStream diff --git a/internal/integrationtest/compile_4/compile_test.go b/internal/integrationtest/compile_4/compile_test.go index ad9a5b7b9e1..41ae6bd856a 100644 --- a/internal/integrationtest/compile_4/compile_test.go +++ b/internal/integrationtest/compile_4/compile_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" "text/template" + "time" "github.com/arduino/arduino-cli/arduino/builder/cpp" "github.com/arduino/arduino-cli/internal/integrationtest" @@ -710,3 +711,55 @@ func comparePreprocessGoldenFile(t *testing.T, sketchDir *paths.Path, preprocess require.Equal(t, buf.String(), strings.Replace(preprocessedSketch, "\r\n", "\n", -1)) } + +func TestCoreCaching(t *testing.T) { + env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t) + defer env.CleanUp() + + sketchPath, err := paths.New("..", "testdata", "bare_minimum").Abs() + require.NoError(t, err) + + // Install Arduino AVR Boards + _, _, err = cli.Run("core", "install", "arduino:avr@1.8.6") + require.NoError(t, err) + + // Create temporary cache dir + buildCachePath, err := paths.MkTempDir("", "test_build_cache") + require.NoError(t, err) + defer buildCachePath.RemoveAll() + + // Build first time + _, _, err = cli.Run("compile", "-b", "arduino:avr:uno", "--build-cache-path", buildCachePath.String(), sketchPath.String()) + require.NoError(t, err) + + // Find cached core and save timestamp + pathList, err := buildCachePath.ReadDirRecursiveFiltered(nil, paths.FilterPrefixes("core.a")) + require.NoError(t, err) + require.Len(t, pathList, 1) + cachedCoreFile := pathList[0] + lastUsedPath := cachedCoreFile.Parent().Join(".last-used") + require.True(t, lastUsedPath.Exist()) + coreStatBefore, err := cachedCoreFile.Stat() + require.NoError(t, err) + + // Run build again and check timestamp is unchanged + _, _, err = cli.Run("compile", "-b", "arduino:avr:uno", "--build-cache-path", buildCachePath.String(), sketchPath.String()) + require.NoError(t, err) + coreStatAfterRebuild, err := cachedCoreFile.Stat() + require.NoError(t, err) + require.Equal(t, coreStatBefore.ModTime(), coreStatAfterRebuild.ModTime()) + + // Touch a file of the core and check if the builder invalidate the cache + time.Sleep(time.Second) + now := time.Now().Local() + coreFolder := cli.DataDir().Join("packages", "arduino", "hardware", "avr", "1.8.6") + err = coreFolder.Join("cores", "arduino", "Arduino.h").Chtimes(now, now) + require.NoError(t, err) + + // Run build again, to verify that the builder rebuilds core.a + _, _, err = cli.Run("compile", "-b", "arduino:avr:uno", "--build-cache-path", buildCachePath.String(), sketchPath.String()) + require.NoError(t, err) + coreStatAfterTouch, err := cachedCoreFile.Stat() + require.NoError(t, err) + require.NotEqual(t, coreStatBefore.ModTime(), coreStatAfterTouch.ModTime()) +} diff --git a/legacy/builder/builder.go b/legacy/builder/builder.go index ebc63291889..12a35fccbca 100644 --- a/legacy/builder/builder.go +++ b/legacy/builder/builder.go @@ -73,8 +73,8 @@ func (s *Builder) Run(ctx *types.Context) error { &RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.core.prebuild", Suffix: ".pattern"}, types.BareCommand(func(ctx *types.Context) error { - objectFiles, archiveFile, coreBuildCachePath, err := phases.CoreBuilder( - ctx.BuildPath, ctx.CoreBuildPath, ctx.CoreBuildCachePath, + objectFiles, archiveFile, err := phases.CoreBuilder( + ctx.BuildPath, ctx.CoreBuildPath, ctx.Builder.CoreBuildCachePath(), ctx.BuildProperties, ctx.ActualPlatform, ctx.Verbose, ctx.OnlyUpdateCompilationDatabase, ctx.Clean, @@ -90,7 +90,6 @@ func (s *Builder) Run(ctx *types.Context) error { ctx.CoreObjectsFiles = objectFiles ctx.CoreArchiveFilePath = archiveFile - ctx.CoreBuildCachePath = coreBuildCachePath return err }), diff --git a/legacy/builder/phases/core_builder.go b/legacy/builder/phases/core_builder.go index 36a8335d1bf..5c5e0339e41 100644 --- a/legacy/builder/phases/core_builder.go +++ b/legacy/builder/phases/core_builder.go @@ -52,9 +52,9 @@ func CoreBuilder( verboseInfoFn func(msg string), verboseStdoutFn, verboseStderrFn func(data []byte), progress *progress.Struct, progressCB rpc.TaskProgressCB, -) (paths.PathList, *paths.Path, *paths.Path, error) { +) (paths.PathList, *paths.Path, error) { if err := coreBuildPath.MkdirAll(); err != nil { - return nil, nil, coreBuildCachePath, errors.WithStack(err) + return nil, nil, errors.WithStack(err) } if coreBuildCachePath != nil { @@ -63,7 +63,7 @@ func CoreBuilder( verboseInfoFn(tr("Running normal build of the core...")) coreBuildCachePath = nil } else if err := coreBuildCachePath.MkdirAll(); err != nil { - return nil, nil, coreBuildCachePath, errors.WithStack(err) + return nil, nil, errors.WithStack(err) } } @@ -81,10 +81,10 @@ func CoreBuilder( progress, progressCB, ) if err != nil { - return nil, nil, coreBuildCachePath, errors.WithStack(err) + return nil, nil, errors.WithStack(err) } - return objectFiles, archiveFile, coreBuildCachePath, nil + return objectFiles, archiveFile, nil } func compileCore( diff --git a/legacy/builder/test/builder_test.go b/legacy/builder/test/builder_test.go index 648a96cbbcd..f35102a162a 100644 --- a/legacy/builder/test/builder_test.go +++ b/legacy/builder/test/builder_test.go @@ -19,7 +19,6 @@ import ( "fmt" "path/filepath" "testing" - "time" bldr "github.com/arduino/arduino-cli/arduino/builder" "github.com/arduino/arduino-cli/arduino/builder/detector" @@ -27,7 +26,6 @@ import ( "github.com/arduino/arduino-cli/arduino/sketch" "github.com/arduino/arduino-cli/legacy/builder" "github.com/arduino/arduino-cli/legacy/builder/constants" - "github.com/arduino/arduino-cli/legacy/builder/phases" "github.com/arduino/arduino-cli/legacy/builder/types" "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/require" @@ -100,7 +98,7 @@ func prepareBuilderTestContext(t *testing.T, ctx *types.Context, sketchPath *pat ctx.Sketch = sk } - ctx.Builder = bldr.NewBuilder(ctx.Sketch) + ctx.Builder = bldr.NewBuilder(ctx.Sketch, nil) if fqbn != "" { ctx.FQBN = parseFQBN(t, fqbn) targetPackage, targetPlatform, targetBoard, buildProperties, buildPlatform, err := pme.ResolveFQBN(ctx.FQBN) @@ -185,48 +183,3 @@ func TestBuilderWithBuildPathInSketchDir(t *testing.T) { err = command.Run(ctx) NoError(t, err) } - -func TestBuilderCacheCoreAFile(t *testing.T) { - ctx := prepareBuilderTestContext(t, nil, paths.New("sketch1", "sketch1.ino"), "arduino:avr:uno") - defer cleanUpBuilderTestContext(t, ctx) - - SetupBuildCachePath(t, ctx) - defer ctx.CoreBuildCachePath.RemoveAll() - - // Run build - bldr := builder.Builder{} - err := bldr.Run(ctx) - NoError(t, err) - - // Pick timestamp of cached core - coreFolder := paths.New("downloaded_hardware", "arduino", "avr") - coreFileName := phases.GetCachedCoreArchiveDirName(ctx.FQBN.String(), ctx.BuildProperties.Get("compiler.optimization_flags"), coreFolder) - cachedCoreFile := ctx.CoreBuildCachePath.Join(coreFileName, "core.a") - coreStatBefore, err := cachedCoreFile.Stat() - require.NoError(t, err) - lastUsedFile := ctx.CoreBuildCachePath.Join(coreFileName, ".last-used") - _, err = lastUsedFile.Stat() - require.NoError(t, err) - - // Run build again, to verify that the builder skips rebuilding core.a - err = bldr.Run(ctx) - NoError(t, err) - - coreStatAfterRebuild, err := cachedCoreFile.Stat() - require.NoError(t, err) - require.Equal(t, coreStatBefore.ModTime(), coreStatAfterRebuild.ModTime()) - - // Touch a file of the core and check if the builder invalidate the cache - time.Sleep(time.Second) - now := time.Now().Local() - err = coreFolder.Join("cores", "arduino", "Arduino.h").Chtimes(now, now) - require.NoError(t, err) - - // Run build again, to verify that the builder rebuilds core.a - err = bldr.Run(ctx) - NoError(t, err) - - coreStatAfterTouch, err := cachedCoreFile.Stat() - require.NoError(t, err) - require.NotEqual(t, coreStatBefore.ModTime(), coreStatAfterTouch.ModTime()) -} diff --git a/legacy/builder/test/helper.go b/legacy/builder/test/helper.go index 88432e5fc76..3e3267556ad 100644 --- a/legacy/builder/test/helper.go +++ b/legacy/builder/test/helper.go @@ -26,7 +26,6 @@ import ( "github.com/arduino/arduino-cli/arduino/builder/cpp" "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino/libraries" - "github.com/arduino/arduino-cli/legacy/builder/constants" "github.com/arduino/arduino-cli/legacy/builder/types" paths "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/assert" @@ -70,13 +69,6 @@ func SetupBuildPath(t *testing.T, ctx *types.Context) *paths.Path { return buildPath } -func SetupBuildCachePath(t *testing.T, ctx *types.Context) *paths.Path { - buildCachePath, err := paths.MkTempDir(constants.EMPTY_STRING, "test_build_cache") - NoError(t, err) - ctx.CoreBuildCachePath = buildCachePath - return buildCachePath -} - func parseFQBN(t *testing.T, fqbnIn string) *cores.FQBN { fqbn, err := cores.ParseFQBN(fqbnIn) require.NoError(t, err) diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go index bb29b33499e..14616a15fb8 100644 --- a/legacy/builder/types/context.go +++ b/legacy/builder/types/context.go @@ -61,7 +61,6 @@ type Context struct { BuildPath *paths.Path SketchBuildPath *paths.Path CoreBuildPath *paths.Path - CoreBuildCachePath *paths.Path CoreArchiveFilePath *paths.Path CoreObjectsFiles paths.PathList LibrariesBuildPath *paths.Path