Skip to content

Commit 771ccbf

Browse files
authored
Merge pull request #189 from facchinm/sizer_move_pr
Move sketch size calculation to Golang
2 parents 1c1369d + 3a4c024 commit 771ccbf

File tree

5 files changed

+302
-1
lines changed

5 files changed

+302
-1
lines changed

Diff for: src/arduino.cc/builder/builder.go

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ func (s *Builder) Run(ctx *types.Context) error {
127127
&PrintUsedAndNotUsedLibraries{SketchError: mainErr != nil},
128128

129129
&PrintUsedLibrariesIfVerbose{},
130+
131+
&phases.Sizer{SketchError: mainErr != nil},
130132
}
131133
otherErr := runCommands(ctx, commands, false)
132134

Diff for: src/arduino.cc/builder/constants/constants.go

+14
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ const MSG_PROP_IN_LIBRARY = "Missing '{0}' from library in {1}"
170170
const MSG_RUNNING_COMMAND = "Ts: {0} - Running: {1}"
171171
const MSG_RUNNING_RECIPE = "Running recipe: {0}"
172172
const MSG_SETTING_BUILD_PATH = "Setting build path to {0}"
173+
const MSG_SIZER_TEXT_FULL = "Sketch uses {0} bytes ({2}%%) of program storage space. Maximum is {1} bytes."
174+
const MSG_SIZER_DATA_FULL = "Global variables use {0} bytes ({2}%%) of dynamic memory, leaving {3} bytes for local variables. Maximum is {1} bytes."
175+
const MSG_SIZER_DATA = "Global variables use {0} bytes of dynamic memory."
176+
const MSG_SIZER_TEXT_TOO_BIG = "Sketch too big; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing it."
177+
const MSG_SIZER_DATA_TOO_BIG = "Not enough memory; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing your footprint."
178+
const MSG_SIZER_LOW_MEMORY = "Low memory available, stability problems may occur."
179+
const MSG_SIZER_ERROR_NO_RULE = "Couldn't determine program size"
173180
const MSG_SKETCH_CANT_BE_IN_BUILDPATH = "Sketch cannot be located in build path. Please specify a different build path"
174181
const MSG_SKIPPING_TAG_ALREADY_DEFINED = "Skipping tag {0} because prototype is already defined"
175182
const MSG_SKIPPING_TAG_BECAUSE_HAS_FIELD = "Skipping tag {0} because it has field {0}"
@@ -197,14 +204,21 @@ const PLATFORM_REWRITE_NEW = "new"
197204
const PLATFORM_REWRITE_OLD = "old"
198205
const PLATFORM_URL = "url"
199206
const PLATFORM_VERSION = "version"
207+
const PROPERTY_WARN_DATA_PERCENT = "build.warn_data_percentage"
208+
const PROPERTY_UPLOAD_MAX_SIZE = "upload.maximum_size"
209+
const PROPERTY_UPLOAD_MAX_DATA_SIZE = "upload.maximum_data_size"
200210
const PROGRAMMER_NAME = "name"
201211
const RECIPE_AR_PATTERN = "recipe.ar.pattern"
202212
const RECIPE_C_COMBINE_PATTERN = "recipe.c.combine.pattern"
203213
const RECIPE_C_PATTERN = "recipe.c.o.pattern"
204214
const RECIPE_CPP_PATTERN = "recipe.cpp.o.pattern"
215+
const RECIPE_SIZE_PATTERN = "recipe.size.pattern"
205216
const RECIPE_PREPROC_INCLUDES = "recipe.preproc.includes"
206217
const RECIPE_PREPROC_MACROS = "recipe.preproc.macros"
207218
const RECIPE_S_PATTERN = "recipe.S.o.pattern"
219+
const RECIPE_SIZE_REGEXP = "recipe.size.regex"
220+
const RECIPE_SIZE_REGEXP_DATA = "recipe.size.regex.data"
221+
const RECIPE_SIZE_REGEXP_EEPROM = "recipe.size.regex.eeprom"
208222
const REWRITING_DISABLED = "disabled"
209223
const REWRITING = "rewriting"
210224
const SPACE = " "

Diff for: src/arduino.cc/builder/i18n/i18n.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...int
6565
}
6666

6767
func (s HumanLogger) Println(level string, format string, a ...interface{}) {
68-
s.Fprintln(os.Stdout, level, Format(format, a...))
68+
s.Fprintln(os.Stdout, level, format, a...)
6969
}
7070

7171
func (s HumanLogger) Name() string {

Diff for: src/arduino.cc/builder/phases/sizer.go

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* This file is part of Arduino Builder.
3+
*
4+
* Arduino Builder is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
* As a special exception, you may use this file as part of a free software
19+
* library without restriction. Specifically, if other files instantiate
20+
* templates or use macros or inline functions from this file, or you compile
21+
* this file and link it with other files to produce an executable, this
22+
* file does not by itself cause the resulting executable to be covered by
23+
* the GNU General Public License. This exception does not however
24+
* invalidate any other reasons why the executable file might be covered by
25+
* the GNU General Public License.
26+
*
27+
* Copyright 2016 Arduino LLC (http://www.arduino.cc/)
28+
*/
29+
30+
package phases
31+
32+
import (
33+
"errors"
34+
"regexp"
35+
"strconv"
36+
37+
"arduino.cc/builder/builder_utils"
38+
"arduino.cc/builder/constants"
39+
"arduino.cc/builder/i18n"
40+
"arduino.cc/builder/types"
41+
"arduino.cc/properties"
42+
)
43+
44+
type Sizer struct {
45+
SketchError bool
46+
}
47+
48+
func (s *Sizer) Run(ctx *types.Context) error {
49+
50+
if s.SketchError {
51+
return nil
52+
}
53+
54+
buildProperties := ctx.BuildProperties
55+
verbose := ctx.Verbose
56+
warningsLevel := ctx.WarningsLevel
57+
logger := ctx.GetLogger()
58+
59+
err := checkSize(buildProperties, verbose, warningsLevel, logger)
60+
if err != nil {
61+
return i18n.WrapError(err)
62+
}
63+
64+
return nil
65+
}
66+
67+
func checkSize(buildProperties properties.Map, verbose bool, warningsLevel string, logger i18n.Logger) error {
68+
69+
properties := buildProperties.Clone()
70+
properties[constants.BUILD_PROPERTIES_COMPILER_WARNING_FLAGS] = properties[constants.BUILD_PROPERTIES_COMPILER_WARNING_FLAGS+"."+warningsLevel]
71+
72+
maxTextSizeString := properties[constants.PROPERTY_UPLOAD_MAX_SIZE]
73+
maxDataSizeString := properties[constants.PROPERTY_UPLOAD_MAX_DATA_SIZE]
74+
75+
if maxTextSizeString == "" {
76+
return nil
77+
}
78+
79+
maxTextSize, err := strconv.Atoi(maxTextSizeString)
80+
if err != nil {
81+
return err
82+
}
83+
84+
maxDataSize := -1
85+
if maxDataSizeString != "" {
86+
maxDataSize, err = strconv.Atoi(maxDataSizeString)
87+
if err != nil {
88+
return err
89+
}
90+
}
91+
92+
textSize, dataSize, _, err := execSizeReceipe(properties, logger)
93+
if err != nil {
94+
logger.Println(constants.LOG_LEVEL_WARN, constants.MSG_SIZER_ERROR_NO_RULE)
95+
return nil
96+
}
97+
98+
logger.Println(constants.LOG_LEVEL_INFO, constants.MSG_SIZER_TEXT_FULL, strconv.Itoa(textSize), strconv.Itoa(maxTextSize), strconv.Itoa(textSize*100/maxTextSize))
99+
if dataSize >= 0 {
100+
if maxDataSize > 0 {
101+
logger.Println(constants.LOG_LEVEL_INFO, constants.MSG_SIZER_DATA_FULL, strconv.Itoa(dataSize), strconv.Itoa(maxDataSize), strconv.Itoa(dataSize*100/maxDataSize), strconv.Itoa(maxDataSize-dataSize))
102+
} else {
103+
logger.Println(constants.LOG_LEVEL_INFO, constants.MSG_SIZER_DATA, strconv.Itoa(dataSize))
104+
}
105+
}
106+
107+
if textSize > maxTextSize {
108+
logger.Println(constants.LOG_LEVEL_ERROR, constants.MSG_SIZER_TEXT_TOO_BIG)
109+
return errors.New("")
110+
}
111+
112+
if maxDataSize > 0 && dataSize > maxDataSize {
113+
logger.Println(constants.LOG_LEVEL_ERROR, constants.MSG_SIZER_DATA_TOO_BIG)
114+
return errors.New("")
115+
}
116+
117+
if properties[constants.PROPERTY_WARN_DATA_PERCENT] != "" {
118+
warnDataPercentage, err := strconv.Atoi(properties[constants.PROPERTY_WARN_DATA_PERCENT])
119+
if err != nil {
120+
return err
121+
}
122+
if maxDataSize > 0 && dataSize > maxDataSize*warnDataPercentage/100 {
123+
logger.Println(constants.LOG_LEVEL_WARN, constants.MSG_SIZER_LOW_MEMORY)
124+
}
125+
}
126+
127+
return nil
128+
}
129+
130+
func execSizeReceipe(properties properties.Map, logger i18n.Logger) (textSize int, dataSize int, eepromSize int, resErr error) {
131+
out, err := builder_utils.ExecRecipe(properties, constants.RECIPE_SIZE_PATTERN, false, false, false, logger)
132+
if err != nil {
133+
resErr = errors.New("Error while determining sketch size: " + err.Error())
134+
return
135+
}
136+
137+
// force multiline match prepending "(?m)" to the actual regexp
138+
// return an error if RECIPE_SIZE_REGEXP doesn't exist
139+
140+
textSize, err = computeSize(properties[constants.RECIPE_SIZE_REGEXP], out)
141+
if err != nil {
142+
resErr = errors.New("Invalid size regexp: " + err.Error())
143+
return
144+
}
145+
if textSize == -1 {
146+
resErr = errors.New("Missing size regexp")
147+
return
148+
}
149+
150+
dataSize, err = computeSize(properties[constants.RECIPE_SIZE_REGEXP_DATA], out)
151+
if err != nil {
152+
resErr = errors.New("Invalid data size regexp: " + err.Error())
153+
return
154+
}
155+
156+
eepromSize, err = computeSize(properties[constants.RECIPE_SIZE_REGEXP_EEPROM], out)
157+
if err != nil {
158+
resErr = errors.New("Invalid eeprom size regexp: " + err.Error())
159+
return
160+
}
161+
162+
return
163+
}
164+
165+
func computeSize(re string, output []byte) (int, error) {
166+
if re == "" {
167+
return -1, nil
168+
}
169+
r, err := regexp.Compile("(?m)" + re)
170+
if err != nil {
171+
return -1, err
172+
}
173+
result := r.FindAllSubmatch(output, -1)
174+
size := 0
175+
for _, b := range result {
176+
for _, c := range b {
177+
if res, err := strconv.Atoi(string(c)); err == nil {
178+
size += res
179+
}
180+
}
181+
}
182+
return size, nil
183+
}

Diff for: src/arduino.cc/builder/phases/sizer_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* This file is part of Arduino Builder.
3+
*
4+
* Arduino Builder is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
* As a special exception, you may use this file as part of a free software
19+
* library without restriction. Specifically, if other files instantiate
20+
* templates or use macros or inline functions from this file, or you compile
21+
* this file and link it with other files to produce an executable, this
22+
* file does not by itself cause the resulting executable to be covered by
23+
* the GNU General Public License. This exception does not however
24+
* invalidate any other reasons why the executable file might be covered by
25+
* the GNU General Public License.
26+
*
27+
* Copyright 2016 Arduino LLC (http://www.arduino.cc/)
28+
*/
29+
30+
package phases
31+
32+
import (
33+
"testing"
34+
35+
"github.com/stretchr/testify/require"
36+
)
37+
38+
func TestSizerWithAVRData(t *testing.T) {
39+
output := []byte(`/tmp/test597119152/sketch.ino.elf :
40+
section size addr
41+
.data 36 8388864
42+
.text 3966 0
43+
.bss 112 8388900
44+
.comment 17 0
45+
.debug_aranges 704 0
46+
.debug_info 21084 0
47+
.debug_abbrev 4704 0
48+
.debug_line 5456 0
49+
.debug_frame 1964 0
50+
.debug_str 8251 0
51+
.debug_loc 7747 0
52+
.debug_ranges 856 0
53+
Total 54897
54+
`)
55+
56+
size, err := computeSize(`^(?:\.text|\.data|\.bootloader)\s+([0-9]+).*`, output)
57+
require.NoError(t, err)
58+
require.Equal(t, 4002, size)
59+
60+
size, err = computeSize(`^(?:\.data|\.bss|\.noinit)\s+([0-9]+).*`, output)
61+
require.NoError(t, err)
62+
require.Equal(t, 148, size)
63+
64+
size, err = computeSize(`^(?:\.eeprom)\s+([0-9]+).*`, output)
65+
require.NoError(t, err)
66+
require.Equal(t, 0, size)
67+
}
68+
69+
func TestSizerWithSAMDData(t *testing.T) {
70+
output := []byte(`/tmp/test737785204/sketch_usbhost.ino.elf :
71+
section size addr
72+
.text 8076 8192
73+
.data 152 536870912
74+
.bss 1984 536871064
75+
.ARM.attributes 40 0
76+
.comment 128 0
77+
.debug_info 178841 0
78+
.debug_abbrev 14968 0
79+
.debug_aranges 2080 0
80+
.debug_ranges 3840 0
81+
.debug_line 26068 0
82+
.debug_str 55489 0
83+
.debug_frame 5036 0
84+
.debug_loc 20978 0
85+
Total 317680
86+
`)
87+
88+
size, err := computeSize(`\.text\s+([0-9]+).*`, output)
89+
require.NoError(t, err)
90+
require.Equal(t, 8076, size)
91+
}
92+
93+
func TestSizerEmptyRegexpReturnsMinusOne(t *testing.T) {
94+
size, err := computeSize(``, []byte(`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`))
95+
require.NoError(t, err)
96+
require.Equal(t, -1, size)
97+
}
98+
99+
func TestSizerWithInvalidRegexp(t *testing.T) {
100+
_, err := computeSize(`[xx`, []byte(`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`))
101+
require.Error(t, err)
102+
}

0 commit comments

Comments
 (0)