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 extend build process with the custom extra_scripts using Python interpreter and SCons construction tool. Build and upload flags, targets, toolchains data, and other information are stored in SCons Construction Environments.
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 launch type of extra scripts:
PRE - executes before a main script of Development Platforms
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, “platformio.ini” (Project Configuration File)
[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_script.py
post:post_extra_script1.py
post_extra_script2.py
This option can be set by global environment variable PLATFORMIO_EXTRA_SCRIPTS
.
There are 2 built-in construction environments which PlatformIO Build System uses to process a project:
env
, Import("env")
- global construction environment which is used
for Development Platforms and Frameworks build scripts, upload tools,
Library Dependency Finder (LDF), and other internal operations
projenv
, Import("projenv")
- isolated construction environment which
is used for processing of a project source code located in src_dir.
Please note that src_build_flags specified in
“platformio.ini” (Project Configuration File) will be passed to projenv
and not to env
.
Warning
projenv
is available only for POST-type scripts
Flags passed to env
using PRE-type script will affect projenv
too.
my_pre_extra_script.py
:
Import("env")
# access to global construction environment
print env
# Dump construction environment (for debug purpose)
print env.Dump()
# append extra flags to global build environment
# which later will be used to build:
# - project source code
# - frameworks
# - dependent libraries
env.Append(CPPDEFINES=[
"MACRO_1_NAME",
("MACRO_2_NAME", "MACRO_2_VALUE")
])
my_post_extra_script.py
:
Import("env", "projenv")
# access to global construction environment
print env
# access to project construction environment
print projenv
# Dump construction environments (for debug purpose)
print env.Dump()
print projenv.Dump()
# append extra flags to global build environment
# which later will be used to build:
# - frameworks
# - dependent libraries
env.Append(CPPDEFINES=[
"MACRO_1_NAME",
("MACRO_2_NAME", "MACRO_2_VALUE")
])
# append extra flags to only project build environment
projenv.Append(CPPDEFINES=[
"PROJECT_EXTRA_MACRO_1_NAME",
("ROJECT_EXTRA_MACRO_2_NAME", "ROJECT_EXTRA_MACRO_2_VALUE")
])
See examples below how to import construction environments and modify existing data or add new.
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", "projenv")
# access to global build environment
print env
# access to project build environment (is used source files in "src" folder)
print projenv
#
# Dump build environment (for debug purpose)
# 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")
)
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.
We will use SCons’s Alias(alias, [targets, [action]]) , env.Alias(alias, [targets, [action]]) function to declare a custom target/alias.
Create a custom node
target (alias) which will print a NodeJS version
platformio.ini
:
[env:myenv]
platform = ...
...
extra_scripts = extra_script.py
extra_script.py
:
Import("env")
env.AlwaysBuild(env.Alias("node", None, ["node --version"]))
Now, run pio run -t node
.
Sometime you need to run a command which depends on another target (file,
firmware, etc). Let’s create an ota
target and declare command which will
depend on a project firmware. If a build process successes, declared command
will be run.
platformio.ini
:
[env:myenv]
platform = ...
...
extra_scripts = extra_script.py
extra_script.py
:
Import("env")
env.AlwaysBuild(env.Alias("ota",
"$BUILD_DIR/${PROGNAME}.elf",
["ota_script --firmware-path $SOURCE"]))
Now, run pio run -t ota
.
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 platformio import util
Import("env")
config = util.load_project_config()
host = config.get("env_custom_target", "custom_ping_host")
def mytarget_callback(*args, **kwargs):
print "Hello PlatformIO!"
env.Execute("ping " + host)
env.AlwaysBuild(env.Alias("ping", None, mytarget_callback))
The beast examples are PaltformIO development platforms.
Please check builder
folder for the main and framework scripts.
platformio.ini
¶platformio.ini
:
[env:my_env]
platform = ...
extra_scripts = extra_script.py
custom_option1 = value1
custom_option2 = value2
extra_script.py
:
from platformio import util
config = util.load_project_config()
value1 = config.get("my_env", "custom_option1")
value2 = config.get("my_env", "custom_option2")
platformio.ini
:
[env:my_env]
platform = ...
extra_scripts = extra_script.py
extra_script.py
(place it near platformio.ini
):
Import("env")
# General options that are passed to the C and C++ compilers
env.Append(CCFLAGS=["flag1", "falg2"])
# General options that are passed to the C compiler (C only; not C++).
env.Append(CFLAGS=["flag1", "flag2"])
# General options that are passed to the C++ compiler
env.Append(CXXFLAGS=["flag1", "flag2"])
-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++"
]
)
You can override default upload command of development platform using extra
script. There is the common environment variable UPLOADCMD
which PlatformIO
Build System will handle when you platformio run -t upload.
Please note that some development platforms can have more than 1 upload command.
For example, Atmel AVR has UPLOADHEXCMD
(firmware) and UPLOADEEPCMD
(EEPROM data).
See examples below:
platformio.ini
:
[env:my_custom_upload_tool]
platform = ...
; place it into the root of project or use full path
extra_scripts = extra_script.py
upload_protocol = custom
; each flag in a new line
upload_flags =
-arg1
-arg2
-argN
extra_script.py
(place it near platformio.ini
):
Import("env")
# please keep $SOURCE variable, it will be replaced with a path to firmware
# Generic
env.Replace(
UPLOADER="executable or path to executable"
UPLOADCMD="$UPLOADER $UPLOADERFLAGS $SOURCE"
)
# In-line command with arguments
env.Replace(
UPLOADCMD="executable -arg1 -arg2 $SOURCE"
)
# Python callback
def on_upload(source, target, env):
print source, target
firmware_path = str(source[0])
# do something
env.Execute("executable arg1 arg2")
env.Replace(UPLOADCMD=on_upload)
See project example https://github.com/platformio/bintray-secure-ota
Sometimes 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"))