Sming build system
This guide is provided to assist with understanding, developing and modifying the build system.
A Sming project is built from a set of static libraries (object archives). Typically the application code is one library, built from the user’s source code, whilst the other libraries are common to all projects and stored in a separate, shared location.
Until recently Sming itself has always been built as one large library, but this is now broken into a number of discrete Component libraries. The concept is borrowed from Espressif’s ESP-IDF build system and whilst there are some similarities the two systems are completely independent.
These are the main variables you need to be aware of:
must be set to the full path of the
Defines the target architecture
Esp8266 The default if not specified.
ESP_HOMEmust also be provided to locate SDK & tools.
Esp32 Supports ESP32 architecture.
Host builds a version of the library for native host debugging on Linux or Windows
Rp2040 Supports Raspberry Pi RP2040-based boards.
Some architectures support families of SOCs with different capabilities. Set this value to the specific variant being targeted.
Will automatically set SMING_ARCH to the appropriate value.
The build standard applied for the framework.
This defaults to C++17 if the toolchain supports it (GCC 5+), C++11 otherwise. You can override to use other standards, such as
c++2afor experimental C++20 support.
These variables are available for application use:
Path to the project’s root source directory, without trailing path separator. This variable is available within makefiles, but is also provided as a #defined C string to allow references to source files within application code, such as with the
COMPONENT_PATH As for PROJECT_DIR, but provides the path to the
current component’s root source directory.
Converting existing projects
Makefile-user.mk a project should provide a
component.mk. To convert to the new style:
Copy any customisations from
component.mkfile. (Or, rename
component.mkthen edit it.)
If the project uses any Arduino libraries, set the
Targets You can add your own targets to component.mk as usual. It’s a good idea to add a comment for the target, like this:
##@Building .PHONY: mytarget mytarget: ##This is my target
When you type
make help it will appear in the list.
If you need a target to be added as a dependency to the main application
build, add it to
CUSTOM_TARGETS - the Basic Serial sample
contains a simple example of this.
If your project uses any Arduino libraries, you must set this value appropriately.
Source files Use
COMPONENT_SRCDIRS instead of
COMPONENT_SRCFILES to add individual files.
Include paths Use
COMPONENT_INCDIRS instead of
unless the paths are only required to build this Component.
See component.mk for a full list of variables.
You should normally work from the project directory. Examples:
maketo build the project and any required Components. To speed things up, use parallel building, e.g.
make -j5will build using a maximum of 5 concurrent jobs. The optimum value for this is usually (CPU CORES + 1). Using
make -jwill use unlimited jobs, but can cause problems in virtual environments.
make helpfrom the project directory to get a list of available build targets.
To switch to a different build architecture, for example:
make SMING_ARCH=Hostto build the project for the host emulator
make flashto copy any SPIFFS image (if enabled) to the virtual flash, and run the application. (Note that you don’t need to set SMING_ARCH again, the value is cached.)
To inspect the current build configuration, type
The appropriate hardware configuration should be selected in the project’s component.mk file. Use one of the standard configurations or create your own. See Hardware configuration.
Configuration variables should be set in the project’s component.mk file. If appropriate, they can also be set as environment variables.
During development, the easiest way to change variables is on the
make command line. These are cached so persist between make
sessions, and will override any values set in your project’s
component.mk file. For example:
make SPIFF_BIN=test-romto build the project and (if enabled) create a SPIFFS image file called
make flash COM_PORT=COM4to flash the project and
test-romSPIFFS image using the provided flash memory settings
Next time you type
make flash, the same settings will be used, no need to type them again
A separate cache is maintained for each build type (arch + release/debug). For example:
make SMING_RELEASE=1 list-configto switch to release build and display the active configuration
make config-clean to clear all caches and revert to defaults.
For reference, a copy of all build variables are stored in a file with each firmware image created in the ‘firmware’ directory.
Placing Components in a common location allows them to be used by
multiple projects. To set up your own Component repository, create a
directory in a suitable location which will contain your Components and
COMPONENT_SEARCH_DIRS to the full path of that directory. For
|_ opt/ |_ shared/ |_ Components/ The repository |_ MyComponent/ |_ AnotherComponent/ |_ spiffs/ Will be used instead of Sming version
User repositories are searched first, which allows replacement of any
Component for a project. In this example, our
spiffs component will
be selected instead of the one provided with Sming.
The main Sming repo. is laid out like this:
|_ sming/ |_ .appveyor.yml CI testing |_ .travis.yml CI testing |_ .readthedocs.yml Documentation build |_ lgtm.yml CI Static code analysis |_ docs/ Sming documentation |_ samples/ Samples to demonstrate specific Sming features or libraries |_ Sming/ | |_ Makefile Builds documentation, performs global actions on the framework | |_ project.mk Main makefile to build a project | |_ build.mk Defines the build environment | |_ component.mk Sming Component definition file | |_ component-wrapper.mk Used to build each Component using a separate make instance | |_ Arch/ Architecture-specific makefiles and code | | |_ Esp8266/ | | | |_ sming.mk Defines architecture-specific Components and libraries | | | |_ app.mk Link the project, create output binaries | | | | and perform architecture-specific actions | | | |_ build.mk Architecture-specific build definitions, such as compiler paths | | | |_ Compiler/ | | | |_ Components/ | | | |_ Core/ | | | |_ Platform/ | | | |_ System/ | | | |_ Tools/ Pre-compiled or scripted tools | | |_ Esp32/ | | | |_ ... | | |_ Host/ | | |_ ... | |_ Components/ Framework support code, not to be used directly by applications | |_ Core/ Main framework core | |_ Libraries/ Arduino Libraries | | |_ ... | |_ out/ All generated shared files are written here | | |_ Esp8266/ The Arch | | | |_ debug/ The build type | | | |_ build/ Intermediate object files | | | | |_ Lib/ Generated libraries | | | | |_ tools/ Generated tools | | | |_ release/ | | | |_ ... | | |_ Host/ | | |_ ... | |_ Platform/ System-level classes | | |_ ... | |_ Services/ Modules not considered as part of Core | | |_ ... | |_ System/ Common framework low-level system code | | |_ include/ | |_ Wiring/ | |_ ... |_ tests/ Integration test applications |_ ... |_ Tools/ |_ ci CI testing |_ Docker |_ ide IDE environment support tools |_ Python Shared python scripts |_ travis CI testing
A typical Project looks like this:
|_ Basic_Blink/ |_ Makefile Just includes project.mk |_ component.mk Project-specific definitions |_ app/ Default application source directory |_ include/ Default application include directory |_ out/ All generated shared files are written here |_ Esp8266/ The Architecture | |_ debug/ The build type | | |_ build/ Intermediate object files | | |_ firmware/ Target output files | | |_ lib/ Generated libraries | | |_ tools/ Generated tools | |_ release/ | |_ ... |_ Host |_ ...
The purpose of a Component is to encapsulate related elements for selective inclusion in a project, for easy sharing and re-use:
Shared Library with associated header files
App Code Source files to be compiled directly into the user’s project
Header files without any associated source or library
Build targets to perform specific actions, such as flashing binary data to hardware
By default, a Component is built into a shared library using any source
files found in the base or
src directories. All Arduino Libraries
are built as Components. Note that the application is also built as a
Component library, but the source directory defaults to
Components are referred to simply by name, defined by the directory in
which it is stored. The Component itself is located by looking in all
the directories listed by
COMPONENT_SEARCH_DIRS, which contains a
list of repositories. (Every sub-directory of a repository is considered
to be a Component.) If there are Components with the same name in
different search directories, the first one found will be used.
Components are customised by providing an optional
You can see details of all Components used in a project using
make list-components. Add
V=1 to get more details.
Note that the application itself is also built as a Component, and may be configured in a similar way to any other Component.
Libraries can often be built using different option settings, so a mechanism is required to ensure that libraries (including the application) are rebuilt if those settings change. This is handled using variants, which modifies the library name using a hash of the settings values. Each variant gets its own build sub-directory so incremental building works as usual.
There are several types of config variable:
Rebuild application ?
Variables are usually defined in the context of a Component, in the component.mk file. All Components see the full configuration during building, not just their own variables.
The type of a configuration variable is defined by adding its name to one of the following lists:
The Application library derives its variant from these variables. Use this type if the Component doesn’t require a rebuild, but the application does.
A Component library derives its variant from these variables. Any variable which requires a rebuild of the Component library itself must be listed. For example, the
esp-open-lwipComponent defines this as
ENABLE_LWIPDEBUG ENABLE_ESPCONN. The default values for these produces
ENABLE_LWIPDEBUG=0 ENABLE_ESPCONN=0, which is hashed (using MD5) to produce
a46d8c208ee44b1ee06f8e69cfa06773, which is appended to the library name.
All dependent Components (which list this one in
COMPONENT_DEPENDS) will also have a variant created.
Behaves just like
COMPONENT_VARSexcept dependent Components are not rebuilt. This is appropriate where the public interface (header files) are not affected by the variable setting, but the library implementation still requires a variant.
Code isn’t re-compiled, but libraries are re-linked and firmware images re-generated if any of these variables are changed. For example,
make RBOOT_ROM_0=new-rom-filerewrites the firmware image using the given filename. (Also, as the value is cached, if you then do
make flashappthat same image gets flashed.)
These variables have no effect on building, but are cached. Variables such as
COM_SPEED_ESPTOOLfall into this category.
These are generally for information only, and are not cached (except for
Note that the lists not prefixed
COMPONENT_xx are global and so should only
be appended, never assigned.
COMPONENT_DEPENDS identifies a list of Components upon which this
one depends. These are established as pre-requisites so will trigger a
rebuild. In addition, all dependent
COMPONENT_VARS are (recursively)
used in creation of the library hash.
For example, the
axtls-8266 Component declares
SSL_DEBUG as a
Sming depends on
sming-arch, which in
turn depends on
axtls-8266, all of these Components get rebuilt as
different variants when
SSL_DEBUG changes values. The project code
App Component) also gets rebuilt as it implicitly depends on
Sming uses source code from other repositories. Instead of including local copies, these are handled using GIT submodules. Where changes are required, patches may be provided as a diff .patch file and/or set of files to be added/replaced. Only those submodules necessary for a build are pulled in, as follows:
The submodule is fetched from its remote repository
If a .patch file exists, it is applied
Any additional files are copied into the submodule directory
.submodulefile is created to tells the build system that the submodule is present and correct.
The patch file must have the same name as the submodule, with a .patch extension. It can be located in the submodule’s parent directory:
|_ Components/ |_ heap/ |_ .component.mk Component definition |_ umm_malloc.patch Diff patch file |_ umm_malloc/ Submodule directory |_ .submodule Created after successful patching ...
However, if the Component is itself a submodule, then patch files must
be placed in a
|_ Libraries/ |_ .patches/ | |_ Adafruit_SSD1306.patch Diff patch file | |_ Adafruit_SSD1306/ | |_ component.mk This file is added to submodule |_ Adafruit_SSD1306/ The submodule directory |_ .submodule Created after successful patching ...
This example includes additional files for the submodule. There are some advantages to this approach:
Don’t need to modify or create .patch
Changes to the file are easier to follow than in a .patch
IMPORTANT Adding a component.mk file in this manner allows the build system to resolve dependencies before any submodules are fetched.
In the above example, the
component.mk file defines a dependency on
Adafruit_GFX library, so that will automatically get pulled in
component.mk is parsed twice, first from the top-level makefile
and the second time from the sub-make which does the actual building. A
number of variables are used to define behaviour.
These values are for reference only and should not be modified.
Name of the Component
Base directory path for Component, no trailing path separator
The current directory.
This should be used if the Component provides any application code or targets to ensure it is built in the correct directory (but not by this makefile).
This value changes depending on the build variant.
This value does not change with build variant.
If the Component generates source code, for example, it can be placed here (in a sub-directory).
Location to store created Component (shared) libraries
Name of the library to build
Full path to the library to be built
These values may be used to customise Component behaviour and may be changed as required.
By default, the library has the same name as the Component but can be changed if required. Note that this will be used as the stem for any variants.
COMPONENT_LIBNAME :=if the Component doesn’t create a library. If you don’t do this, a default library will be built but will be empty if no source files are found.
Set this to any additional targets to be built as part of the Component, prefixed with
If targets should be built for each application, use
CUSTOM_TARGETSinstead. See SPIFFS IFS Library for an example.
These targets will be built before anything else. If your library generates source code, for example, then it should be done by setting this value to the appropriate targets.
This is a special value used to prefix any custom targets which are to be built as part of the Component. The target must be prefixed by
$(COMPONENT_RULE)without any space between it and the target. This ensures the rule only gets invoked during a component build, and is ignored by the top-level make.
Relative paths to dependent submodule directories for this Component. These will be fetched/patched automatically before building.
Default behaviour is to initialise submodules recursively. To prevent this behaviour and initialise only the top-level submodule, add a file to the parent directory with the same name as the submodule and a
Locations for source code relative to COMPONENT_PATH (defaults to “. src”)
Individual source files. Useful for conditional includes.
Include directories available when building ALL Components (not just this one). Paths may be relative or absolute
Include directories for just this Component. Paths may be relative or absolute
The resultant set of include directories used to build this Component. Will contain include directories specified by all other Components in the build. May be overridden if required.
List of directories containing source code to be compiled directly with the application. (Ignore in the project.mk file - use
Set to 1 if providing an alternative build method. See Custom building section.
Absolute paths to any additional binary object files to be added to the Component archive library.
Set to the name(s) of any dependent Components.
Set to names of any additional libraries to be linked.
Set to any additional flags to be used when linking.
If the component requires uncommon Python modules (e. g. as part of a custom build step), set this variable to one or more requirements.txt files. This allows installation of all python requirements of the project by invoking:
make python-requirements [PIP_ARGS=...]
A requirements.txt file in the root directory of the Component is detected automatically without setting this variable. To prevent autodetection (e.g. if the python requirements depend on another configuration variable) you must set this variable to an empty value.
These values are global so must only be appended to (with
Identifies targets to be built along with the application. These will be invoked directly by the top-level make.
Use only if you need to provide additional compiler flags to be included when building all Components (including Application) and custom targets.
Used when building application and custom targets.
Will be visible ONLY to C code within the component.
Will be visible ONLY to C++ code within the component.
Will be visible to both C and C++ code within the component.
During initial parsing, many of these variables (specifically, the
COMPONENT_xxx ones) do not keep their values. For this reason it
is usually best to use simple variable assignment using
For example, in
Esp8266/Components/gdbstub we define
GDB_CMDLINE. It may be tempting to do this:
GDB_CMDLINE = trap '' INT; $(GDB) -x $(COMPONENT_PATH)/gdbcmds -b $(COM_SPEED_GDB) -ex "target remote $(COM_PORT_GDB)"
That won’t work! By the time
GDB_CMDLINE gets expanded,
COMPONENT_PATH could contain anything. We need
GDB_CMDLINE to be
expanded only when used, so the solution is to take a simple copy of
COMPONENT_PATH and use it instead, like this:
GDBSTUB_DIR := $(COMPONENT_PATH) GDB_CMDLINE = trap '' INT; $(GDB) -x $(GDBSTUB_DIR)/gdbcmds -b $(COM_SPEED_GDB) -ex "target remote $(COM_PORT_GDB)"
These values are global and should be used ONLY in the
Sming/Arch/*/build.mk files to tune the architecture compilation flags.
These values must only be appended to (with
+=), never overwritten.
Used to provide both C and C++ flags that are applied globally.
Used to provide ONLY C flags that are applied globally.
Used to provide ONLY C++ flags that are applied globally.
Used to provide the C language standard. The default is
Do NOT set
CXXFLAGS outside of the
For faster builds use make with the
-j (jobs) feature of make. It is
usually necessary to specify a limit for the number of jobs, especially
on virtual machines. There is usually no point in using a figure greater
than (CPU cores + 1). The CI builds use
Makefile-app.mk enforces sequential building to ensure
submodules are fetched and patched correctly. This also ensures that
only one Component is built at a time which keeps the build logs quite
clean and easy to follow.
Components can be rebuilt and cleaned individually. For example:
make spiffs-buildruns the Component ‘make’ for spiffs, which contains the SPIFFS library.
make spiffs-cleanremoves all intermediate build files for the Component
make spiffs-rebuildcleans and then re-builds the Component
By default, a regular
make performs an incremental build on the
application, which invokes a separate (recursive) make for the
Component. All other Components only get built if any of their targets
don’t exist (e.g. variant library not yet built). This makes application
building faster and less ‘busy’, which is generally preferable for
regular application development. For Component development this
behaviour can be changed using the
(which is cached). Examples:
make FULL_COMPONENT_BUILD=lwipwill perform an incremental build on the
make FULL_COMPONENT_BUILD=1will incrementally build all Components
To use an external makefile or other build system (such as CMake) to
create the Component library, or to add additional shared libraries or
other targets, customise the
component.mk file as follows:
Define the custom rule, prefixed with
$(COMPONENT_RULE). Note that Components are built using a separate make instance with the current directory set to the build output directory, not the source directory.
It is important that the rule uses the provided values for
that variant building, cleaning, etc. work correctly. See below under
‘Building’, and the Host
lwip Component for an example.
Components are built using a make instance with the current directory set to the build output directory, not the source directory. If any custom building is done then these variables must be obeyed to ensure variants, etc. work as expected:
COMPONENT_LIBNAME as provided by component.mk, defaults to component
COMPONENT_LIBHASH hash of the component
variables used to create unique library names,
of the library to be built, including hash.
directory where any generated libraries must be output,
COMPONENT_LIBPATH full path to the library to be created,
COMPONENT_BUILDDIR where to write intermediate object files,
Porting existing libraries
to be completed
Cleaning Components are not cleaned unless defined.
make axtls-8266-clean will fail unless you also specify
Empty libraries Components without any source code produce an empty library. This is because, for simplicity, we don’t want to add a component.mk to every Arduino library.
Empty Component directories Every sub-directory in the
COMPONENT_SEARCH_DIRS is interpreted as a Component. For example,
spiffs was moved out of Arch/Esp8266/Components but if an empty
directory called ‘spiffs’ still remains then it will be picked up
instead of the main one. These sorts of issues can be checked using
make list-components to ensure the correct Component path has been