Skip to content

Commit

Permalink
refactor Sizer in a function
Browse files Browse the repository at this point in the history
  • Loading branch information
alessio-perugini committed Sep 7, 2023
1 parent 592f46e commit 48be630
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 86 deletions.
26 changes: 26 additions & 0 deletions arduino/builder/sizer.go
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 18 additions & 1 deletion legacy/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,24 @@ 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, verboseInfoOut, infoOut, warnOut, err := phases.Sizer(
mainErr != nil,
ctx.OnlyUpdateCompilationDatabase,
ctx.BuildProperties,
ctx.Verbose,
ctx.Stdout, ctx.Stderr,
ctx.WarningsLevel,
)
ctx.ExecutableSectionsSize = executableSectionsSize
if ctx.Verbose {
ctx.Info(string(verboseInfoOut))
}
ctx.Info(string(infoOut))
ctx.Warn(string(warnOut))

return err
}),
}
for _, command := range commands {
PrintRingNameIfDebug(ctx, command)
Expand Down
134 changes: 73 additions & 61 deletions legacy/builder/phases/sizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,54 @@
package phases

import (
"bytes"
"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
}
if s.SketchError {
return nil
func Sizer(
sketchError, onlyUpdateCompilationDatabase bool,
buildProperties *properties.Map,
verbose bool,
stdoutWriter, stderrWriter io.Writer,
warningsLevel string,
) (builder.ExecutablesFileSections, []byte, []byte, []byte, error) {
if onlyUpdateCompilationDatabase || sketchError {
return nil, nil, nil, nil, nil
}

buildProperties := ctx.BuildProperties

if buildProperties.ContainsKey("recipe.advanced_size.pattern") {
return checkSizeAdvanced(ctx, buildProperties)
return checkSizeAdvanced(buildProperties, verbose, stdoutWriter, stderrWriter)
}

return checkSize(ctx, buildProperties)
return checkSize(buildProperties, verbose, stdoutWriter, stderrWriter, 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,
) (builder.ExecutablesFileSections, []byte, []byte, []byte, error) {
verboseInfoBuf, infoBuf, warnBuf := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
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, nil, nil, 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 {
verboseInfoBuf.WriteString(string(verboseInfo))
}
if err != nil {
return errors.New(tr("Error while determining sketch size: %s", err))
return nil, verboseInfoBuf.Bytes(), nil, nil, errors.New(tr("Error while determining sketch size: %s", err))
}

type AdvancedSizerResponse struct {
Expand All @@ -69,7 +73,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"
Expand All @@ -78,118 +82,126 @@ 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, verboseInfoBuf.Bytes(), nil, 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)
warnBuf.WriteString(resp.Output)
return executableSectionsSize, verboseInfoBuf.Bytes(), nil, warnBuf.Bytes(), errors.New(resp.ErrorMessage)
case "warning":
ctx.Warn(resp.Output)
warnBuf.WriteString(resp.Output)
case "info":
ctx.Info(resp.Output)
infoBuf.WriteString(resp.Output)
default:
return fmt.Errorf("invalid '%s' severity from sketch sizer: it must be 'error', 'warning' or 'info'", resp.Severity)
return executableSectionsSize, verboseInfoBuf.Bytes(), nil, nil, fmt.Errorf("invalid '%s' severity from sketch sizer: it must be 'error', 'warning' or 'info'", resp.Severity)
}
return nil
return executableSectionsSize, verboseInfoBuf.Bytes(), infoBuf.Bytes(), warnBuf.Bytes(), nil
}

func checkSize(ctx *types.Context, buildProperties *properties.Map) error {
func checkSize(
buildProperties *properties.Map,
verbose bool,
stdoutWriter, stderrWriter io.Writer,
warningsLevel string,
) (builder.ExecutablesFileSections, []byte, []byte, []byte, error) {
infoBuf, warnBuf := &bytes.Buffer{}, &bytes.Buffer{}
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, nil, nil, nil
}

maxTextSize, err := strconv.Atoi(maxTextSizeString)
if err != nil {
return err
return nil, nil, nil, nil, err
}

maxDataSize := -1
if maxDataSizeString != "" {
maxDataSize, err = strconv.Atoi(maxDataSizeString)
if err != nil {
return err
return nil, nil, nil, nil, err
}
}

textSize, dataSize, _, err := execSizeRecipe(ctx, properties)
textSize, dataSize, _, verboseInfoOut, err := execSizeRecipe(properties, verbose, stdoutWriter, stderrWriter)
if err != nil {
ctx.Warn(tr("Couldn't determine program size"))
return nil
infoBuf.WriteString(tr("Couldn't determine program size"))
return nil, verboseInfoOut, infoBuf.Bytes(), nil, nil
}

ctx.Info(tr("Sketch uses %[1]s bytes (%[3]s%%) of program storage space. Maximum is %[2]s bytes.",
infoBuf.WriteString(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.",
infoBuf.WriteString(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)))
infoBuf.WriteString(tr("Global variables use %[1]s bytes of dynamic memory.", strconv.Itoa(dataSize)))
}
}

ctx.ExecutableSectionsSize = []types.ExecutableSectionSize{
executableSectionsSize := []builder.ExecutableSectionSize{
{
Name: "text",
Size: textSize,
MaxSize: maxTextSize,
},
}
if maxDataSize > 0 {
ctx.ExecutableSectionsSize = append(ctx.ExecutableSectionsSize, types.ExecutableSectionSize{
executableSectionsSize = append(executableSectionsSize, builder.ExecutableSectionSize{
Name: "data",
Size: dataSize,
MaxSize: maxDataSize,
})
}

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"))
warnBuf.WriteString(tr("Sketch too big; see %[1]s for tips on reducing it.", "https://support.arduino.cc/hc/en-us/articles/360013825179"))
return executableSectionsSize, verboseInfoOut, infoBuf.Bytes(), warnBuf.Bytes(), 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"))
warnBuf.WriteString(tr("Not enough memory; see %[1]s for tips on reducing your footprint.", "https://support.arduino.cc/hc/en-us/articles/360013825179"))
return executableSectionsSize, verboseInfoOut, infoBuf.Bytes(), warnBuf.Bytes(), 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, verboseInfoOut, infoBuf.Bytes(), warnBuf.Bytes(), err
}
if maxDataSize > 0 && dataSize > maxDataSize*warnDataPercentage/100 {
ctx.Warn(tr("Low memory available, stability problems may occur."))
warnBuf.WriteString(tr("Low memory available, stability problems may occur."))
}
}

return nil
return executableSectionsSize, verboseInfoOut, infoBuf.Bytes(), warnBuf.Bytes(), nil
}

func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize int, dataSize int, eepromSize int, resErr error) {
command, err := utils.PrepareCommandForRecipe(properties, "recipe.size.pattern", false)
func execSizeRecipe(
buildProperties *properties.Map,
verbose bool,
stdoutWriter, stderrWriter io.Writer,
) (textSize int, dataSize int, eepromSize int, verboseInfoOut []byte, resErr error) {
command, err := utils.PrepareCommandForRecipe(buildProperties, "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 {
verboseInfoOut = verboseInfo
}
if err != nil {
resErr = fmt.Errorf(tr("Error while determining sketch size: %s"), err)
Expand All @@ -199,7 +211,7 @@ func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize in
// force multiline match prepending "(?m)" to the actual regexp
// return an error if RECIPE_SIZE_REGEXP doesn't exist

textSize, err = computeSize(properties.Get("recipe.size.regex"), out)
textSize, err = computeSize(buildProperties.Get("recipe.size.regex"), out)
if err != nil {
resErr = fmt.Errorf(tr("Invalid size regexp: %s"), err)
return
Expand All @@ -209,13 +221,13 @@ func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize in
return
}

dataSize, err = computeSize(properties.Get("recipe.size.regex.data"), out)
dataSize, err = computeSize(buildProperties.Get("recipe.size.regex.data"), out)
if err != nil {
resErr = fmt.Errorf(tr("Invalid data size regexp: %s"), err)
return
}

eepromSize, err = computeSize(properties.Get("recipe.size.regex.eeprom"), out)
eepromSize, err = computeSize(buildProperties.Get("recipe.size.regex.eeprom"), out)
if err != nil {
resErr = fmt.Errorf(tr("Invalid eeprom size regexp: %s"), err)
return
Expand Down
25 changes: 1 addition & 24 deletions legacy/builder/types/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(), ","))
Expand Down

0 comments on commit 48be630

Please sign in to comment.