Skip to content

Commit 18cb00c

Browse files
cmagliesilvanocerzaper1234
authored
Added support for advanced sketch "size" command (#1211)
* Remove some constants indirection * Added support for advanced-sizers * Added documentation * Apply suggestions from code review Co-authored-by: Silvano Cerza <[email protected]> * Update docs/platform-specification.md Co-authored-by: Silvano Cerza <[email protected]> * Enforce severity correctness from sizer tool * Apply suggestions from code review Co-authored-by: per1234 <[email protected]> * Use 'max_size' field name in json to match cli output * Apply suggestions from code review Co-authored-by: per1234 <[email protected]> Co-authored-by: Silvano Cerza <[email protected]> Co-authored-by: per1234 <[email protected]>
1 parent 4256524 commit 18cb00c

File tree

5 files changed

+117
-23
lines changed

5 files changed

+117
-23
lines changed

Diff for: docs/platform-specification.md

+57
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,63 @@ Sketch uses 924 bytes (2%) of program storage space. Maximum is 32256 bytes.
309309
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.
310310
```
311311

312+
#### Recipes to compute binary sketch size for more complex systems (since Arduino CLI >=0.21.0)
313+
314+
A platform may provide a tool for the specific purpose to analyze the binaries and compute the sketch size and memory
315+
usage statistics. This is especially useful for boards with non-trivial memory layouts where
316+
[the classic reg-exp based approach](#recipes-to-compute-binary-sketch-size) is not sufficient.
317+
318+
The command line to run is specified with the recipe **recipe.advanced_size.pattern**.
319+
320+
The expected output from the tool is a JSON object with the following format:
321+
322+
```json
323+
{
324+
"output": "Your sketch uses 2200 bytes of program memory out of 8192 (27%)\nThe static RAM used is 200 bytes (of 2048 max)",
325+
"severity": "info",
326+
"sections": [
327+
{ "name": "text", "size": 2200, "max_size": 8192 },
328+
{ "name": "data", "size": 200, "max_size": 2048 }
329+
]
330+
}
331+
```
332+
333+
The meaning of the fields is the following:
334+
335+
- `output`: is a preformatted text that is displayed as-is in console.
336+
- `severity`: indicates the warning level of the output messages, it must be `info`, `warning` or `error`. Warnings and
337+
errors are displayed in red (or in a different color than normal output). Errors will make the build/upload fail.
338+
- `sections`: is an array containing the memory sections and their usage level. This array is used to report memory
339+
usage in a machine-readable format if requested by the user. Each item represents a memory section and may contain the
340+
following fields
341+
- `name`: an identifier for the section
342+
- `size`: the sketch size for the section
343+
- `max_size`: the maximum size for the section
344+
345+
When the `severity` is set to `error` the build/upload is interrupted and an exception is returned to the calling
346+
process. In this case an extra exception message must be provided through the `error` field, for example:
347+
348+
```json
349+
{
350+
"output": "Your sketch uses 12200 bytes of program memory out of 8192 (149%))\nThe static RAM used is 200 bytes (of 2048 max)",
351+
"severity": "error",
352+
"error": "Sketch is too big!",
353+
"sections": [
354+
{ "name": "text", "size": 12200, "max_size": 8192 },
355+
{ "name": "data", "size": 200, "max_size": 2048 }
356+
]
357+
}
358+
```
359+
360+
This means that the `sections` part is **NOT** used to automatically check if the sketch size exceeds the available
361+
memory: this check is now delegated to the tool that must report a `"severity":"error"` with a meaningful error message.
362+
363+
If both **recipe.size.pattern** and **recipe.advanced_size.pattern** are present then **recipe.advanced_size.pattern**
364+
will be used. Since the **recipe.advanced_size.pattern** feature is available starting from Arduino CLI>=0.21.0, to
365+
maximize backward compatibility, we recommend to provide both **recipe.size.pattern** and
366+
**recipe.advanced_size.pattern** if possible, so the old versions of the IDE/CLI will continue to work (even with a less
367+
detailed memory usage report).
368+
312369
#### Recipes to export compiled binary
313370

314371
When you do a **Sketch > Export compiled Binary** in the Arduino IDE, the compiled binary is copied from the build

Diff for: legacy/builder/constants/constants.go

-7
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,12 @@ const PLATFORM_REWRITE_NEW = "new"
6464
const PLATFORM_REWRITE_OLD = "old"
6565
const PLATFORM_URL = "url"
6666
const PLATFORM_VERSION = "version"
67-
const PROPERTY_WARN_DATA_PERCENT = "build.warn_data_percentage"
68-
const PROPERTY_UPLOAD_MAX_SIZE = "upload.maximum_size"
69-
const PROPERTY_UPLOAD_MAX_DATA_SIZE = "upload.maximum_data_size"
7067
const RECIPE_AR_PATTERN = "recipe.ar.pattern"
7168
const RECIPE_C_COMBINE_PATTERN = "recipe.c.combine.pattern"
7269
const RECIPE_C_PATTERN = "recipe.c.o.pattern"
7370
const RECIPE_CPP_PATTERN = "recipe.cpp.o.pattern"
74-
const RECIPE_SIZE_PATTERN = "recipe.size.pattern"
7571
const RECIPE_PREPROC_MACROS = "recipe.preproc.macros"
7672
const RECIPE_S_PATTERN = "recipe.S.o.pattern"
77-
const RECIPE_SIZE_REGEXP = "recipe.size.regex"
78-
const RECIPE_SIZE_REGEXP_DATA = "recipe.size.regex.data"
79-
const RECIPE_SIZE_REGEXP_EEPROM = "recipe.size.regex.eeprom"
8073
const REWRITING_DISABLED = "disabled"
8174
const REWRITING = "rewriting"
8275
const SPACE = " "

Diff for: legacy/builder/merge_sketch_with_bootloader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (s *MergeSketchWithBootloader) Run(ctx *types.Context) error {
7575

7676
// Ignore merger errors for the first iteration
7777
maximumBinSize := 16000000
78-
if uploadMaxSize, ok := ctx.BuildProperties.GetOk(constants.PROPERTY_UPLOAD_MAX_SIZE); ok {
78+
if uploadMaxSize, ok := ctx.BuildProperties.GetOk("upload.maximum_size"); ok {
7979
maximumBinSize, _ = strconv.Atoi(uploadMaxSize)
8080
maximumBinSize *= 2
8181
}

Diff for: legacy/builder/phases/sizer.go

+56-12
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
package phases
1717

1818
import (
19+
"encoding/json"
1920
"fmt"
2021
"regexp"
2122
"strconv"
2223

2324
"github.com/arduino/arduino-cli/legacy/builder/builder_utils"
24-
"github.com/arduino/arduino-cli/legacy/builder/constants"
2525
"github.com/arduino/arduino-cli/legacy/builder/types"
2626
"github.com/arduino/arduino-cli/legacy/builder/utils"
2727
"github.com/arduino/go-properties-orderedmap"
@@ -42,20 +42,64 @@ func (s *Sizer) Run(ctx *types.Context) error {
4242

4343
buildProperties := ctx.BuildProperties
4444

45-
err := checkSize(ctx, buildProperties)
45+
if buildProperties.ContainsKey("recipe.advanced_size.pattern") {
46+
return checkSizeAdvanced(ctx, buildProperties)
47+
}
48+
49+
return checkSize(ctx, buildProperties)
50+
}
51+
52+
func checkSizeAdvanced(ctx *types.Context, properties *properties.Map) error {
53+
command, err := builder_utils.PrepareCommandForRecipe(properties, "recipe.advanced_size.pattern", false, ctx.PackageManager.GetEnvVarsForSpawnedProcess())
4654
if err != nil {
47-
return errors.WithStack(err)
55+
return errors.New(tr("Error while determining sketch size: %s", err))
4856
}
4957

58+
out, _, err := utils.ExecCommand(ctx, command, utils.Capture /* stdout */, utils.Show /* stderr */)
59+
if err != nil {
60+
return errors.New(tr("Error while determining sketch size: %s", err))
61+
}
62+
63+
type AdvancedSizerResponse struct {
64+
// Output are the messages displayed in console to the user
65+
Output string `json:"output"`
66+
// Severity may be one of "info", "warning" or "error". Warnings and errors will
67+
// likely be printed in red. Errors will stop build/upload.
68+
Severity string `json:"severity"`
69+
// Sections are the sections sizes for machine readable use
70+
Sections []types.ExecutableSectionSize `json:"sections"`
71+
// ErrorMessage is a one line error message like:
72+
// "text section exceeds available space in board"
73+
// it must be set when Severity is "error"
74+
ErrorMessage string `json:"error"`
75+
}
76+
77+
var resp AdvancedSizerResponse
78+
if err := json.Unmarshal(out, &resp); err != nil {
79+
return errors.New(tr("Error while determining sketch size: %s", err))
80+
}
81+
82+
ctx.ExecutableSectionsSize = resp.Sections
83+
switch resp.Severity {
84+
case "error":
85+
ctx.Warn(resp.Output)
86+
return errors.New(resp.ErrorMessage)
87+
case "warning":
88+
ctx.Warn(resp.Output)
89+
case "info":
90+
ctx.Info(resp.Output)
91+
default:
92+
return fmt.Errorf("invalid '%s' severity from sketch sizer: it must be 'error', 'warning' or 'info'", resp.Severity)
93+
}
5094
return nil
5195
}
5296

5397
func checkSize(ctx *types.Context, buildProperties *properties.Map) error {
5498
properties := buildProperties.Clone()
55-
properties.Set(constants.BUILD_PROPERTIES_COMPILER_WARNING_FLAGS, properties.Get(constants.BUILD_PROPERTIES_COMPILER_WARNING_FLAGS+"."+ctx.WarningsLevel))
99+
properties.Set("compiler.warning_flags", properties.Get("compiler.warning_flags."+ctx.WarningsLevel))
56100

57-
maxTextSizeString := properties.Get(constants.PROPERTY_UPLOAD_MAX_SIZE)
58-
maxDataSizeString := properties.Get(constants.PROPERTY_UPLOAD_MAX_DATA_SIZE)
101+
maxTextSizeString := properties.Get("upload.maximum_size")
102+
maxDataSizeString := properties.Get("upload.maximum_data_size")
59103

60104
if maxTextSizeString == "" {
61105
return nil
@@ -121,8 +165,8 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error {
121165
return errors.New(tr("data section exceeds available space in board"))
122166
}
123167

124-
if properties.Get(constants.PROPERTY_WARN_DATA_PERCENT) != "" {
125-
warnDataPercentage, err := strconv.Atoi(properties.Get(constants.PROPERTY_WARN_DATA_PERCENT))
168+
if w := properties.Get("build.warn_data_percentage"); w != "" {
169+
warnDataPercentage, err := strconv.Atoi(w)
126170
if err != nil {
127171
return err
128172
}
@@ -135,7 +179,7 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error {
135179
}
136180

137181
func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize int, dataSize int, eepromSize int, resErr error) {
138-
command, err := builder_utils.PrepareCommandForRecipe(properties, constants.RECIPE_SIZE_PATTERN, false, ctx.PackageManager.GetEnvVarsForSpawnedProcess())
182+
command, err := builder_utils.PrepareCommandForRecipe(properties, "recipe.size.pattern", false, ctx.PackageManager.GetEnvVarsForSpawnedProcess())
139183
if err != nil {
140184
resErr = fmt.Errorf(tr("Error while determining sketch size: %s"), err)
141185
return
@@ -150,7 +194,7 @@ func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize in
150194
// force multiline match prepending "(?m)" to the actual regexp
151195
// return an error if RECIPE_SIZE_REGEXP doesn't exist
152196

153-
textSize, err = computeSize(properties.Get(constants.RECIPE_SIZE_REGEXP), out)
197+
textSize, err = computeSize(properties.Get("recipe.size.regex"), out)
154198
if err != nil {
155199
resErr = fmt.Errorf(tr("Invalid size regexp: %s"), err)
156200
return
@@ -160,13 +204,13 @@ func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize in
160204
return
161205
}
162206

163-
dataSize, err = computeSize(properties.Get(constants.RECIPE_SIZE_REGEXP_DATA), out)
207+
dataSize, err = computeSize(properties.Get("recipe.size.regex.data"), out)
164208
if err != nil {
165209
resErr = fmt.Errorf(tr("Invalid data size regexp: %s"), err)
166210
return
167211
}
168212

169-
eepromSize, err = computeSize(properties.Get(constants.RECIPE_SIZE_REGEXP_EEPROM), out)
213+
eepromSize, err = computeSize(properties.Get("recipe.size.regex.eeprom"), out)
170214
if err != nil {
171215
resErr = fmt.Errorf(tr("Invalid eeprom size regexp: %s"), err)
172216
return

Diff for: legacy/builder/types/context.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ type Context struct {
183183

184184
// ExecutableSectionSize represents a section of the executable output file
185185
type ExecutableSectionSize struct {
186-
Name string
187-
Size int
188-
MaxSize int
186+
Name string `json:"name"`
187+
Size int `json:"size"`
188+
MaxSize int `json:"max_size"`
189189
}
190190

191191
// ExecutablesFileSections is an array of ExecutablesFileSection

0 commit comments

Comments
 (0)