Advanced Scripting

New in version 3.4.1.

Warning

Advanced Scripting is recommended for Advanced Users and requires Python language knowledge.

We highly recommend to take a look at Dynamic build flags option where you can use any programming language. Also, this option is useful if you need to apply changes to the project before building/uploading process:

  • Macro with the latest VCS revision/tag “on-the-fly”

  • Generate dynamic headers (*.h)

  • Process media content before generating SPIFFS image

  • Make some changes to source code or related libraries

More details Dynamic build flags.

PlatformIO Build System allows to launch custom extra_scripts (based on SCons software construction tool) while processing environment. For more details please follow to “Construction Environments” section of SCons documentation.

Warning

You can not run/debug these scripts directly with Python Interpreter. They will be loaded automatically when you processing project environment using platformio run command.

There are 2 type of extra scripts:

  1. PRE - executes before a main script of Development Platforms

  2. POST - executes after a main script of Development Platforms

Multiple extra scripts are allowed. Please split them via “, ” (comma + space) in the same line or use multi-line values.

For example, Project Configuration File platformio.ini

[env:my_env_1]
platform = ...
; without prefix, POST script
extra_scripts = post_extra_script.py

[env:my_env_2]
platform = ...
extra_scripts = pre:pre_extra_script1.py, pre:pre_extra_script2.py

[env:my_env_3]
platform = ...
extra_scripts =
  pre:pre_extra_script.py
  post:post_extra_script1.py
  post_extra_script2.py

This option can be set by global environment variable PLATFORMIO_EXTRA_SCRIPTS.

Take a look at the multiple snippets/answers for the user questions:

Before/Pre and After/Post actions

PlatformIO Build System has rich API that allows to attach different pre-/post actions (hooks) using env.AddPreAction(target, callback) or env.AddPreAction(target, [callback1, callback2, ...]) function. A first argument target can be a name of target that is passed using platformio run --target command, a name of built-in targets (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota) or path to file which PlatformIO processes (ELF, HEX, BIN, OBJ, etc.).

Examples

extra_script.py file is located on the same level as platformio.ini.

platformio.ini:

[env:pre_and_post_hooks]
extra_scripts = post:extra_script.py

extra_script.py:

Import("env")

#
# Dump build environment (for debug)
# print env.Dump()
#

#
# Change build flags in runtime
#
env.ProcessUnFlags("-DVECT_TAB_ADDR")
env.Append(CPPDEFINES=("VECT_TAB_ADDR", 0x123456789))

#
# Upload actions
#

def before_upload(source, target, env):
    print "before_upload"
    # do some actions

    # call Node.JS or other script
    env.Execute("node --version")


def after_upload(source, target, env):
    print "after_upload"
    # do some actions

print "Current build targets", map(str, BUILD_TARGETS)

env.AddPreAction("upload", before_upload)
env.AddPostAction("upload", after_upload)

#
# Custom actions when building program/firmware
#

env.AddPreAction("buildprog", callback...)
env.AddPostAction("buildprog", callback...)

#
# Custom actions for specific files/objects
#

env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", [callback1, callback2,...])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", callback...)

# custom action before building SPIFFS image. For example, compress HTML, etc.
env.AddPreAction("$BUILD_DIR/spiffs.bin", callback...)

# custom action for project's main.cpp
env.AddPostAction("$BUILD_DIR/src/main.cpp.o", callback...)

# Custom HEX from ELF
env.AddPostAction(
    "$BUILD_DIR/${PROGNAME}.elf",
    env.VerboseAction(" ".join([
        "$OBJCOPY", "-O", "ihex", "-R", ".eeprom",
        "$BUILD_DIR/${PROGNAME}.elf", "$BUILD_DIR/${PROGNAME}.hex"
    ]), "Building $BUILD_DIR/${PROGNAME}.hex")
)

Extra Linker Flags without -Wl, prefix

Sometimes you need to pass extra flags to GCC linker without Wl,. You could use build_flags option but it will not work. PlatformIO will not parse these flags to LINKFLAGS scope. In this case, simple extra script will help:

platformio.ini:

[env:env_extra_link_flags]
platform = windows_x86
extra_scripts = extra_script.py

extra_script.py (place it near platformio.ini):

Import('env')

#
# Dump build environment (for debug)
# print env.Dump()
#

env.Append(
  LINKFLAGS=[
      "-static",
      "-static-libgcc",
      "-static-libstdc++"
  ]
)

Custom Uploader

Example, specify own upload command for Atmel AVR:

platformio.ini:

[env:env_custom_uploader]
platform = atmelavr
extra_scripts = /path/to/extra_script.py
custom_option = hello

extra_script.py:

Import('env')
from base64 import b64decode

#
# Dump build environment (for debug)
# print env.Dump()
#

env.Replace(UPLOADHEXCMD='"$UPLOADER" ' + b64decode(ARGUMENTS.get("CUSTOM_OPTION")) + ' --uploader --flags')

# uncomment line below to see environment variables
# print ARGUMENTS

Custom firmware/program name

Sometime is useful to have a different firmware/program name in build_dir.

platformio.ini:

[env:env_custom_prog_name]
platform = espressif8266
board = nodemcuv2
framework = arduino
build_flags = -D VERSION=13
extra_scripts = pre:extra_script.py

extra_script.py:

Import("env")

my_flags = env.ParseFlags(env['BUILD_FLAGS'])
defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")}
# print defines

env.Replace(PROGNAME="firmware_%s" % defines.get("VERSION"))

Custom build target

There is a list with built-in targets which could be processed using platformio run --target option. You can create unlimited number of the own targets and declare custom handlers for them.

Let’s create a simple ping target and process it with platformio run --target ping command:

platformio.ini:

[env:env_custom_target]
platform = ...
...
extra_scripts = extra_script.py
custom_ping_host = google.com

extra_script.py:

from base64 import b64decode

from SCons.Script import ARGUMENTS, AlwaysBuild

Import("env")


def mytarget_callback(*args, **kwargs):
    print "Hello PlatformIO!"
    env.Execute("ping " + b64decode(ARGUMENTS.get("CUSTOM_PING_HOST")))


AlwaysBuild(env.Alias("ping", "", mytarget_callback))