diff --git a/arduino/builder/builder.go b/arduino/builder/builder.go
index fa1a2ba6fef..32bd35d7e68 100644
--- a/arduino/builder/builder.go
+++ b/arduino/builder/builder.go
@@ -26,6 +26,9 @@ type Builder struct {
 	sketch          *sketch.Sketch
 	buildProperties *properties.Map
 
+	// Parallel processes
+	jobs int
+
 	// core related
 	coreBuildCachePath *paths.Path
 }
@@ -37,6 +40,7 @@ func NewBuilder(
 	buildPath *paths.Path,
 	optimizeForDebug bool,
 	coreBuildCachePath *paths.Path,
+	jobs int,
 ) *Builder {
 	buildProperties := properties.NewMap()
 	if boardBuildProperties != nil {
@@ -64,6 +68,7 @@ func NewBuilder(
 		sketch:             sk,
 		buildProperties:    buildProperties,
 		coreBuildCachePath: coreBuildCachePath,
+		jobs:               jobs,
 	}
 }
 
@@ -71,3 +76,8 @@ func NewBuilder(
 func (b *Builder) GetBuildProperties() *properties.Map {
 	return b.buildProperties
 }
+
+// Jobs number of parallel processes
+func (b *Builder) Jobs() int {
+	return b.jobs
+}
diff --git a/legacy/builder/test/includes_finder_with_regexp_test.go b/arduino/builder/detector/detector_test.go
similarity index 99%
rename from legacy/builder/test/includes_finder_with_regexp_test.go
rename to arduino/builder/detector/detector_test.go
index 16dd07da48f..fc933cf5cff 100644
--- a/legacy/builder/test/includes_finder_with_regexp_test.go
+++ b/arduino/builder/detector/detector_test.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package test
+package detector_test
 
 import (
 	"testing"
diff --git a/arduino/builder/sketch.go b/arduino/builder/sketch.go
index 43a5e635237..f8eff7caafa 100644
--- a/arduino/builder/sketch.go
+++ b/arduino/builder/sketch.go
@@ -21,6 +21,7 @@ import (
 	"regexp"
 
 	"github.com/arduino/arduino-cli/arduino/builder/cpp"
+	"github.com/arduino/arduino-cli/arduino/sketch"
 	"github.com/arduino/arduino-cli/i18n"
 	"github.com/arduino/go-paths-helper"
 
@@ -32,6 +33,11 @@ var (
 	tr               = i18n.Tr
 )
 
+// Sketch fixdoc
+func (b *Builder) Sketch() *sketch.Sketch {
+	return b.sketch
+}
+
 // PrepareSketchBuildPath copies the sketch source files in the build path.
 // The .ino files are merged together to create a .cpp file (by the way, the
 // .cpp file still needs to be Arduino-preprocessed to compile).
diff --git a/arduino/builder/sketch_test.go b/arduino/builder/sketch_test.go
index f00a4e68ea8..6aaedaaa0fc 100644
--- a/arduino/builder/sketch_test.go
+++ b/arduino/builder/sketch_test.go
@@ -48,7 +48,7 @@ func TestMergeSketchSources(t *testing.T) {
 	}
 	mergedSources := strings.ReplaceAll(string(mergedBytes), "%s", pathToGoldenSource)
 
-	b := NewBuilder(sk, nil, nil, false, nil)
+	b := NewBuilder(sk, nil, nil, false, nil, 0)
 	offset, source, err := b.sketchMergeSources(nil)
 	require.Nil(t, err)
 	require.Equal(t, 2, offset)
@@ -61,7 +61,7 @@ func TestMergeSketchSourcesArduinoIncluded(t *testing.T) {
 	require.NotNil(t, sk)
 
 	// ensure not to include Arduino.h when it's already there
-	b := NewBuilder(sk, nil, nil, false, nil)
+	b := NewBuilder(sk, nil, nil, false, nil, 0)
 	_, source, err := b.sketchMergeSources(nil)
 	require.Nil(t, err)
 	require.Equal(t, 1, strings.Count(source, "<Arduino.h>"))
@@ -76,7 +76,7 @@ func TestCopyAdditionalFiles(t *testing.T) {
 	sk1, err := sketch.New(paths.New("testdata", t.Name()))
 	require.Nil(t, err)
 	require.Equal(t, sk1.AdditionalFiles.Len(), 1)
-	b1 := NewBuilder(sk1, nil, nil, false, nil)
+	b1 := NewBuilder(sk1, nil, nil, false, nil, 0)
 
 	// copy the sketch over, create a fake main file we don't care about it
 	// but we need it for `SketchLoad` to succeed later
diff --git a/commands/compile/compile.go b/commands/compile/compile.go
index a5f6c3542e8..f04a239a870 100644
--- a/commands/compile/compile.go
+++ b/commands/compile/compile.go
@@ -169,7 +169,14 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		coreBuildCachePath = buildCachePath.Join("core")
 	}
 
-	sketchBuilder := bldr.NewBuilder(sk, boardBuildProperties, buildPath, req.GetOptimizeForDebug(), coreBuildCachePath)
+	sketchBuilder := bldr.NewBuilder(
+		sk,
+		boardBuildProperties,
+		buildPath,
+		req.GetOptimizeForDebug(),
+		coreBuildCachePath,
+		int(req.GetJobs()),
+	)
 
 	buildProperties := sketchBuilder.GetBuildProperties()
 
@@ -197,7 +204,6 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	builderCtx.BuildProperties = buildProperties
 	builderCtx.CustomBuildProperties = customBuildPropertiesArgs
 	builderCtx.FQBN = fqbn
-	builderCtx.Sketch = sk
 	builderCtx.BuildPath = buildPath
 	builderCtx.ProgressCB = progressCB
 
@@ -212,7 +218,6 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	)
 
 	builderCtx.Verbose = req.GetVerbose()
-	builderCtx.Jobs = int(req.GetJobs())
 
 	builderCtx.WarningsLevel = req.GetWarnings()
 	if builderCtx.WarningsLevel == "" {
@@ -243,7 +248,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	builderCtx.LibrariesBuildPath = librariesBuildPath
 	builderCtx.CoreBuildPath = coreBuildPath
 
-	if builderCtx.BuildPath.Canonical().EqualsTo(builderCtx.Sketch.FullPath.Canonical()) {
+	if builderCtx.BuildPath.Canonical().EqualsTo(sk.FullPath.Canonical()) {
 		return r, &arduino.CompileFailedError{
 			Message: tr("Sketch cannot be located in build path. Please specify a different build path"),
 		}
@@ -365,8 +370,13 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		exportBinaries = false
 	}
 	if exportBinaries {
-		presaveHex := builder.RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.savehex.presavehex", Suffix: ".pattern"}
-		if err := presaveHex.Run(builderCtx); err != nil {
+		err := builder.RecipeByPrefixSuffixRunner(
+			"recipe.hooks.savehex.presavehex", ".pattern", false,
+			builderCtx.OnlyUpdateCompilationDatabase, builderCtx.Verbose,
+			builderCtx.BuildProperties, builderCtx.Stdout, builderCtx.Stderr,
+			func(msg string) { builderCtx.Info(msg) },
+		)
+		if err != nil {
 			return r, err
 		}
 
@@ -404,8 +414,13 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 			}
 		}
 
-		postsaveHex := builder.RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.savehex.postsavehex", Suffix: ".pattern"}
-		if err := postsaveHex.Run(builderCtx); err != nil {
+		err = builder.RecipeByPrefixSuffixRunner(
+			"recipe.hooks.savehex.postsavehex", ".pattern", false,
+			builderCtx.OnlyUpdateCompilationDatabase, builderCtx.Verbose,
+			builderCtx.BuildProperties, builderCtx.Stdout, builderCtx.Stderr,
+			func(msg string) { builderCtx.Info(msg) },
+		)
+		if err != nil {
 			return r, err
 		}
 	}
diff --git a/legacy/builder/builder.go b/legacy/builder/builder.go
index 2ad23985ce5..0e1156666e6 100644
--- a/legacy/builder/builder.go
+++ b/legacy/builder/builder.go
@@ -20,9 +20,12 @@ import (
 	"time"
 
 	"github.com/arduino/arduino-cli/arduino/builder/preprocessor"
+	"github.com/arduino/arduino-cli/arduino/sketch"
 	"github.com/arduino/arduino-cli/i18n"
 	"github.com/arduino/arduino-cli/legacy/builder/phases"
 	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/go-paths-helper"
+	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -41,9 +44,11 @@ func (s *Builder) Run(ctx *types.Context) error {
 
 	var _err, mainErr error
 	commands := []types.Command{
-		&ContainerBuildOptions{},
+		containerBuildOptions(ctx),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.prebuild", Suffix: ".pattern"},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.prebuild", ".pattern", false)
+		}),
 
 		types.BareCommand(func(ctx *types.Context) error {
 			ctx.LineOffset, _err = ctx.Builder.PrepareSketchBuildPath(ctx.SourceOverride, ctx.SketchBuildPath)
@@ -53,13 +58,17 @@ func (s *Builder) Run(ctx *types.Context) error {
 		logIfVerbose(false, tr("Detecting libraries used...")),
 		findIncludes(ctx),
 
-		&WarnAboutArchIncompatibleLibraries{},
+		warnAboutArchIncompatibleLibraries(ctx),
 
 		logIfVerbose(false, tr("Generating function prototypes...")),
-		types.BareCommand(PreprocessSketch),
+		preprocessSketchCommand(ctx),
 
 		logIfVerbose(false, tr("Compiling sketch...")),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.sketch.prebuild", Suffix: ".pattern"},
+
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.sketch.prebuild", ".pattern", false)
+		}),
+
 		types.BareCommand(func(ctx *types.Context) error {
 			sketchObjectFiles, err := phases.SketchBuilder(
 				ctx.SketchBuildPath,
@@ -68,7 +77,7 @@ func (s *Builder) Run(ctx *types.Context) error {
 				ctx.OnlyUpdateCompilationDatabase,
 				ctx.Verbose,
 				ctx.CompilationDatabase,
-				ctx.Jobs,
+				ctx.Builder.Jobs(),
 				ctx.WarningsLevel,
 				ctx.Stdout, ctx.Stderr,
 				func(msg string) { ctx.Info(msg) },
@@ -82,11 +91,23 @@ func (s *Builder) Run(ctx *types.Context) error {
 			ctx.SketchObjectFiles = sketchObjectFiles
 			return nil
 		}),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.sketch.postbuild", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.sketch.postbuild", ".pattern", true)
+		}),
 
 		logIfVerbose(false, tr("Compiling libraries...")),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.libraries.prebuild", Suffix: ".pattern"},
-		&UnusedCompiledLibrariesRemover{},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.libraries.prebuild", ".pattern", false)
+		}),
+
+		types.BareCommand(func(ctx *types.Context) error {
+			return UnusedCompiledLibrariesRemover(
+				ctx.LibrariesBuildPath,
+				ctx.SketchLibrariesDetector.ImportedLibraries(),
+			)
+		}),
+
 		types.BareCommand(func(ctx *types.Context) error {
 			librariesObjectFiles, err := phases.LibrariesBuilder(
 				ctx.LibrariesBuildPath,
@@ -96,7 +117,7 @@ func (s *Builder) Run(ctx *types.Context) error {
 				ctx.Verbose,
 				ctx.OnlyUpdateCompilationDatabase,
 				ctx.CompilationDatabase,
-				ctx.Jobs,
+				ctx.Builder.Jobs(),
 				ctx.WarningsLevel,
 				ctx.Stdout,
 				ctx.Stderr,
@@ -112,10 +133,14 @@ func (s *Builder) Run(ctx *types.Context) error {
 
 			return nil
 		}),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.libraries.postbuild", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.libraries.postbuild", ".pattern", true)
+		}),
 
 		logIfVerbose(false, tr("Compiling core...")),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.core.prebuild", Suffix: ".pattern"},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.core.prebuild", ".pattern", false)
+		}),
 
 		types.BareCommand(func(ctx *types.Context) error {
 			objectFiles, archiveFile, err := phases.CoreBuilder(
@@ -124,7 +149,7 @@ func (s *Builder) Run(ctx *types.Context) error {
 				ctx.ActualPlatform,
 				ctx.Verbose, ctx.OnlyUpdateCompilationDatabase, ctx.Clean,
 				ctx.CompilationDatabase,
-				ctx.Jobs,
+				ctx.Builder.Jobs(),
 				ctx.WarningsLevel,
 				ctx.Stdout, ctx.Stderr,
 				func(msg string) { ctx.Info(msg) },
@@ -139,10 +164,14 @@ func (s *Builder) Run(ctx *types.Context) error {
 			return err
 		}),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.core.postbuild", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.core.postbuild", ".pattern", true)
+		}),
 
 		logIfVerbose(false, tr("Linking everything together...")),
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.linking.prelink", Suffix: ".pattern"},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.linking.prelink", ".pattern", false)
+		}),
 
 		types.BareCommand(func(ctx *types.Context) error {
 			verboseInfoOut, err := phases.Linker(
@@ -164,15 +193,32 @@ func (s *Builder) Run(ctx *types.Context) error {
 			return err
 		}),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.linking.postlink", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.linking.postlink", ".pattern", true)
+		}),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.objcopy.preobjcopy", Suffix: ".pattern"},
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.objcopy.", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.objcopy.postobjcopy", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.objcopy.preobjcopy", ".pattern", false)
+		}),
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.objcopy.", ".pattern", true)
+		}),
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.objcopy.postobjcopy", ".pattern", true)
+		}),
 
-		&MergeSketchWithBootloader{},
+		types.BareCommand(func(ctx *types.Context) error {
+			return MergeSketchWithBootloader(
+				ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+				ctx.BuildPath, ctx.Builder.Sketch(), ctx.BuildProperties,
+				func(s string) { ctx.Info(s) },
+				func(s string) { ctx.Warn(s) },
+			)
+		}),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.postbuild", Suffix: ".pattern", SkipIfOnlyUpdatingCompilationDatabase: true},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.postbuild", ".pattern", true)
+		}),
 	}
 
 	ctx.Progress.AddSubSteps(len(commands) + 5)
@@ -200,9 +246,30 @@ func (s *Builder) Run(ctx *types.Context) error {
 			return nil
 		}),
 
-		&PrintUsedLibrariesIfVerbose{},
+		types.BareCommand(func(ctx *types.Context) error {
+			infoOut, _ := PrintUsedLibrariesIfVerbose(ctx.Verbose, ctx.SketchLibrariesDetector.ImportedLibraries())
+			ctx.Info(string(infoOut))
+			return nil
+		}),
 
-		&ExportProjectCMake{SketchError: mainErr != nil},
+		types.BareCommand(func(ctx *types.Context) error {
+			normalOutput, verboseOutput, err := ExportProjectCMake(
+				mainErr != nil,
+				ctx.BuildPath, ctx.SketchBuildPath,
+				ctx.SketchLibrariesDetector.ImportedLibraries(),
+				ctx.BuildProperties,
+				ctx.Builder.Sketch(),
+				ctx.SketchLibrariesDetector.IncludeFolders(),
+				ctx.LineOffset,
+				ctx.OnlyUpdateCompilationDatabase,
+			)
+			if ctx.Verbose {
+				ctx.WriteStdout(verboseOutput)
+			} else {
+				ctx.WriteStdout(normalOutput)
+			}
+			return err
+		}),
 
 		types.BareCommand(func(ctx *types.Context) error {
 			executableSectionsSize, err := phases.Sizer(
@@ -235,17 +302,27 @@ func (s *Builder) Run(ctx *types.Context) error {
 	return otherErr
 }
 
-func PreprocessSketch(ctx *types.Context) error {
-	preprocessorImpl := preprocessor.PreprocessSketchWithCtags
-	normalOutput, verboseOutput, err := preprocessorImpl(
-		ctx.Sketch, ctx.BuildPath, ctx.SketchLibrariesDetector.IncludeFolders(), ctx.LineOffset,
-		ctx.BuildProperties, ctx.OnlyUpdateCompilationDatabase)
-	if ctx.Verbose {
-		ctx.WriteStdout(verboseOutput)
-	} else {
-		ctx.WriteStdout(normalOutput)
+func preprocessSketchCommand(ctx *types.Context) types.BareCommand {
+	return func(ctx *types.Context) error {
+		normalOutput, verboseOutput, err := PreprocessSketch(
+			ctx.Builder.Sketch(), ctx.BuildPath, ctx.SketchLibrariesDetector.IncludeFolders(), ctx.LineOffset,
+			ctx.BuildProperties, ctx.OnlyUpdateCompilationDatabase)
+		if ctx.Verbose {
+			ctx.WriteStdout(verboseOutput)
+		} else {
+			ctx.WriteStdout(normalOutput)
+		}
+		return err
 	}
-	return err
+}
+
+func PreprocessSketch(
+	sketch *sketch.Sketch, buildPath *paths.Path, includes paths.PathList, lineOffset int,
+	buildProperties *properties.Map, onlyUpdateCompilationDatabase bool,
+) ([]byte, []byte, error) {
+	// In the future we might change the preprocessor
+	preprocessorImpl := preprocessor.PreprocessSketchWithCtags
+	return preprocessorImpl(sketch, buildPath, includes, lineOffset, buildProperties, onlyUpdateCompilationDatabase)
 }
 
 type Preprocess struct{}
@@ -257,9 +334,11 @@ func (s *Preprocess) Run(ctx *types.Context) error {
 
 	var _err error
 	commands := []types.Command{
-		&ContainerBuildOptions{},
+		containerBuildOptions(ctx),
 
-		&RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.prebuild", Suffix: ".pattern"},
+		types.BareCommand(func(ctx *types.Context) error {
+			return recipeByPrefixSuffixRunner(ctx, "recipe.hooks.prebuild", ".pattern", false)
+		}),
 
 		types.BareCommand(func(ctx *types.Context) error {
 			ctx.LineOffset, _err = ctx.Builder.PrepareSketchBuildPath(ctx.SourceOverride, ctx.SketchBuildPath)
@@ -268,9 +347,9 @@ func (s *Preprocess) Run(ctx *types.Context) error {
 
 		findIncludes(ctx),
 
-		&WarnAboutArchIncompatibleLibraries{},
+		warnAboutArchIncompatibleLibraries(ctx),
 
-		types.BareCommand(PreprocessSketch),
+		preprocessSketchCommand(ctx),
 	}
 
 	if err := runCommands(ctx, commands); err != nil {
@@ -278,7 +357,7 @@ func (s *Preprocess) Run(ctx *types.Context) error {
 	}
 
 	// Output arduino-preprocessed source
-	preprocessedSketch, err := ctx.SketchBuildPath.Join(ctx.Sketch.MainFile.Base() + ".cpp").ReadFile()
+	preprocessedSketch, err := ctx.SketchBuildPath.Join(ctx.Builder.Sketch().MainFile.Base() + ".cpp").ReadFile()
 	if err != nil {
 		return err
 	}
@@ -322,7 +401,7 @@ func findIncludes(ctx *types.Context) types.BareCommand {
 			ctx.BuildProperties.GetPath("build.core.path"),
 			ctx.BuildProperties.GetPath("build.variant.path"),
 			ctx.SketchBuildPath,
-			ctx.Sketch,
+			ctx.Builder.Sketch(),
 			ctx.LibrariesBuildPath,
 			ctx.BuildProperties,
 			ctx.TargetPlatform.Platform.Architecture,
@@ -343,3 +422,48 @@ func logIfVerbose(warn bool, msg string) types.BareCommand {
 		return nil
 	})
 }
+
+func recipeByPrefixSuffixRunner(ctx *types.Context, prefix, suffix string, skipIfOnlyUpdatingCompilationDatabase bool) error {
+	return RecipeByPrefixSuffixRunner(
+		prefix, suffix, skipIfOnlyUpdatingCompilationDatabase,
+		ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+		ctx.BuildProperties, ctx.Stdout, ctx.Stderr,
+		func(msg string) { ctx.Info(msg) },
+	)
+}
+
+func containerBuildOptions(ctx *types.Context) types.BareCommand {
+	return types.BareCommand(func(ctx *types.Context) error {
+		// TODO here we can pass only the properties we're reading from the
+		// ctx.BuildProperties
+		buildOptionsJSON, buildOptionsJSONPrevious, infoMessage, err := ContainerBuildOptions(
+			ctx.HardwareDirs, ctx.BuiltInToolsDirs, ctx.OtherLibrariesDirs,
+			ctx.BuiltInLibrariesDirs, ctx.BuildPath, ctx.Builder.Sketch(), ctx.CustomBuildProperties,
+			ctx.FQBN.String(), ctx.Clean, ctx.BuildProperties,
+		)
+		if infoMessage != "" {
+			ctx.Info(infoMessage)
+		}
+		if err != nil {
+			return err
+		}
+
+		ctx.BuildOptionsJson = buildOptionsJSON
+		ctx.BuildOptionsJsonPrevious = buildOptionsJSONPrevious
+
+		return nil
+	})
+}
+
+func warnAboutArchIncompatibleLibraries(ctx *types.Context) types.BareCommand {
+	return types.BareCommand(func(ctx *types.Context) error {
+		overrides, _ := ctx.BuildProperties.GetOk("architecture.override_check")
+		WarnAboutArchIncompatibleLibraries(
+			ctx.TargetPlatform,
+			overrides,
+			ctx.SketchLibrariesDetector.ImportedLibraries(),
+			func(s string) { ctx.Info(s) },
+		)
+		return nil
+	})
+}
diff --git a/legacy/builder/container_build_options.go b/legacy/builder/container_build_options.go
index b06b9e9c763..6302bc558f7 100644
--- a/legacy/builder/container_build_options.go
+++ b/legacy/builder/container_build_options.go
@@ -16,27 +16,45 @@
 package builder
 
 import (
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/arduino-cli/arduino/sketch"
+	"github.com/arduino/go-paths-helper"
+	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/pkg/errors"
 )
 
-type ContainerBuildOptions struct{}
+func ContainerBuildOptions(
+	hardwareDirs, builtInToolsDirs, otherLibrariesDirs paths.PathList,
+	builtInLibrariesDirs, buildPath *paths.Path,
+	sketch *sketch.Sketch,
+	customBuildProperties []string,
+	fqbn string,
+	clean bool,
+	buildProperties *properties.Map,
+) (string, string, string, error) {
+	buildOptionsJSON, err := CreateBuildOptionsMap(
+		hardwareDirs, builtInToolsDirs, otherLibrariesDirs,
+		builtInLibrariesDirs, sketch, customBuildProperties,
+		fqbn, buildProperties.Get("compiler.optimization_flags"),
+	)
+	if err != nil {
+		return "", "", "", errors.WithStack(err)
+	}
 
-func (s *ContainerBuildOptions) Run(ctx *types.Context) error {
-	commands := []types.Command{
-		&CreateBuildOptionsMap{},
-		&LoadPreviousBuildOptionsMap{},
-		&WipeoutBuildPathIfBuildOptionsChanged{},
-		&StoreBuildOptionsMap{},
+	buildOptionsJSONPrevious, err := LoadPreviousBuildOptionsMap(buildPath)
+	if err != nil {
+		return "", "", "", errors.WithStack(err)
 	}
 
-	for _, command := range commands {
-		PrintRingNameIfDebug(ctx, command)
-		err := command.Run(ctx)
-		if err != nil {
-			return errors.WithStack(err)
-		}
+	infoOut, err := WipeoutBuildPathIfBuildOptionsChanged(
+		clean,
+		buildPath,
+		buildOptionsJSON,
+		buildOptionsJSONPrevious,
+		buildProperties,
+	)
+	if err != nil {
+		return "", "", "", errors.WithStack(err)
 	}
 
-	return nil
+	return buildOptionsJSON, buildOptionsJSONPrevious, infoOut, StoreBuildOptionsMap(buildPath, buildOptionsJSON)
 }
diff --git a/legacy/builder/create_build_options_map.go b/legacy/builder/create_build_options_map.go
index 8c667f592da..23240f56bc1 100644
--- a/legacy/builder/create_build_options_map.go
+++ b/legacy/builder/create_build_options_map.go
@@ -17,20 +17,47 @@ package builder
 
 import (
 	"encoding/json"
+	"strings"
 
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/arduino-cli/arduino/sketch"
+	"github.com/arduino/go-paths-helper"
+	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/pkg/errors"
 )
 
-type CreateBuildOptionsMap struct{}
+func CreateBuildOptionsMap(
+	hardwareDirs, builtInToolsDirs, otherLibrariesDirs paths.PathList,
+	builtInLibrariesDirs *paths.Path,
+	sketch *sketch.Sketch,
+	customBuildProperties []string,
+	fqbn, compilerOptimizationFlags string,
+) (string, error) {
+	opts := properties.NewMap()
+	opts.Set("hardwareFolders", strings.Join(hardwareDirs.AsStrings(), ","))
+	opts.Set("builtInToolsFolders", strings.Join(builtInToolsDirs.AsStrings(), ","))
+	if builtInLibrariesDirs != nil {
+		opts.Set("builtInLibrariesFolders", builtInLibrariesDirs.String())
+	}
+	opts.Set("otherLibrariesFolders", strings.Join(otherLibrariesDirs.AsStrings(), ","))
+	opts.SetPath("sketchLocation", sketch.FullPath)
+	var additionalFilesRelative []string
+	absPath := sketch.FullPath.Parent()
+	for _, f := range sketch.AdditionalFiles {
+		relPath, err := f.RelTo(absPath)
+		if err != nil {
+			continue // ignore
+		}
+		additionalFilesRelative = append(additionalFilesRelative, relPath.String())
+	}
+	opts.Set("fqbn", fqbn)
+	opts.Set("customBuildProperties", strings.Join(customBuildProperties, ","))
+	opts.Set("additionalFiles", strings.Join(additionalFilesRelative, ","))
+	opts.Set("compiler.optimization_flags", compilerOptimizationFlags)
 
-func (s *CreateBuildOptionsMap) Run(ctx *types.Context) error {
-	buildOptions := ctx.ExtractBuildOptions()
-	bytes, err := json.MarshalIndent(buildOptions, "", "  ")
+	buildOptionsJSON, err := json.MarshalIndent(opts, "", "  ")
 	if err != nil {
-		return errors.WithStack(err)
+		return "", errors.WithStack(err)
 	}
-	ctx.BuildOptionsJson = string(bytes)
 
-	return nil
+	return string(buildOptionsJSON), nil
 }
diff --git a/legacy/builder/create_cmake_rule.go b/legacy/builder/create_cmake_rule.go
index 262218150f2..d24b31df8ad 100644
--- a/legacy/builder/create_cmake_rule.go
+++ b/legacy/builder/create_cmake_rule.go
@@ -23,23 +23,29 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
 	"golang.org/x/exp/slices"
 
 	"github.com/arduino/arduino-cli/arduino/builder/utils"
 	"github.com/arduino/arduino-cli/arduino/globals"
+	"github.com/arduino/arduino-cli/arduino/libraries"
+	"github.com/arduino/arduino-cli/arduino/sketch"
 	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
 )
 
-type ExportProjectCMake struct {
-	// Was there an error while compiling the sketch?
-	SketchError bool
-}
-
 var lineMatcher = regexp.MustCompile(`^#line\s\d+\s"`)
 
-func (s *ExportProjectCMake) Run(ctx *types.Context) error {
+func ExportProjectCMake(
+	sketchError bool, // Was there an error while compiling the sketch?
+	buildPath, sketchBuildPath *paths.Path,
+	importedLibraries libraries.List,
+	buildProperties *properties.Map,
+	sketch *sketch.Sketch,
+	includeFolders paths.PathList,
+	lineOffset int,
+	onlyUpdateCompilationDatabase bool,
+) ([]byte, []byte, error) {
 	// copies the contents of the file named src to the file named
 	// by dst. The file will be created if it does not already exist. If the
 	// destination file exists, all it's contents will be replaced by the contents
@@ -175,12 +181,13 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 	}
 	var validStaticLibExtensions = []string{".a"}
 
-	if s.SketchError || !canExportCmakeProject(ctx) {
-		return nil
+	//	If sketch error or cannot export Cmake project
+	if sketchError || buildProperties.Get("compiler.export_cmake") == "" {
+		return nil, nil, nil
 	}
 
 	// Create new cmake subFolder - clean if the folder is already there
-	cmakeFolder := ctx.BuildPath.Join("_cmake")
+	cmakeFolder := buildPath.Join("_cmake")
 	if _, err := cmakeFolder.Stat(); err == nil {
 		cmakeFolder.RemoveAll()
 	}
@@ -197,10 +204,10 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 	cmakeFile := cmakeFolder.Join("CMakeLists.txt")
 
 	dynamicLibsFromPkgConfig := map[string]bool{}
-	for _, library := range ctx.SketchLibrariesDetector.ImportedLibraries() {
+	for _, library := range importedLibraries {
 		// Copy used libraries in the correct folder
 		libDir := libBaseFolder.Join(library.DirName)
-		mcu := ctx.BuildProperties.Get("build.mcu")
+		mcu := buildProperties.Get("build.mcu")
 		copyDir(library.InstallDir.String(), libDir.String(), validExportExtensions)
 
 		// Read cmake options if available
@@ -231,20 +238,28 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 	}
 
 	// Copy core + variant in use + preprocessed sketch in the correct folders
-	err := copyDir(ctx.BuildProperties.Get("build.core.path"), coreFolder.String(), validExportExtensions)
+	err := copyDir(buildProperties.Get("build.core.path"), coreFolder.String(), validExportExtensions)
 	if err != nil {
 		fmt.Println(err)
 	}
-	err = copyDir(ctx.BuildProperties.Get("build.variant.path"), coreFolder.Join("variant").String(), validExportExtensions)
+	err = copyDir(buildProperties.Get("build.variant.path"), coreFolder.Join("variant").String(), validExportExtensions)
 	if err != nil {
 		fmt.Println(err)
 	}
 
-	if err := PreprocessSketch(ctx); err != nil {
-		return err
+	normalOutput, verboseOutput, err := PreprocessSketch(
+		sketch,
+		buildPath,
+		includeFolders,
+		lineOffset,
+		buildProperties,
+		onlyUpdateCompilationDatabase,
+	)
+	if err != nil {
+		return normalOutput, verboseOutput, err
 	}
 
-	err = copyDir(ctx.SketchBuildPath.String(), cmakeFolder.Join("sketch").String(), validExportExtensions)
+	err = copyDir(sketchBuildPath.String(), cmakeFolder.Join("sketch").String(), validExportExtensions)
 	if err != nil {
 		fmt.Println(err)
 	}
@@ -279,9 +294,9 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 	var dynamicLibsFromGccMinusL []string
 	var linkDirectories []string
 
-	extractCompileFlags(ctx, constants.RECIPE_C_COMBINE_PATTERN, &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
-	extractCompileFlags(ctx, "recipe.c.o.pattern", &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
-	extractCompileFlags(ctx, "recipe.cpp.o.pattern", &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
+	extractCompileFlags(buildProperties, constants.RECIPE_C_COMBINE_PATTERN, &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
+	extractCompileFlags(buildProperties, "recipe.c.o.pattern", &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
+	extractCompileFlags(buildProperties, "recipe.cpp.o.pattern", &defines, &dynamicLibsFromGccMinusL, &linkerflags, &linkDirectories)
 
 	// Extract folders with .h in them for adding in include list
 	headerFiles, _ := utils.FindFilesInFolder(cmakeFolder, true, validHeaderExtensions...)
@@ -292,7 +307,7 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 
 	// Generate the CMakeLists global file
 
-	projectName := ctx.Sketch.Name
+	projectName := sketch.Name
 
 	cmakelist := "cmake_minimum_required(VERSION 3.5.0)\n"
 	cmakelist += "INCLUDE(FindPkgConfig)\n"
@@ -349,14 +364,10 @@ func (s *ExportProjectCMake) Run(ctx *types.Context) error {
 
 	cmakeFile.WriteFile([]byte(cmakelist))
 
-	return nil
-}
-
-func canExportCmakeProject(ctx *types.Context) bool {
-	return ctx.BuildProperties.Get("compiler.export_cmake") != ""
+	return normalOutput, verboseOutput, nil
 }
 
-func extractCompileFlags(ctx *types.Context, recipe string, defines, dynamicLibs, linkerflags, linkDirectories *[]string) {
+func extractCompileFlags(buildProperties *properties.Map, recipe string, defines, dynamicLibs, linkerflags, linkDirectories *[]string) {
 	appendIfNotPresent := func(target []string, elements ...string) []string {
 		for _, element := range elements {
 			if !slices.Contains(target, element) {
@@ -366,7 +377,7 @@ func extractCompileFlags(ctx *types.Context, recipe string, defines, dynamicLibs
 		return target
 	}
 
-	command, _ := utils.PrepareCommandForRecipe(ctx.BuildProperties, recipe, true)
+	command, _ := utils.PrepareCommandForRecipe(buildProperties, recipe, true)
 
 	for _, arg := range command.GetArgs() {
 		if strings.HasPrefix(arg, "-D") {
diff --git a/legacy/builder/load_previous_build_options.go b/legacy/builder/load_previous_build_options.go
index b7b03e9c84d..63dd83804e4 100644
--- a/legacy/builder/load_previous_build_options.go
+++ b/legacy/builder/load_previous_build_options.go
@@ -17,24 +17,20 @@ package builder
 
 import (
 	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/go-paths-helper"
 	"github.com/pkg/errors"
 )
 
-type LoadPreviousBuildOptionsMap struct{}
-
-func (s *LoadPreviousBuildOptionsMap) Run(ctx *types.Context) error {
-	buildOptionsFile := ctx.BuildPath.Join(constants.BUILD_OPTIONS_FILE)
+func LoadPreviousBuildOptionsMap(buildPath *paths.Path) (string, error) {
+	buildOptionsFile := buildPath.Join(constants.BUILD_OPTIONS_FILE)
 
 	if buildOptionsFile.NotExist() {
-		return nil
+		return "", nil
 	}
 
-	bytes, err := buildOptionsFile.ReadFile()
+	buildOptionsJsonPrevious, err := buildOptionsFile.ReadFile()
 	if err != nil {
-		return errors.WithStack(err)
+		return "", errors.WithStack(err)
 	}
-
-	ctx.BuildOptionsJsonPrevious = string(bytes)
-	return nil
+	return string(buildOptionsJsonPrevious), nil
 }
diff --git a/legacy/builder/merge_sketch_with_bootloader.go b/legacy/builder/merge_sketch_with_bootloader.go
index 7b9f0da3542..a397ac102f4 100644
--- a/legacy/builder/merge_sketch_with_bootloader.go
+++ b/legacy/builder/merge_sketch_with_bootloader.go
@@ -20,29 +20,30 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/arduino/arduino-cli/arduino/sketch"
 	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
 	"github.com/arduino/go-paths-helper"
+	"github.com/arduino/go-properties-orderedmap"
 	"github.com/marcinbor85/gohex"
 	"github.com/pkg/errors"
 )
 
-type MergeSketchWithBootloader struct{}
-
-func (s *MergeSketchWithBootloader) Run(ctx *types.Context) error {
-	if ctx.OnlyUpdateCompilationDatabase {
+func MergeSketchWithBootloader(
+	onlyUpdateCompilationDatabase, verbose bool,
+	buildPath *paths.Path,
+	sketch *sketch.Sketch,
+	buildProperties *properties.Map,
+	printInfoFn, printWarnFn func(string),
+) error {
+	if onlyUpdateCompilationDatabase {
 		return nil
 	}
 
-	buildProperties := ctx.BuildProperties
 	if !buildProperties.ContainsKey(constants.BUILD_PROPERTIES_BOOTLOADER_NOBLINK) && !buildProperties.ContainsKey(constants.BUILD_PROPERTIES_BOOTLOADER_FILE) {
 		return nil
 	}
 
-	buildPath := ctx.BuildPath
-	sketch := ctx.Sketch
 	sketchFileName := sketch.MainFile.Base()
-
 	sketchInBuildPath := buildPath.Join(sketchFileName + ".hex")
 	sketchInSubfolder := buildPath.Join(constants.FOLDER_SKETCH, sketchFileName+".hex")
 
@@ -65,8 +66,8 @@ func (s *MergeSketchWithBootloader) Run(ctx *types.Context) error {
 
 	bootloaderPath := buildProperties.GetPath("runtime.platform.path").Join(constants.FOLDER_BOOTLOADERS, bootloader)
 	if bootloaderPath.NotExist() {
-		if ctx.Verbose {
-			ctx.Warn(tr("Bootloader file specified but missing: %[1]s", bootloaderPath))
+		if verbose {
+			printWarnFn(tr("Bootloader file specified but missing: %[1]s", bootloaderPath))
 		}
 		return nil
 	}
@@ -75,13 +76,13 @@ func (s *MergeSketchWithBootloader) Run(ctx *types.Context) error {
 
 	// Ignore merger errors for the first iteration
 	maximumBinSize := 16000000
-	if uploadMaxSize, ok := ctx.BuildProperties.GetOk("upload.maximum_size"); ok {
+	if uploadMaxSize, ok := buildProperties.GetOk("upload.maximum_size"); ok {
 		maximumBinSize, _ = strconv.Atoi(uploadMaxSize)
 		maximumBinSize *= 2
 	}
 	err := merge(builtSketchPath, bootloaderPath, mergedSketchPath, maximumBinSize)
-	if err != nil && ctx.Verbose {
-		ctx.Info(err.Error())
+	if err != nil && verbose {
+		printInfoFn(err.Error())
 	}
 
 	return nil
diff --git a/legacy/builder/print_used_libraries_if_verbose.go b/legacy/builder/print_used_libraries_if_verbose.go
index 15211a46519..1ae369d30a2 100644
--- a/legacy/builder/print_used_libraries_if_verbose.go
+++ b/legacy/builder/print_used_libraries_if_verbose.go
@@ -16,31 +16,31 @@
 package builder
 
 import (
+	"bytes"
 	"time"
 
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/arduino-cli/arduino/libraries"
 )
 
-type PrintUsedLibrariesIfVerbose struct{}
-
-func (s *PrintUsedLibrariesIfVerbose) Run(ctx *types.Context) error {
-	if !ctx.Verbose || len(ctx.SketchLibrariesDetector.ImportedLibraries()) == 0 {
-		return nil
+func PrintUsedLibrariesIfVerbose(verbose bool, importedLibraries libraries.List) ([]byte, error) {
+	if !verbose || len(importedLibraries) == 0 {
+		return nil, nil
 	}
 
-	for _, library := range ctx.SketchLibrariesDetector.ImportedLibraries() {
+	infoBuf := &bytes.Buffer{}
+	for _, library := range importedLibraries {
 		legacy := ""
 		if library.IsLegacy {
 			legacy = tr("(legacy)")
 		}
 		if library.Version.String() == "" {
-			ctx.Info(
+			infoBuf.WriteString(
 				tr("Using library %[1]s in folder: %[2]s %[3]s",
 					library.Name,
 					library.InstallDir,
 					legacy))
 		} else {
-			ctx.Info(
+			infoBuf.WriteString(
 				tr("Using library %[1]s at version %[2]s in folder: %[3]s %[4]s",
 					library.Name,
 					library.Version,
@@ -50,5 +50,5 @@ func (s *PrintUsedLibrariesIfVerbose) Run(ctx *types.Context) error {
 	}
 
 	time.Sleep(100 * time.Millisecond)
-	return nil
+	return infoBuf.Bytes(), nil
 }
diff --git a/legacy/builder/recipe_runner.go b/legacy/builder/recipe_runner.go
index 9e8bdcd2425..78215bd211c 100644
--- a/legacy/builder/recipe_runner.go
+++ b/legacy/builder/recipe_runner.go
@@ -17,28 +17,30 @@ package builder
 
 import (
 	"fmt"
+	"io"
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/legacy/builder/types"
 	"github.com/arduino/arduino-cli/arduino/builder/utils"
 	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
-type RecipeByPrefixSuffixRunner struct {
-	Prefix                                string
-	Suffix                                string
-	SkipIfOnlyUpdatingCompilationDatabase bool
-}
-
-func (s *RecipeByPrefixSuffixRunner) Run(ctx *types.Context) error {
-	logrus.Debugf(fmt.Sprintf("Looking for recipes like %s", s.Prefix+"*"+s.Suffix))
+func RecipeByPrefixSuffixRunner(
+	prefix, suffix string,
+	skipIfOnlyUpdatingCompilationDatabase, onlyUpdateCompilationDatabase, verbose bool,
+	buildProps *properties.Map,
+	stdoutWriter, stderrWriter io.Writer,
+	printInfoFn func(string),
+) error {
+	logrus.Debugf(fmt.Sprintf("Looking for recipes like %s", prefix+"*"+suffix))
 
-	buildProperties := ctx.BuildProperties.Clone()
-	recipes := findRecipes(buildProperties, s.Prefix, s.Suffix)
+	// TODO is it necessary to use Clone?
+	buildProperties := buildProps.Clone()
+	recipes := findRecipes(buildProperties, prefix, suffix)
 
+	// TODO is it necessary to use Clone?
 	properties := buildProperties.Clone()
 	for _, recipe := range recipes {
 		logrus.Debugf(fmt.Sprintf("Running recipe: %s", recipe))
@@ -48,16 +50,16 @@ func (s *RecipeByPrefixSuffixRunner) Run(ctx *types.Context) error {
 			return errors.WithStack(err)
 		}
 
-		if ctx.OnlyUpdateCompilationDatabase && s.SkipIfOnlyUpdatingCompilationDatabase {
-			if ctx.Verbose {
-				ctx.Info(tr("Skipping: %[1]s", strings.Join(command.GetArgs(), " ")))
+		if onlyUpdateCompilationDatabase && skipIfOnlyUpdatingCompilationDatabase {
+			if verbose {
+				printInfoFn(tr("Skipping: %[1]s", strings.Join(command.GetArgs(), " ")))
 			}
 			return nil
 		}
 
-		verboseInfo, _, _, err := utils.ExecCommand(ctx.Verbose, ctx.Stdout, ctx.Stderr, command, utils.ShowIfVerbose /* stdout */, utils.Show /* stderr */)
-		if ctx.Verbose {
-			ctx.Info(string(verboseInfo))
+		verboseInfo, _, _, err := utils.ExecCommand(verbose, stdoutWriter, stderrWriter, command, utils.ShowIfVerbose /* stdout */, utils.Show /* stderr */)
+		if verbose {
+			printInfoFn(string(verboseInfo))
 		}
 		if err != nil {
 			return errors.WithStack(err)
@@ -65,7 +67,6 @@ func (s *RecipeByPrefixSuffixRunner) Run(ctx *types.Context) error {
 	}
 
 	return nil
-
 }
 
 func findRecipes(buildProperties *properties.Map, patternPrefix string, patternSuffix string) []string {
diff --git a/legacy/builder/store_build_options_map.go b/legacy/builder/store_build_options_map.go
index 28456113c15..bc872d76d63 100644
--- a/legacy/builder/store_build_options_map.go
+++ b/legacy/builder/store_build_options_map.go
@@ -17,12 +17,9 @@ package builder
 
 import (
 	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/go-paths-helper"
 )
 
-type StoreBuildOptionsMap struct{}
-
-func (s *StoreBuildOptionsMap) Run(ctx *types.Context) error {
-	ctx.BuildPath.Join(constants.BUILD_OPTIONS_FILE).WriteFile([]byte(ctx.BuildOptionsJson))
-	return nil
+func StoreBuildOptionsMap(buildPath *paths.Path, buildOptionsJson string) error {
+	return buildPath.Join(constants.BUILD_OPTIONS_FILE).WriteFile([]byte(buildOptionsJson))
 }
diff --git a/legacy/builder/test/builder_test.go b/legacy/builder/test/builder_test.go
index a9ba6865561..4ec13627315 100644
--- a/legacy/builder/test/builder_test.go
+++ b/legacy/builder/test/builder_test.go
@@ -91,13 +91,14 @@ func prepareBuilderTestContext(t *testing.T, ctx *types.Context, sketchPath *pat
 	pme, _ /* never release... */ := pm.NewExplorer()
 	ctx.PackageManager = pme
 
+	var sk *sketch.Sketch
 	if sketchPath != nil {
-		sk, err := sketch.New(sketchPath)
+		s, err := sketch.New(sketchPath)
 		require.NoError(t, err)
-		ctx.Sketch = sk
+		sk = s
 	}
 
-	ctx.Builder = bldr.NewBuilder(ctx.Sketch, nil, nil, false, nil)
+	ctx.Builder = bldr.NewBuilder(sk, nil, nil, false, nil, 0)
 	if fqbn != "" {
 		ctx.FQBN = parseFQBN(t, fqbn)
 		targetPackage, targetPlatform, targetBoard, boardBuildProperties, buildPlatform, err := pme.ResolveFQBN(ctx.FQBN)
@@ -105,7 +106,7 @@ func prepareBuilderTestContext(t *testing.T, ctx *types.Context, sketchPath *pat
 		requiredTools, err := pme.FindToolsRequiredForBuild(targetPlatform, buildPlatform)
 		require.NoError(t, err)
 
-		ctx.Builder = bldr.NewBuilder(ctx.Sketch, boardBuildProperties, ctx.BuildPath, false /*OptimizeForDebug*/, nil)
+		ctx.Builder = bldr.NewBuilder(sk, boardBuildProperties, ctx.BuildPath, false /*OptimizeForDebug*/, nil, 0)
 		ctx.PackageManager = pme
 		ctx.TargetBoard = targetBoard
 		ctx.BuildProperties = ctx.Builder.GetBuildProperties()
@@ -115,8 +116,8 @@ func prepareBuilderTestContext(t *testing.T, ctx *types.Context, sketchPath *pat
 		ctx.RequiredTools = requiredTools
 	}
 
-	if ctx.Sketch != nil {
-		require.False(t, ctx.BuildPath.Canonical().EqualsTo(ctx.Sketch.FullPath.Canonical()))
+	if sk != nil {
+		require.False(t, ctx.BuildPath.Canonical().EqualsTo(sk.FullPath.Canonical()))
 	}
 
 	if !stepToSkip[skipLibraries] {
diff --git a/legacy/builder/test/create_build_options_map_test.go b/legacy/builder/test/create_build_options_map_test.go
index 4cd1986f55d..b2d90ffa253 100644
--- a/legacy/builder/test/create_build_options_map_test.go
+++ b/legacy/builder/test/create_build_options_map_test.go
@@ -31,15 +31,17 @@ func TestCreateBuildOptionsMap(t *testing.T) {
 		HardwareDirs:       paths.NewPathList("hardware", "hardware2"),
 		BuiltInToolsDirs:   paths.NewPathList("tools"),
 		OtherLibrariesDirs: paths.NewPathList("libraries"),
-		Sketch:             &sketch.Sketch{FullPath: paths.New("sketchLocation")},
 		FQBN:               parseFQBN(t, "my:nice:fqbn"),
 		Verbose:            true,
 		BuildPath:          paths.New("buildPath"),
 		BuildProperties:    properties.NewFromHashmap(map[string]string{"compiler.optimization_flags": "-Os"}),
 	}
 
-	create := builder.CreateBuildOptionsMap{}
-	err := create.Run(ctx)
+	buildPropertiesJSON, err := builder.CreateBuildOptionsMap(
+		ctx.HardwareDirs, ctx.BuiltInToolsDirs, ctx.OtherLibrariesDirs,
+		ctx.BuiltInLibrariesDirs, &sketch.Sketch{FullPath: paths.New("sketchLocation")}, ctx.CustomBuildProperties,
+		ctx.FQBN.String(), ctx.BuildProperties.Get("compiler.optimization_flags"),
+	)
 	require.NoError(t, err)
 
 	require.Equal(t, `{
@@ -51,5 +53,5 @@ func TestCreateBuildOptionsMap(t *testing.T) {
   "hardwareFolders": "hardware,hardware2",
   "otherLibrariesFolders": "libraries",
   "sketchLocation": "sketchLocation"
-}`, ctx.BuildOptionsJson)
+}`, buildPropertiesJSON)
 }
diff --git a/legacy/builder/test/load_previous_build_options_map_test.go b/legacy/builder/test/load_previous_build_options_map_test.go
index b137b6e4b45..88613fb9034 100644
--- a/legacy/builder/test/load_previous_build_options_map_test.go
+++ b/legacy/builder/test/load_previous_build_options_map_test.go
@@ -33,11 +33,10 @@ func TestLoadPreviousBuildOptionsMap(t *testing.T) {
 	err := buildPath.Join(constants.BUILD_OPTIONS_FILE).WriteFile([]byte("test"))
 	require.NoError(t, err)
 
-	command := builder.LoadPreviousBuildOptionsMap{}
-	err = command.Run(ctx)
+	buildOptionsJsonPrevious, err := builder.LoadPreviousBuildOptionsMap(ctx.BuildPath)
 	require.NoError(t, err)
 
-	require.Equal(t, "test", ctx.BuildOptionsJsonPrevious)
+	require.Equal(t, "test", buildOptionsJsonPrevious)
 }
 
 func TestLoadPreviousBuildOptionsMapMissingFile(t *testing.T) {
@@ -46,9 +45,7 @@ func TestLoadPreviousBuildOptionsMapMissingFile(t *testing.T) {
 	buildPath := SetupBuildPath(t, ctx)
 	defer buildPath.RemoveAll()
 
-	command := builder.LoadPreviousBuildOptionsMap{}
-	err := command.Run(ctx)
+	buildOptionsJsonPrevious, err := builder.LoadPreviousBuildOptionsMap(ctx.BuildPath)
 	require.NoError(t, err)
-
-	require.Empty(t, ctx.BuildOptionsJsonPrevious)
+	require.Empty(t, buildOptionsJsonPrevious)
 }
diff --git a/legacy/builder/test/merge_sketch_with_bootloader_test.go b/legacy/builder/test/merge_sketch_with_bootloader_test.go
index 6dac87c05b3..6305a174928 100644
--- a/legacy/builder/test/merge_sketch_with_bootloader_test.go
+++ b/legacy/builder/test/merge_sketch_with_bootloader_test.go
@@ -69,14 +69,13 @@ func TestMergeSketchWithBootloader(t *testing.T) {
 	err = buildPath.Join("sketch", "sketch1.ino.hex").WriteFile([]byte(fakeSketchHex))
 	require.NoError(t, err)
 
-	commands := []types.Command{
-		&builder.MergeSketchWithBootloader{},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	err = builder.MergeSketchWithBootloader(
+		ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+		ctx.BuildPath, ctx.Builder.Sketch(), ctx.BuildProperties,
+		func(s string) { ctx.Info(s) },
+		func(s string) { ctx.Warn(s) },
+	)
+	require.NoError(t, err)
 
 	bytes, err := buildPath.Join("sketch", "sketch1.ino.with_bootloader.hex").ReadFile()
 	require.NoError(t, err)
@@ -127,14 +126,13 @@ func TestMergeSketchWithBootloaderSketchInBuildPath(t *testing.T) {
 	err = buildPath.Join("sketch1.ino.hex").WriteFile([]byte(fakeSketchHex))
 	require.NoError(t, err)
 
-	commands := []types.Command{
-		&builder.MergeSketchWithBootloader{},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	err = builder.MergeSketchWithBootloader(
+		ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+		ctx.BuildPath, ctx.Builder.Sketch(), ctx.BuildProperties,
+		func(s string) { ctx.Info(s) },
+		func(s string) { ctx.Warn(s) },
+	)
+	require.NoError(t, err)
 
 	bytes, err := buildPath.Join("sketch1.ino.with_bootloader.hex").ReadFile()
 	require.NoError(t, err)
@@ -154,8 +152,12 @@ func TestMergeSketchWithBootloaderWhenNoBootloaderAvailable(t *testing.T) {
 	buildProperties.Remove(constants.BUILD_PROPERTIES_BOOTLOADER_NOBLINK)
 	buildProperties.Remove(constants.BUILD_PROPERTIES_BOOTLOADER_FILE)
 
-	command := &builder.MergeSketchWithBootloader{}
-	err := command.Run(ctx)
+	err := builder.MergeSketchWithBootloader(
+		ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+		ctx.BuildPath, ctx.Builder.Sketch(), ctx.BuildProperties,
+		func(s string) { ctx.Info(s) },
+		func(s string) { ctx.Warn(s) },
+	)
 	require.NoError(t, err)
 
 	exist, err := buildPath.Join("sketch.ino.with_bootloader.hex").ExistCheck()
@@ -210,14 +212,13 @@ func TestMergeSketchWithBootloaderPathIsParameterized(t *testing.T) {
 	err = buildPath.Join("sketch", "sketch1.ino.hex").WriteFile([]byte(fakeSketchHex))
 	require.NoError(t, err)
 
-	commands := []types.Command{
-		&builder.MergeSketchWithBootloader{},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	err = builder.MergeSketchWithBootloader(
+		ctx.OnlyUpdateCompilationDatabase, ctx.Verbose,
+		ctx.BuildPath, ctx.Builder.Sketch(), ctx.BuildProperties,
+		func(s string) { ctx.Info(s) },
+		func(s string) { ctx.Warn(s) },
+	)
+	require.NoError(t, err)
 
 	bytes, err := buildPath.Join("sketch", "sketch1.ino.with_bootloader.hex").ReadFile()
 	require.NoError(t, err)
diff --git a/legacy/builder/test/recipe_runner_test.go b/legacy/builder/test/recipe_runner_test.go
deleted file mode 100644
index 72a23353faf..00000000000
--- a/legacy/builder/test/recipe_runner_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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 test
-
-import (
-	"testing"
-
-	"github.com/arduino/arduino-cli/legacy/builder"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
-	"github.com/arduino/go-properties-orderedmap"
-	"github.com/stretchr/testify/require"
-)
-
-// TODO
-// I can't find a command I can run on linux, mac and windows
-// and that allows to test if the recipe is actually run
-// So this test is pretty useless
-func TestRecipeRunner(t *testing.T) {
-	ctx := &types.Context{}
-	buildProperties := properties.NewMap()
-	ctx.BuildProperties = buildProperties
-
-	buildProperties.Set("recipe.hooks.prebuild.1.pattern", "echo")
-
-	commands := []types.Command{
-		&builder.RecipeByPrefixSuffixRunner{Prefix: "recipe.hooks.prebuild", Suffix: ".pattern"},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
-}
diff --git a/legacy/builder/test/store_build_options_map_test.go b/legacy/builder/test/store_build_options_map_test.go
index 25f5866a9af..a532ac5007a 100644
--- a/legacy/builder/test/store_build_options_map_test.go
+++ b/legacy/builder/test/store_build_options_map_test.go
@@ -33,7 +33,6 @@ func TestStoreBuildOptionsMap(t *testing.T) {
 		BuiltInToolsDirs:      paths.NewPathList("tools"),
 		BuiltInLibrariesDirs:  paths.New("built-in libraries"),
 		OtherLibrariesDirs:    paths.NewPathList("libraries"),
-		Sketch:                &sketch.Sketch{FullPath: paths.New("sketchLocation")},
 		FQBN:                  parseFQBN(t, "my:nice:fqbn"),
 		CustomBuildProperties: []string{"custom=prop"},
 		Verbose:               true,
@@ -43,15 +42,16 @@ func TestStoreBuildOptionsMap(t *testing.T) {
 	buildPath := SetupBuildPath(t, ctx)
 	defer buildPath.RemoveAll()
 
-	commands := []types.Command{
-		&builder.CreateBuildOptionsMap{},
-		&builder.StoreBuildOptionsMap{},
-	}
+	buildPropertiesJSON, err := builder.CreateBuildOptionsMap(
+		ctx.HardwareDirs, ctx.BuiltInToolsDirs, ctx.OtherLibrariesDirs,
+		ctx.BuiltInLibrariesDirs, &sketch.Sketch{FullPath: paths.New("sketchLocation")}, ctx.CustomBuildProperties,
+		ctx.FQBN.String(), ctx.BuildProperties.Get("compiler.optimization_flags"),
+	)
+	require.NoError(t, err)
+	ctx.BuildOptionsJson = buildPropertiesJSON
 
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	err = builder.StoreBuildOptionsMap(ctx.BuildPath, ctx.BuildOptionsJson)
+	require.NoError(t, err)
 
 	exist, err := buildPath.Join(constants.BUILD_OPTIONS_FILE).ExistCheck()
 	require.NoError(t, err)
diff --git a/legacy/builder/test/unused_compiled_libraries_remover_test.go b/legacy/builder/test/unused_compiled_libraries_remover_test.go
index d8586eb7996..6f3217d505b 100644
--- a/legacy/builder/test/unused_compiled_libraries_remover_test.go
+++ b/legacy/builder/test/unused_compiled_libraries_remover_test.go
@@ -42,8 +42,10 @@ func TestUnusedCompiledLibrariesRemover(t *testing.T) {
 	)
 	ctx.SketchLibrariesDetector.AppendImportedLibraries(&libraries.Library{Name: "Bridge"})
 
-	cmd := builder.UnusedCompiledLibrariesRemover{}
-	err = cmd.Run(ctx)
+	err = builder.UnusedCompiledLibrariesRemover(
+		ctx.LibrariesBuildPath,
+		ctx.SketchLibrariesDetector.ImportedLibraries(),
+	)
 	require.NoError(t, err)
 
 	exist, err := temp.Join("SPI").ExistCheck()
@@ -65,8 +67,10 @@ func TestUnusedCompiledLibrariesRemoverLibDoesNotExist(t *testing.T) {
 	)
 	ctx.SketchLibrariesDetector.AppendImportedLibraries(&libraries.Library{Name: "Bridge"})
 
-	cmd := builder.UnusedCompiledLibrariesRemover{}
-	err := cmd.Run(ctx)
+	err := builder.UnusedCompiledLibrariesRemover(
+		ctx.LibrariesBuildPath,
+		ctx.SketchLibrariesDetector.ImportedLibraries(),
+	)
 	require.NoError(t, err)
 }
 
@@ -85,8 +89,10 @@ func TestUnusedCompiledLibrariesRemoverNoUsedLibraries(t *testing.T) {
 	)
 	ctx.LibrariesBuildPath = temp
 
-	cmd := builder.UnusedCompiledLibrariesRemover{}
-	err = cmd.Run(ctx)
+	err = builder.UnusedCompiledLibrariesRemover(
+		ctx.LibrariesBuildPath,
+		ctx.SketchLibrariesDetector.ImportedLibraries(),
+	)
 	require.NoError(t, err)
 
 	exist, err := temp.Join("SPI").ExistCheck()
diff --git a/legacy/builder/test/wipeout_build_path_if_build_options_changed_test.go b/legacy/builder/test/wipeout_build_path_if_build_options_changed_test.go
index dd72c1926e9..7df448e9b54 100644
--- a/legacy/builder/test/wipeout_build_path_if_build_options_changed_test.go
+++ b/legacy/builder/test/wipeout_build_path_if_build_options_changed_test.go
@@ -34,14 +34,14 @@ func TestWipeoutBuildPathIfBuildOptionsChanged(t *testing.T) {
 
 	buildPath.Join("should_be_deleted.txt").Truncate()
 
-	commands := []types.Command{
-		&builder.WipeoutBuildPathIfBuildOptionsChanged{},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	_, err := builder.WipeoutBuildPathIfBuildOptionsChanged(
+		ctx.Clean,
+		ctx.BuildPath,
+		ctx.BuildOptionsJson,
+		ctx.BuildOptionsJsonPrevious,
+		ctx.BuildProperties,
+	)
+	require.NoError(t, err)
 
 	exist, err := buildPath.ExistCheck()
 	require.NoError(t, err)
@@ -66,14 +66,14 @@ func TestWipeoutBuildPathIfBuildOptionsChangedNoPreviousBuildOptions(t *testing.
 
 	require.NoError(t, buildPath.Join("should_not_be_deleted.txt").Truncate())
 
-	commands := []types.Command{
-		&builder.WipeoutBuildPathIfBuildOptionsChanged{},
-	}
-
-	for _, command := range commands {
-		err := command.Run(ctx)
-		require.NoError(t, err)
-	}
+	_, err := builder.WipeoutBuildPathIfBuildOptionsChanged(
+		ctx.Clean,
+		ctx.BuildPath,
+		ctx.BuildOptionsJson,
+		ctx.BuildOptionsJsonPrevious,
+		ctx.BuildProperties,
+	)
+	require.NoError(t, err)
 
 	exist, err := buildPath.ExistCheck()
 	require.NoError(t, err)
diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go
index 0c9b1a18458..6d7ac15952f 100644
--- a/legacy/builder/types/context.go
+++ b/legacy/builder/types/context.go
@@ -19,7 +19,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"strings"
 	"sync"
 
 	"github.com/arduino/arduino-cli/arduino/builder"
@@ -27,7 +26,6 @@ import (
 	"github.com/arduino/arduino-cli/arduino/builder/progress"
 	"github.com/arduino/arduino-cli/arduino/cores"
 	"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
-	"github.com/arduino/arduino-cli/arduino/sketch"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
@@ -67,7 +65,6 @@ type Context struct {
 	LibrariesObjectFiles paths.PathList
 	SketchObjectFiles    paths.PathList
 
-	Sketch        *sketch.Sketch
 	WarningsLevel string
 
 	// C++ Parsing
@@ -84,9 +81,6 @@ type Context struct {
 	// Custom build properties defined by user (line by line as "key=value" pairs)
 	CustomBuildProperties []string
 
-	// Parallel processes
-	Jobs int
-
 	// Out and Err stream to redirect all output
 	Stdout  io.Writer
 	Stderr  io.Writer
@@ -106,31 +100,6 @@ type Context struct {
 	SourceOverride map[string]string
 }
 
-func (ctx *Context) ExtractBuildOptions() *properties.Map {
-	opts := properties.NewMap()
-	opts.Set("hardwareFolders", strings.Join(ctx.HardwareDirs.AsStrings(), ","))
-	opts.Set("builtInToolsFolders", strings.Join(ctx.BuiltInToolsDirs.AsStrings(), ","))
-	if ctx.BuiltInLibrariesDirs != nil {
-		opts.Set("builtInLibrariesFolders", ctx.BuiltInLibrariesDirs.String())
-	}
-	opts.Set("otherLibrariesFolders", strings.Join(ctx.OtherLibrariesDirs.AsStrings(), ","))
-	opts.SetPath("sketchLocation", ctx.Sketch.FullPath)
-	var additionalFilesRelative []string
-	absPath := ctx.Sketch.FullPath.Parent()
-	for _, f := range ctx.Sketch.AdditionalFiles {
-		relPath, err := f.RelTo(absPath)
-		if err != nil {
-			continue // ignore
-		}
-		additionalFilesRelative = append(additionalFilesRelative, relPath.String())
-	}
-	opts.Set("fqbn", ctx.FQBN.String())
-	opts.Set("customBuildProperties", strings.Join(ctx.CustomBuildProperties, ","))
-	opts.Set("additionalFiles", strings.Join(additionalFilesRelative, ","))
-	opts.Set("compiler.optimization_flags", ctx.BuildProperties.Get("compiler.optimization_flags"))
-	return opts
-}
-
 func (ctx *Context) PushProgress() {
 	if ctx.ProgressCB != nil {
 		ctx.ProgressCB(&rpc.TaskProgress{
diff --git a/legacy/builder/types/context_test.go b/legacy/builder/types/context_test.go
deleted file mode 100644
index 491c49acf6e..00000000000
--- a/legacy/builder/types/context_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2022 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 types
-
-import (
-	"testing"
-
-	"github.com/arduino/arduino-cli/arduino/cores"
-	"github.com/arduino/arduino-cli/arduino/sketch"
-	paths "github.com/arduino/go-paths-helper"
-	"github.com/arduino/go-properties-orderedmap"
-	"github.com/stretchr/testify/require"
-)
-
-func TestInjectBuildOption(t *testing.T) {
-	fqbn, err := cores.ParseFQBN("aaa:bbb:ccc")
-	require.NoError(t, err)
-
-	{
-		ctx := &Context{
-			HardwareDirs:          paths.NewPathList("aaa", "bbb"),
-			BuiltInToolsDirs:      paths.NewPathList("ccc", "ddd"),
-			BuiltInLibrariesDirs:  paths.New("eee"),
-			OtherLibrariesDirs:    paths.NewPathList("fff", "ggg"),
-			Sketch:                &sketch.Sketch{FullPath: paths.New("hhh")},
-			FQBN:                  fqbn,
-			CustomBuildProperties: []string{"jjj", "kkk"},
-			BuildProperties:       properties.NewFromHashmap(map[string]string{"compiler.optimization_flags": "lll"}),
-		}
-		opts := ctx.ExtractBuildOptions()
-		require.Equal(t, `properties.Map{
-  "hardwareFolders": "aaa,bbb",
-  "builtInToolsFolders": "ccc,ddd",
-  "builtInLibrariesFolders": "eee",
-  "otherLibrariesFolders": "fff,ggg",
-  "sketchLocation": "hhh",
-  "fqbn": "aaa:bbb:ccc",
-  "customBuildProperties": "jjj,kkk",
-  "additionalFiles": "",
-  "compiler.optimization_flags": "lll",
-}`, opts.Dump())
-	}
-}
diff --git a/legacy/builder/unused_compiled_libraries_remover.go b/legacy/builder/unused_compiled_libraries_remover.go
index 03ea5b85d01..cea88bd9db6 100644
--- a/legacy/builder/unused_compiled_libraries_remover.go
+++ b/legacy/builder/unused_compiled_libraries_remover.go
@@ -17,26 +17,22 @@ package builder
 
 import (
 	"github.com/arduino/arduino-cli/arduino/libraries"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/go-paths-helper"
 	"github.com/pkg/errors"
 	"golang.org/x/exp/slices"
 )
 
-type UnusedCompiledLibrariesRemover struct{}
-
-func (s *UnusedCompiledLibrariesRemover) Run(ctx *types.Context) error {
-	librariesBuildPath := ctx.LibrariesBuildPath
-
+func UnusedCompiledLibrariesRemover(librariesBuildPath *paths.Path, importedLibraries libraries.List) error {
 	if librariesBuildPath.NotExist() {
 		return nil
 	}
 
-	libraryNames := toLibraryNames(ctx.SketchLibrariesDetector.ImportedLibraries())
-
 	files, err := librariesBuildPath.ReadDir()
 	if err != nil {
 		return errors.WithStack(err)
 	}
+
+	libraryNames := toLibraryNames(importedLibraries)
 	for _, file := range files {
 		if file.IsDir() {
 			if !slices.Contains(libraryNames, file.Base()) {
diff --git a/legacy/builder/warn_about_arch_incompatible_libraries.go b/legacy/builder/warn_about_arch_incompatible_libraries.go
index c815a54f3aa..ea7392560bc 100644
--- a/legacy/builder/warn_about_arch_incompatible_libraries.go
+++ b/legacy/builder/warn_about_arch_incompatible_libraries.go
@@ -18,30 +18,28 @@ package builder
 import (
 	"strings"
 
-	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
+	"github.com/arduino/arduino-cli/arduino/cores"
+	"github.com/arduino/arduino-cli/arduino/libraries"
 )
 
-type WarnAboutArchIncompatibleLibraries struct{}
-
-func (s *WarnAboutArchIncompatibleLibraries) Run(ctx *types.Context) error {
-	targetPlatform := ctx.TargetPlatform
-	buildProperties := ctx.BuildProperties
-
+func WarnAboutArchIncompatibleLibraries(
+	targetPlatform *cores.PlatformRelease,
+	overrides string,
+	importedLibraries libraries.List,
+	printInfoFn func(string),
+) {
 	archs := []string{targetPlatform.Platform.Architecture}
-	if overrides, ok := buildProperties.GetOk(constants.BUILD_PROPERTIES_ARCH_OVERRIDE_CHECK); ok {
+	if overrides != "" {
 		archs = append(archs, strings.Split(overrides, ",")...)
 	}
 
-	for _, importedLibrary := range ctx.SketchLibrariesDetector.ImportedLibraries() {
+	for _, importedLibrary := range importedLibraries {
 		if !importedLibrary.SupportsAnyArchitectureIn(archs...) {
-			ctx.Info(
+			printInfoFn(
 				tr("WARNING: library %[1]s claims to run on %[2]s architecture(s) and may be incompatible with your current board which runs on %[3]s architecture(s).",
 					importedLibrary.Name,
 					strings.Join(importedLibrary.Architectures, ", "),
 					strings.Join(archs, ", ")))
 		}
 	}
-
-	return nil
 }
diff --git a/legacy/builder/wipeout_build_path_if_build_options_changed.go b/legacy/builder/wipeout_build_path_if_build_options_changed.go
index 5b429971901..108664978ca 100644
--- a/legacy/builder/wipeout_build_path_if_build_options_changed.go
+++ b/legacy/builder/wipeout_build_path_if_build_options_changed.go
@@ -21,23 +21,23 @@ import (
 
 	"github.com/arduino/arduino-cli/arduino/builder/utils"
 	"github.com/arduino/arduino-cli/legacy/builder/constants"
-	"github.com/arduino/arduino-cli/legacy/builder/types"
 	"github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/pkg/errors"
 )
 
-type WipeoutBuildPathIfBuildOptionsChanged struct{}
-
-func (s *WipeoutBuildPathIfBuildOptionsChanged) Run(ctx *types.Context) error {
-	if ctx.Clean {
-		return doCleanup(ctx.BuildPath)
+func WipeoutBuildPathIfBuildOptionsChanged(
+	clean bool,
+	buildPath *paths.Path,
+	buildOptionsJson, buildOptionsJsonPrevious string,
+	buildProperties *properties.Map,
+) (string, error) {
+	if clean {
+		return "", doCleanup(buildPath)
 	}
-	if ctx.BuildOptionsJsonPrevious == "" {
-		return nil
+	if buildOptionsJsonPrevious == "" {
+		return "", nil
 	}
-	buildOptionsJson := ctx.BuildOptionsJson
-	previousBuildOptionsJson := ctx.BuildOptionsJsonPrevious
 
 	var opts *properties.Map
 	if err := json.Unmarshal([]byte(buildOptionsJson), &opts); err != nil || opts == nil {
@@ -45,9 +45,8 @@ func (s *WipeoutBuildPathIfBuildOptionsChanged) Run(ctx *types.Context) error {
 	}
 
 	var prevOpts *properties.Map
-	if err := json.Unmarshal([]byte(previousBuildOptionsJson), &prevOpts); err != nil || prevOpts == nil {
-		ctx.Info(tr("%[1]s invalid, rebuilding all", constants.BUILD_OPTIONS_FILE))
-		return doCleanup(ctx.BuildPath)
+	if err := json.Unmarshal([]byte(buildOptionsJsonPrevious), &prevOpts); err != nil || prevOpts == nil {
+		return tr("%[1]s invalid, rebuilding all", constants.BUILD_OPTIONS_FILE), doCleanup(buildPath)
 	}
 
 	// If SketchLocation path is different but filename is the same, consider it equal
@@ -61,21 +60,20 @@ func (s *WipeoutBuildPathIfBuildOptionsChanged) Run(ctx *types.Context) error {
 		// check if any of the files contained in the core folders has changed
 		// since the json was generated - like platform.txt or similar
 		// if so, trigger a "safety" wipe
-		buildProperties := ctx.BuildProperties
 		targetCoreFolder := buildProperties.GetPath("runtime.platform.path")
 		coreFolder := buildProperties.GetPath("build.core.path")
 		realCoreFolder := coreFolder.Parent().Parent()
-		jsonPath := ctx.BuildPath.Join(constants.BUILD_OPTIONS_FILE)
+		jsonPath := buildPath.Join(constants.BUILD_OPTIONS_FILE)
 		coreUnchanged, _ := utils.DirContentIsOlderThan(realCoreFolder, jsonPath, ".txt")
 		if coreUnchanged && targetCoreFolder != nil && !realCoreFolder.EqualsTo(targetCoreFolder) {
 			coreUnchanged, _ = utils.DirContentIsOlderThan(targetCoreFolder, jsonPath, ".txt")
 		}
 		if coreUnchanged {
-			return nil
+			return "", nil
 		}
 	}
 
-	return doCleanup(ctx.BuildPath)
+	return "", doCleanup(buildPath)
 }
 
 func doCleanup(buildPath *paths.Path) error {