diff --git a/arduino/builder/sizer.go b/arduino/builder/sizer.go new file mode 100644 index 00000000000..911f261af67 --- /dev/null +++ b/arduino/builder/sizer.go @@ -0,0 +1,26 @@ +package builder + +import rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + +// ExecutableSectionSize represents a section of the executable output file +type ExecutableSectionSize struct { + Name string `json:"name"` + Size int `json:"size"` + MaxSize int `json:"max_size"` +} + +// ExecutablesFileSections is an array of ExecutablesFileSection +type ExecutablesFileSections []ExecutableSectionSize + +// ToRPCExecutableSectionSizeArray transforms this array into a []*rpc.ExecutableSectionSize +func (s ExecutablesFileSections) ToRPCExecutableSectionSizeArray() []*rpc.ExecutableSectionSize { + res := []*rpc.ExecutableSectionSize{} + for _, section := range s { + res = append(res, &rpc.ExecutableSectionSize{ + Name: section.Name, + Size: int64(section.Size), + MaxSize: int64(section.MaxSize), + }) + } + return res +} diff --git a/legacy/builder/builder.go b/legacy/builder/builder.go index 278245fcf9f..2ad23985ce5 100644 --- a/legacy/builder/builder.go +++ b/legacy/builder/builder.go @@ -204,7 +204,18 @@ func (s *Builder) Run(ctx *types.Context) error { &ExportProjectCMake{SketchError: mainErr != nil}, - &phases.Sizer{SketchError: mainErr != nil}, + types.BareCommand(func(ctx *types.Context) error { + executableSectionsSize, err := phases.Sizer( + ctx.OnlyUpdateCompilationDatabase, mainErr != nil, ctx.Verbose, + ctx.BuildProperties, + ctx.Stdout, ctx.Stderr, + func(msg string) { ctx.Info(msg) }, + func(msg string) { ctx.Warn(msg) }, + ctx.WarningsLevel, + ) + ctx.ExecutableSectionsSize = executableSectionsSize + return err + }), } for _, command := range commands { PrintRingNameIfDebug(ctx, command) diff --git a/legacy/builder/phases/sizer.go b/legacy/builder/phases/sizer.go index b0261daa105..cfc28f987c1 100644 --- a/legacy/builder/phases/sizer.go +++ b/legacy/builder/phases/sizer.go @@ -18,48 +18,50 @@ package phases import ( "encoding/json" "fmt" + "io" "regexp" "strconv" + "github.com/arduino/arduino-cli/arduino/builder" "github.com/arduino/arduino-cli/arduino/builder/utils" - "github.com/arduino/arduino-cli/legacy/builder/types" "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" ) -type Sizer struct { - SketchError bool -} - -func (s *Sizer) Run(ctx *types.Context) error { - if ctx.OnlyUpdateCompilationDatabase { - return nil +func Sizer( + onlyUpdateCompilationDatabase, sketchError, verbose bool, + buildProperties *properties.Map, + stdoutWriter, stderrWriter io.Writer, + printInfoFn, printWarnFn func(msg string), + warningsLevel string, +) (builder.ExecutablesFileSections, error) { + if onlyUpdateCompilationDatabase || sketchError { + return nil, nil } - if s.SketchError { - return nil - } - - buildProperties := ctx.BuildProperties if buildProperties.ContainsKey("recipe.advanced_size.pattern") { - return checkSizeAdvanced(ctx, buildProperties) + return checkSizeAdvanced(buildProperties, verbose, stdoutWriter, stderrWriter, printInfoFn, printWarnFn) } - return checkSize(ctx, buildProperties) + return checkSize(buildProperties, verbose, stdoutWriter, stderrWriter, printInfoFn, printWarnFn, warningsLevel) } -func checkSizeAdvanced(ctx *types.Context, properties *properties.Map) error { - command, err := utils.PrepareCommandForRecipe(properties, "recipe.advanced_size.pattern", false) +func checkSizeAdvanced(buildProperties *properties.Map, + verbose bool, + stdoutWriter, stderrWriter io.Writer, + printInfoFn, printWarnFn func(msg string), +) (builder.ExecutablesFileSections, error) { + command, err := utils.PrepareCommandForRecipe(buildProperties, "recipe.advanced_size.pattern", false) if err != nil { - return errors.New(tr("Error while determining sketch size: %s", err)) + return nil, errors.New(tr("Error while determining sketch size: %s", err)) } - verboseInfo, out, _, err := utils.ExecCommand(ctx.Verbose, ctx.Stdout, ctx.Stderr, command, utils.Capture /* stdout */, utils.Show /* stderr */) - if ctx.Verbose { - ctx.Info(string(verboseInfo)) + verboseInfo, out, _, err := utils.ExecCommand(verbose, stdoutWriter, stderrWriter, command, utils.Capture /* stdout */, utils.Show /* stderr */) + if verbose { + printInfoFn(string(verboseInfo)) } if err != nil { - return errors.New(tr("Error while determining sketch size: %s", err)) + return nil, errors.New(tr("Error while determining sketch size: %s", err)) } type AdvancedSizerResponse struct { @@ -69,7 +71,7 @@ func checkSizeAdvanced(ctx *types.Context, properties *properties.Map) error { // likely be printed in red. Errors will stop build/upload. Severity string `json:"severity"` // Sections are the sections sizes for machine readable use - Sections []types.ExecutableSectionSize `json:"sections"` + Sections []builder.ExecutableSectionSize `json:"sections"` // ErrorMessage is a one line error message like: // "text section exceeds available space in board" // it must be set when Severity is "error" @@ -78,71 +80,76 @@ func checkSizeAdvanced(ctx *types.Context, properties *properties.Map) error { var resp AdvancedSizerResponse if err := json.Unmarshal(out, &resp); err != nil { - return errors.New(tr("Error while determining sketch size: %s", err)) + return nil, errors.New(tr("Error while determining sketch size: %s", err)) } - ctx.ExecutableSectionsSize = resp.Sections + executableSectionsSize := resp.Sections switch resp.Severity { case "error": - ctx.Warn(resp.Output) - return errors.New(resp.ErrorMessage) + printWarnFn(resp.Output) + return executableSectionsSize, errors.New(resp.ErrorMessage) case "warning": - ctx.Warn(resp.Output) + printWarnFn(resp.Output) case "info": - ctx.Info(resp.Output) + printInfoFn(resp.Output) default: - return fmt.Errorf("invalid '%s' severity from sketch sizer: it must be 'error', 'warning' or 'info'", resp.Severity) + return executableSectionsSize, fmt.Errorf("invalid '%s' severity from sketch sizer: it must be 'error', 'warning' or 'info'", resp.Severity) } - return nil + return executableSectionsSize, nil } -func checkSize(ctx *types.Context, buildProperties *properties.Map) error { +func checkSize(buildProperties *properties.Map, + verbose bool, + stdoutWriter, stderrWriter io.Writer, + printInfoFn, printWarnFn func(msg string), + warningsLevel string, +) (builder.ExecutablesFileSections, error) { properties := buildProperties.Clone() - properties.Set("compiler.warning_flags", properties.Get("compiler.warning_flags."+ctx.WarningsLevel)) + properties.Set("compiler.warning_flags", properties.Get("compiler.warning_flags."+warningsLevel)) maxTextSizeString := properties.Get("upload.maximum_size") maxDataSizeString := properties.Get("upload.maximum_data_size") if maxTextSizeString == "" { - return nil + return nil, nil } maxTextSize, err := strconv.Atoi(maxTextSizeString) if err != nil { - return err + return nil, err } maxDataSize := -1 if maxDataSizeString != "" { maxDataSize, err = strconv.Atoi(maxDataSizeString) if err != nil { - return err + return nil, err } } - textSize, dataSize, _, err := execSizeRecipe(ctx, properties) + textSize, dataSize, _, err := execSizeRecipe(properties, verbose, stdoutWriter, stderrWriter, printInfoFn) if err != nil { - ctx.Warn(tr("Couldn't determine program size")) - return nil + printWarnFn(tr("Couldn't determine program size")) + return nil, nil } - ctx.Info(tr("Sketch uses %[1]s bytes (%[3]s%%) of program storage space. Maximum is %[2]s bytes.", + printInfoFn(tr("Sketch uses %[1]s bytes (%[3]s%%) of program storage space. Maximum is %[2]s bytes.", strconv.Itoa(textSize), strconv.Itoa(maxTextSize), strconv.Itoa(textSize*100/maxTextSize))) if dataSize >= 0 { if maxDataSize > 0 { - ctx.Info(tr("Global variables use %[1]s bytes (%[3]s%%) of dynamic memory, leaving %[4]s bytes for local variables. Maximum is %[2]s bytes.", + printInfoFn(tr("Global variables use %[1]s bytes (%[3]s%%) of dynamic memory, leaving %[4]s bytes for local variables. Maximum is %[2]s bytes.", strconv.Itoa(dataSize), strconv.Itoa(maxDataSize), strconv.Itoa(dataSize*100/maxDataSize), strconv.Itoa(maxDataSize-dataSize))) } else { - ctx.Info(tr("Global variables use %[1]s bytes of dynamic memory.", strconv.Itoa(dataSize))) + printInfoFn(tr("Global variables use %[1]s bytes of dynamic memory.", strconv.Itoa(dataSize))) } } - ctx.ExecutableSectionsSize = []types.ExecutableSectionSize{ + executableSectionsSize := []builder.ExecutableSectionSize{ { Name: "text", Size: textSize, @@ -150,7 +157,7 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error { }, } if maxDataSize > 0 { - ctx.ExecutableSectionsSize = append(ctx.ExecutableSectionsSize, types.ExecutableSectionSize{ + executableSectionsSize = append(executableSectionsSize, builder.ExecutableSectionSize{ Name: "data", Size: dataSize, MaxSize: maxDataSize, @@ -158,38 +165,42 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error { } if textSize > maxTextSize { - ctx.Warn(tr("Sketch too big; see %[1]s for tips on reducing it.", "https://support.arduino.cc/hc/en-us/articles/360013825179")) - return errors.New(tr("text section exceeds available space in board")) + printWarnFn(tr("Sketch too big; see %[1]s for tips on reducing it.", "https://support.arduino.cc/hc/en-us/articles/360013825179")) + return executableSectionsSize, errors.New(tr("text section exceeds available space in board")) } if maxDataSize > 0 && dataSize > maxDataSize { - ctx.Warn(tr("Not enough memory; see %[1]s for tips on reducing your footprint.", "https://support.arduino.cc/hc/en-us/articles/360013825179")) - return errors.New(tr("data section exceeds available space in board")) + printWarnFn(tr("Not enough memory; see %[1]s for tips on reducing your footprint.", "https://support.arduino.cc/hc/en-us/articles/360013825179")) + return executableSectionsSize, errors.New(tr("data section exceeds available space in board")) } if w := properties.Get("build.warn_data_percentage"); w != "" { warnDataPercentage, err := strconv.Atoi(w) if err != nil { - return err + return executableSectionsSize, err } if maxDataSize > 0 && dataSize > maxDataSize*warnDataPercentage/100 { - ctx.Warn(tr("Low memory available, stability problems may occur.")) + printWarnFn(tr("Low memory available, stability problems may occur.")) } } - return nil + return executableSectionsSize, nil } -func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize int, dataSize int, eepromSize int, resErr error) { +func execSizeRecipe(properties *properties.Map, + verbose bool, + stdoutWriter, stderrWriter io.Writer, + printInfoFn func(msg string), +) (textSize int, dataSize int, eepromSize int, resErr error) { command, err := utils.PrepareCommandForRecipe(properties, "recipe.size.pattern", false) if err != nil { resErr = fmt.Errorf(tr("Error while determining sketch size: %s"), err) return } - verboseInfo, out, _, err := utils.ExecCommand(ctx.Verbose, ctx.Stdout, ctx.Stderr, command, utils.Capture /* stdout */, utils.Show /* stderr */) - if ctx.Verbose { - ctx.Info(string(verboseInfo)) + verboseInfo, out, _, err := utils.ExecCommand(verbose, stdoutWriter, stderrWriter, command, utils.Capture /* stdout */, utils.Show /* stderr */) + if verbose { + printInfoFn(string(verboseInfo)) } if err != nil { resErr = fmt.Errorf(tr("Error while determining sketch size: %s"), err) diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go index 14616a15fb8..0c9b1a18458 100644 --- a/legacy/builder/types/context.go +++ b/legacy/builder/types/context.go @@ -93,7 +93,7 @@ type Context struct { stdLock sync.Mutex // Sizer results - ExecutableSectionsSize ExecutablesFileSections + ExecutableSectionsSize builder.ExecutablesFileSections // Compilation Database to build/update CompilationDatabase *builder.CompilationDatabase @@ -106,29 +106,6 @@ type Context struct { SourceOverride map[string]string } -// ExecutableSectionSize represents a section of the executable output file -type ExecutableSectionSize struct { - Name string `json:"name"` - Size int `json:"size"` - MaxSize int `json:"max_size"` -} - -// ExecutablesFileSections is an array of ExecutablesFileSection -type ExecutablesFileSections []ExecutableSectionSize - -// ToRPCExecutableSectionSizeArray transforms this array into a []*rpc.ExecutableSectionSize -func (s ExecutablesFileSections) ToRPCExecutableSectionSizeArray() []*rpc.ExecutableSectionSize { - res := []*rpc.ExecutableSectionSize{} - for _, section := range s { - res = append(res, &rpc.ExecutableSectionSize{ - Name: section.Name, - Size: int64(section.Size), - MaxSize: int64(section.MaxSize), - }) - } - return res -} - func (ctx *Context) ExtractBuildOptions() *properties.Map { opts := properties.NewMap() opts.Set("hardwareFolders", strings.Join(ctx.HardwareDirs.AsStrings(), ","))