diff --git a/HISTORY.rst b/HISTORY.rst index bfb85fe7..5a2a955f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,11 +4,16 @@ Release Notes PlatformIO 3.0 -------------- -3.5.4 (2018-??-??) +3.6.0 (2018-??-??) ~~~~~~~~~~~~~~~~~~ -* Check maximum allowed firmware size for programming/uploading using `platformio run --target checkprogsize `__ - (`issue #1412 `_) +* `Program Memory Usage `_ + + - Print human-readable memory usage information after a build and before uploading + - Print detailed memory usage information with "sections" and "addresses" + in `verbose mode `__ + - Check maximum allowed "program" and "data" sizes before uploading/programming + (`issue #1412 `_) 3.5.3 (2018-06-01) ~~~~~~~~~~~~~~~~~~ diff --git a/docs b/docs index 91eae264..22c4d010 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 91eae264171d7c7662ba39be5954d9b76f267373 +Subproject commit 22c4d0109e6f2386153523e5de29e1a77c7ffce6 diff --git a/platformio/builder/main.py b/platformio/builder/main.py index caafda5b..150d848f 100644 --- a/platformio/builder/main.py +++ b/platformio/builder/main.py @@ -20,7 +20,7 @@ from os.path import expanduser, join from time import time from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS, - Action, AllowSubstExceptions, AlwaysBuild, + AllowSubstExceptions, AlwaysBuild, Default, DefaultEnvironment, Variables) from platformio import util @@ -164,15 +164,34 @@ for item in env.GetExtraScripts("pre"): env.SConscript("$BUILD_SCRIPT") -AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS + ["size"])) -AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS + ["size"])) - if "UPLOAD_FLAGS" in env: env.Prepend(UPLOADERFLAGS=["$UPLOAD_FLAGS"]) for item in env.GetExtraScripts("post"): env.SConscript(item, exports="env") +############################################################################## + +# Checking program size +if env.get("SIZETOOL") and "nobuild" not in COMMAND_LINE_TARGETS: + env.Depends(["upload", "program"], "checkprogsize") + # Replace platform's "size" target with our + _new_targets = [t for t in DEFAULT_TARGETS if str(t) != "size"] + Default(None) + Default(_new_targets) + Default("checkprogsize") + +# Print configured protocols +env.AddPreAction( + ["upload", "program"], + env.VerboseAction(lambda source, target, env: env.PrintUploadInfo(), + "Configuring upload protocol...")) + +AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS)) +AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) + +############################################################################## + if "envdump" in COMMAND_LINE_TARGETS: print env.Dump() env.Exit(0) @@ -189,7 +208,3 @@ if "idedata" in COMMAND_LINE_TARGETS: "See explanation in FAQ > Troubleshooting > Building\n" "http://docs.platformio.org/page/faq.html\n\n") env.Exit(1) - -env.AddPreAction(["upload", "program"], - Action(lambda source, target, env: env.PrintUploadInfo(), - "Configuring upload protocol...")) diff --git a/platformio/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index 1ba523e4..88390509 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -14,6 +14,7 @@ from __future__ import absolute_import +import re import sys from fnmatch import fnmatch from os import environ @@ -21,11 +22,13 @@ from os.path import isfile, join from shutil import copyfile from time import sleep -from SCons.Node.Alias import Alias +from SCons.Script import ARGUMENTS from serial import Serial, SerialException from platformio import util +# pylint: disable=unused-argument + def FlushSerialBuffer(env, port): s = Serial(env.subst(port)) @@ -45,7 +48,7 @@ def TouchSerialPort(env, port, baudrate): s = Serial(port=port, baudrate=baudrate) s.setDTR(False) s.close() - except: # pylint: disable=W0702 + except: # pylint: disable=bare-except pass sleep(0.4) # DO NOT REMOVE THAT (required by SAM-BA based boards) @@ -88,7 +91,7 @@ def WaitForNewSerialPort(env, before): return new_port -def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument +def AutodetectUploadPort(*args, **kwargs): env = args[0] def _get_pattern(): @@ -173,7 +176,7 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument env.Exit(1) -def UploadToDisk(_, target, source, env): # pylint: disable=W0613,W0621 +def UploadToDisk(_, target, source, env): assert "UPLOAD_PORT" in env progname = env.subst("$PROGNAME") for ext in ("bin", "hex"): @@ -186,32 +189,87 @@ def UploadToDisk(_, target, source, env): # pylint: disable=W0613,W0621 "(Some boards may require manual hard reset)" -def CheckUploadSize(_, target, source, env): # pylint: disable=W0613,W0621 - if "BOARD" not in env: - return - max_size = int(env.BoardConfig().get("upload.maximum_size", 0)) - if max_size == 0 or "SIZETOOL" not in env: - return - - sysenv = environ.copy() - sysenv['PATH'] = str(env['ENV']['PATH']) - cmd = [ - env.subst("$SIZETOOL"), "-B", - str(source[0] if isinstance(target[0], Alias) else target[0]) +def CheckUploadSize(_, target, source, env): + check_conditions = [ + env.get("BOARD"), + env.get("SIZETOOL") or env.get("SIZECHECKCMD") ] - result = util.exec_command(cmd, env=sysenv) - if result['returncode'] != 0: + if not all(check_conditions): + return + program_max_size = int(env.BoardConfig().get("upload.maximum_size", 0)) + data_max_size = int(env.BoardConfig().get("upload.maximum_ram_size", 0)) + if program_max_size == 0: return - print result['out'].strip() - line = result['out'].strip().splitlines()[1] - values = [v.strip() for v in line.split("\t")] - used_size = int(values[0]) + int(values[1]) + def _configure_defaults(): + env.Replace( + SIZECHECKCMD="$SIZETOOL -B -d $SOURCES", + SIZEPROGREGEXP=r"^(\d+)\s+(\d+)\s+\d+\s", + SIZEDATAREGEXP=r"^\d+\s+(\d+)\s+(\d+)\s+\d+") - if used_size > max_size: + def _get_size_output(): + cmd = env.get("SIZECHECKCMD") + if not cmd: + return None + if not isinstance(cmd, list): + cmd = cmd.split() + cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg] + sysenv = environ.copy() + sysenv['PATH'] = str(env['ENV']['PATH']) + result = util.exec_command(env.subst(cmd), env=sysenv) + if result['returncode'] != 0: + return None + return result['out'].strip() + + def _calculate_size(output, pattern): + if not output or not pattern: + return -1 + size = 0 + regexp = re.compile(pattern) + for line in output.split("\n"): + line = line.strip() + if not line: + continue + match = regexp.search(line) + if not match: + continue + size += sum(int(value) for value in match.groups()) + return size + + def _format_availale_bytes(value, total): + percent_raw = float(value) / float(total) + blocks_per_progress = 10 + used_blocks = int(round(blocks_per_progress * percent_raw)) + if used_blocks > blocks_per_progress: + used_blocks = blocks_per_progress + return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format( + "=" * used_blocks, blocks_per_progress, percent_raw, value, total) + + if not env.get("SIZECHECKCMD") and not env.get("SIZEPROGREGEXP"): + _configure_defaults() + output = _get_size_output() + program_size = _calculate_size(output, env.get("SIZEPROGREGEXP")) + data_size = _calculate_size(output, env.get("SIZEDATAREGEXP")) + + print "Memory Usage -> http://bit.ly/pio-memory-usage" + if data_max_size and data_size > -1: + print "DATA: %s" % _format_availale_bytes(data_size, data_max_size) + if program_size > -1: + print "PROGRAM: %s" % _format_availale_bytes(program_size, + program_max_size) + if int(ARGUMENTS.get("PIOVERBOSE", 0)): + print output + + # raise error + if data_max_size and data_size > data_max_size: + sys.stderr.write( + "Error: The data size (%d bytes) is greater " + "than maximum allowed (%s bytes)\n" % (data_size, data_max_size)) + env.Exit(1) + if program_size > program_max_size: sys.stderr.write("Error: The program size (%d bytes) is greater " - "than maximum allowed (%s bytes)\n" % (used_size, - max_size)) + "than maximum allowed (%s bytes)\n" % + (program_size, program_max_size)) env.Exit(1) diff --git a/platformio/builder/tools/platformio.py b/platformio/builder/tools/platformio.py index 9c6f6d0d..491d7418 100644 --- a/platformio/builder/tools/platformio.py +++ b/platformio/builder/tools/platformio.py @@ -107,12 +107,13 @@ def BuildProgram(env): program = env.Program( join("$BUILD_DIR", env.subst("$PROGNAME")), env['PIOBUILDFILES']) + env.Replace(PIOMAINPROG=program) - checksize_action = env.VerboseAction(env.CheckUploadSize, - "Checking program size") - AlwaysBuild(env.Alias("checkprogsize", program, checksize_action)) - if set(["upload", "program"]) & set(COMMAND_LINE_TARGETS): - env.AddPostAction(program, checksize_action) + AlwaysBuild( + env.Alias( + "checkprogsize", program, + env.VerboseAction(env.CheckUploadSize, + "Checking size $PIOMAINPROG"))) return program