Building, Flashing and Debugging

Zephyr provides several west extension commands for building, flashing, and interacting with Zephyr programs running on a board: build, flash, debug, debugserver and attach.

For information on adding board support for the flashing and debugging commands, see Flash and debug support in the board porting guide.

Building: west build

Tip

Run west build -h for a quick overview.

The build command helps you build Zephyr applications from source. You can use west config to configure its behavior.

Its default behavior tries to “do what you mean”:

  • If there is a Zephyr build directory named build in your current working directory, it is incrementally re-compiled. The same is true if you run west build from a Zephyr build directory.

  • Otherwise, if you run west build from a Zephyr application’s source directory and no build directory is found, a new one is created and the application is compiled in it.

Basics

The easiest way to use west build is to go to an application’s root directory (i.e. the folder containing the application’s CMakeLists.txt) and then run:

west build -b <BOARD>

Where <BOARD> is the name of the board you want to build for. This is exactly the same name you would supply to CMake if you were to invoke it with: cmake -DBOARD=<BOARD>.

Tip

You can use the west boards command to list all supported boards.

A build directory named build will be created, and the application will be compiled there after west build runs CMake to create a build system in that directory. If west build finds an existing build directory, the application is incrementally re-compiled there without re-running CMake. You can force CMake to run again with --cmake.

You don’t need to use the --board option if you’ve already got an existing build directory; west build can figure out the board from the CMake cache. For new builds, the --board option, BOARD environment variable, or build.board configuration option are checked (in that order).

Examples

Here are some west build usage examples, grouped by area.

Forcing CMake to Run Again

To force a CMake re-run, use the --cmake (or --c) option:

west build -c

Setting a Default Board

To configure west build to build for the reel_board by default:

west config build.board reel_board

(You can use any other board supported by Zephyr here; it doesn’t have to be reel_board.)

Setting Source and Build Directories

To set the application source directory explicitly, give its path as a positional argument:

west build -b <BOARD> path/to/source/directory

To set the build directory explicitly, use --build-dir (or -d):

west build -b <BOARD> --build-dir path/to/build/directory

To change the default build directory from build, use the build.dir-fmt configuration option. This lets you name build directories using format strings, like this:

west config build.dir-fmt "build/{board}/{app}"

With the above, running west build -b reel_board samples/hello_world will use build directory build/reel_board/hello_world. See Configuration Options for more details on this option.

Setting the Build System Target

To specify the build system target to run, use --target (or -t).

For example, on host platforms with QEMU, you can use the run target to build and run the Hello World sample for the emulated qemu_x86 board in one command:

west build -b qemu_x86 -t run samples/hello_world

As another example, to use -t to list all build system targets:

west build -t help

As a final example, to use -t to run the pristine target, which deletes all the files in the build directory:

west build -t pristine

Pristine Builds

A pristine build directory is essentially a new build directory. All byproducts from previous builds have been removed.

To force west build make the build directory pristine before re-running CMake to generate a build system, use the --pristine=always (or -p=always) option.

Giving --pristine or -p without a value has the same effect as giving it the value always. For example, these commands are equivalent:

west build -p -b reel_board samples/hello_world
west build -p=always -b reel_board samples/hello_world

By default, west build applies a heuristic to detect if the build directory needs to be made pristine. This is the same as using --pristine=auto.

Tip

You can run west config build.pristine always to always do a pristine build, or west config build.pristine never to disable the heuristic. See the west build Configuration Options for details.

Verbose Builds

To print the CMake and compiler commands run by west build, use the global west verbosity option, -v:

west -v build -b reel_board samples/hello_world

One-Time CMake Arguments

To pass additional arguments to the CMake invocation performed by west build, pass them after a -- at the end of the command line.

Important

Passing additional CMake arguments like this forces west build to re-run CMake, even if a build system has already been generated.

After using -- once to generate the build directory, use west build -d <build-dir> on subsequent runs to do incremental builds.

For example, to use the Unix Makefiles CMake generator instead of Ninja (which west build uses by default), run:

west build -b reel_board -- -G'Unix Makefiles'

To use Unix Makefiles and set CMAKE_VERBOSE_MAKEFILE to ON:

west build -b reel_board -- -G'Unix Makefiles' -DCMAKE_VERBOSE_MAKEFILE=ON

Notice how the -- only appears once, even though multiple CMake arguments are given. All command-line arguments to west build after a -- are passed to CMake.

To set DTC_OVERLAY_FILE to enable-modem.overlay, using that file as a devicetree overlay:

west build -b reel_board -- -DDTC_OVERLAY_FILE=enable-modem.overlay

To merge the file.conf Kconfig fragment into your build’s .config:

west build -- -DOVERLAY_CONFIG=file.conf

Permanent CMake Arguments

The previous section describes how to add CMake arguments for a single west build command. If you want to save CMake arguments for west build to use every time it generates a new build system instead, you should use the build.cmake-args configuration option. Whenever west build runs CMake to generate a build system, it splits this option’s value according to shell rules and includes the results in the cmake command line.

Remember that, by default, west build tries to avoid generating a new build system if one is present in your build directory. Therefore, you need to either delete any existing build directories or do a pristine build after setting build.cmake-args to make sure it will take effect.

For example, to always enable CMAKE_EXPORT_COMPILE_COMMANDS, you can run:

west config build.cmake-args -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

(The extra -- is used to force the rest of the command to be treated as a positional argument. Without it, west config would treat the -DVAR=VAL syntax as a use of its -D option.)

To enable CMAKE_VERBOSE_MAKEFILE, so CMake always produces a verbose build system:

west config build.cmake-args -- -DCMAKE_VERBOSE_MAKEFILE=ON

To save more than one argument in build.cmake-args, use a single string whose value can be split into distinct arguments (west build uses the Python function shlex.split() internally to split the value).

For example, to enable both CMAKE_EXPORT_COMPILE_COMMANDS and CMAKE_VERBOSE_MAKEFILE:

west config build.cmake-args -- "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_VERBOSE_MAKEFILE=ON"

If you want to save your CMake arguments in a separate file instead, you can combine CMake’s -C <initial-cache> option with build.cmake-args. For instance, another way to set the options used in the previous example is to create a file named ~/my-cache.cmake with the following contents:

set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "")
set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "")

Then run:

west config build.cmake-args "-C ~/my-cache.cmake"

See the cmake(1) manual page and the set() command documentation for more details.

Configuration Options

You can configure west build using these options.

Option

Description

build.board

String. If given, this the board used by west build when --board is not given and BOARD is unset in the environment.

build.board_warn

Boolean, default true. If false, disables warnings when west build can’t figure out the target board.

build.cmake-args

String. If present, the value will be split according to shell rules and passed to CMake whenever a new build system is generated. See Permanent CMake Arguments.

build.dir-fmt

String, default build. The build folder format string, used by west whenever it needs to create or locate a build folder. The currently available arguments are:

  • board: The board name

  • source_dir: The relative path from the current working directory to the source directory. If the current working directory is inside the source directory this will be set to an empty string.

  • app: The name of the source directory.

build.generator

String, default Ninja. The CMake Generator to use to create a build system. (To set a generator for a single build, see the above example)

build.guess-dir

String, instructs west whether to try to guess what build folder to use when build.dir-fmt is in use and not enough information is available to resolve the build folder name. Can take these values:

  • never (default): Never try to guess, bail out instead and require the user to provide a build folder with -d.

  • runners: Try to guess the folder when using any of the ‘runner’ commands. These are typically all commands that invoke an external tool, such as flash and debug.

build.pristine

String. Controls the way in which west build may clean the build folder before building. Can take the following values:

  • never (default): Never automatically make the build folder pristine.

  • auto: west build will automatically make the build folder pristine before building, if a build system is present and the build would fail otherwise (e.g. the user has specified a different board or application from the one previously used to make the build directory).

  • always: Always make the build folder pristine before building, if a build system is present.

Flashing: west flash

Tip

Run west flash -h for additional help.

Basics

From a Zephyr build directory, re-build the binary and flash it to your board:

west flash

Without options, the behavior is the same as ninja flash (or make flash, etc.).

To specify the build directory, use --build-dir (or -d):

west flash --build-dir path/to/build/directory

If you don’t specify the build directory, west flash searches for one in build, then the current working directory. If you set the build.dir-fmt configuration option (see Setting Source and Build Directories), west flash searches there instead of build.

Choosing a Runner

If your board’s Zephyr integration supports flashing with multiple programs, you can specify which one to use using the --runner (or -r) option. For example, if West flashes your board with nrfjprog by default, but it also supports JLink, you can override the default with:

west flash --runner jlink

See Flash and debug runners below for more information on the runner library used by West. The list of runners which support flashing can be obtained with west flash -H; if run from a build directory or with --build-dir, this will print additional information on available runners for your board.

Configuration Overrides

The CMake cache contains default values West uses while flashing, such as where the board directory is on the file system, the path to the kernel binaries to flash in several formats, and more. You can override any of this configuration at runtime with additional options.

For example, to override the HEX file containing the Zephyr image to flash (assuming your runner expects a HEX file), but keep other flash configuration at default values:

west flash --kernel-hex path/to/some/other.hex

The west flash -h output includes a complete list of overrides supported by all runners.

Runner-Specific Overrides

Each runner may support additional options related to flashing. For example, some runners support an --erase flag, which mass-erases the flash storage on your board before flashing the Zephyr image.

To view all of the available options for the runners your board supports, as well as their usage information, use --context (or -H):

west flash --context

Important

Note the capital H in the short option name. This re-runs the build in order to ensure the information displayed is up to date!

When running West outside of a build directory, west flash -H just prints a list of runners. You can use west flash -H -r <runner-name> to print usage information for options supported by that runner.

For example, to print usage information about the jlink runner:

west flash -H -r jlink

Debugging: west debug, west debugserver

Tip

Run west debug -h or west debugserver -h for additional help.

Basics

From a Zephyr build directory, to attach a debugger to your board and open up a debug console (e.g. a GDB session):

west debug

To attach a debugger to your board and open up a local network port you can connect a debugger to (e.g. an IDE debugger):

west debugserver

Without options, the behavior is the same as ninja debug and ninja debugserver (or make debug, etc.).

To specify the build directory, use --build-dir (or -d):

west debug --build-dir path/to/build/directory
west debugserver --build-dir path/to/build/directory

If you don’t specify the build directory, these commands search for one in build, then the current working directory. If you set the build.dir-fmt configuration option (see Setting Source and Build Directories), west debug searches there instead of build.

Choosing a Runner

If your board’s Zephyr integration supports debugging with multiple programs, you can specify which one to use using the --runner (or -r) option. For example, if West debugs your board with pyocd-gdbserver by default, but it also supports JLink, you can override the default with:

west debug --runner jlink
west debugserver --runner jlink

See Flash and debug runners below for more information on the runner library used by West. The list of runners which support debugging can be obtained with west debug -H; if run from a build directory or with --build-dir, this will print additional information on available runners for your board.

Configuration Overrides

The CMake cache contains default values West uses for debugging, such as where the board directory is on the file system, the path to the kernel binaries containing symbol tables, and more. You can override any of this configuration at runtime with additional options.

For example, to override the ELF file containing the Zephyr binary and symbol tables (assuming your runner expects an ELF file), but keep other debug configuration at default values:

west debug --kernel-elf path/to/some/other.elf
west debugserver --kernel-elf path/to/some/other.elf

The west debug -h output includes a complete list of overrides supported by all runners.

Runner-Specific Overrides

Each runner may support additional options related to debugging. For example, some runners support flags which allow you to set the network ports used by debug servers.

To view all of the available options for the runners your board supports, as well as their usage information, use --context (or -H):

west debug --context

(The command west debugserver --context will print the same output.)

Important

Note the capital H in the short option name. This re-runs the build in order to ensure the information displayed is up to date!

When running West outside of a build directory, west debug -H just prints a list of runners. You can use west debug -H -r <runner-name> to print usage information for options supported by that runner.

For example, to print usage information about the jlink runner:

west debug -H -r jlink

Flash and debug runners

The flash and debug commands use Python wrappers around various Flash & Debug Host Tools. These wrappers are all defined in a Python library at scripts/west_commands/runners. Each wrapper is called a runner. Runners can flash and/or debug Zephyr programs.

The central abstraction within this library is ZephyrBinaryRunner, an abstract class which represents runners. The set of available runners is determined by the imported subclasses of ZephyrBinaryRunner. ZephyrBinaryRunner is available in the runners.core module; individual runner implementations are in other submodules, such as runners.nrfjprog, runners.openocd, etc.

Hacking

This section documents the runners.core module used by the flash and debug commands. This is the core abstraction used to implement support for these features.

Warning

These APIs are provided for reference, but they are more “shared code” used to implement multiple extension commands than a stable API.

Developers can add support for new ways to flash and debug Zephyr programs by implementing additional runners. To get this support into upstream Zephyr, the runner should be added into a new or existing runners module, and imported from runners/__init__.py.

Note

The test cases in scripts/west_commands/tests add unit test coverage for the runners package and individual runner classes.

Please try to add tests when adding new runners. Note that if your changes break existing test cases, CI testing on upstream pull requests will fail.

Zephyr binary runner core interfaces

This provides the core ZephyrBinaryRunner class meant for public use, as well as some other helpers for concrete runner classes.

class runners.core.BuildConfiguration(build_dir: str)

This helper class provides access to build-time configuration.

Configuration options can be read as if the object were a dict, either object[‘CONFIG_FOO’] or object.get(‘CONFIG_FOO’).

Kconfig configuration values are available (parsed from .config).

exception runners.core.MissingProgram(program)

FileNotFoundError subclass for missing program dependencies.

No significant changes from the parent FileNotFoundError; this is useful for explicitly signaling that the file in question is a program that some class requires to proceed.

The filename attribute contains the missing program.

class runners.core.NetworkPortHelper

Helper class for dealing with local IP network ports.

get_unused_ports(starting_from)

Find unused network ports, starting at given values.

starting_from is an iterable of ports the caller would like to use.

The return value is an iterable of ports, in the same order, using the given values if they were unused, or the next sequentially available unused port otherwise.

Ports may be bound between this call’s check and actual usage, so callers still need to handle errors involving returned ports.

class runners.core.RunnerCaps(commands: Set[str] = {'attach', 'debug', 'debugserver', 'flash'}, flash_addr: bool = False, erase: bool = False)

This class represents a runner class’s capabilities.

Each capability is represented as an attribute with the same name. Flag attributes are True or False.

Available capabilities:

  • commands: set of supported commands; default is {‘flash’, ‘debug’, ‘debugserver’, ‘attach’}.

  • flash_addr: whether the runner supports flashing to an arbitrary address. Default is False. If true, the runner must honor the –dt-flash option.

  • erase: whether the runner supports an –erase option, which does a mass-erase of the entire addressable flash on the target before flashing. On multi-core SoCs, this may only erase portions of flash specific the actual target core. (This option can be useful for things like clearing out old settings values or other subsystem state that may affect the behavior of the zephyr image. It is also sometimes needed by SoCs which have flash-like areas that can’t be sector erased by the underlying tool before flashing; UICR on nRF SoCs is one example.)

class runners.core.RunnerConfig(build_dir: str, board_dir: str, elf_file: str, hex_file: str, bin_file: str, gdb: Optional[str] = None, openocd: Optional[str] = None, openocd_search: Optional[str] = None)

Runner execution-time configuration.

This is a common object shared by all runners. Individual runners can register specific configuration options using their do_add_parser() hooks.

bin_file: str

Alias for field number 4

board_dir: str

Alias for field number 1

build_dir: str

Alias for field number 0

elf_file: str

Alias for field number 2

gdb: Optional[str]

Alias for field number 5

hex_file: str

Alias for field number 3

openocd: Optional[str]

Alias for field number 6

Alias for field number 7

class runners.core.ZephyrBinaryRunner(cfg: runners.core.RunnerConfig)

Abstract superclass for binary runners (flashers, debuggers).

Note: this class’s API has changed relatively rarely since it as added, but it is not considered a stable Zephyr API, and may change without notice.

With some exceptions, boards supported by Zephyr must provide generic means to be flashed (have a Zephyr firmware binary permanently installed on the device for running) and debugged (have a breakpoint debugger and program loader on a host workstation attached to a running target).

This is supported by four top-level commands managed by the Zephyr build system:

  • ‘flash’: flash a previously configured binary to the board, start execution on the target, then return.

  • ‘debug’: connect to the board via a debugging protocol, program the flash, then drop the user into a debugger interface with symbol tables loaded from the current binary, and block until it exits.

  • ‘debugserver’: connect via a board-specific debugging protocol, then reset and halt the target. Ensure the user is now able to connect to a debug server with symbol tables loaded from the binary.

  • ‘attach’: connect to the board via a debugging protocol, then drop the user into a debugger interface with symbol tables loaded from the current binary, and block until it exits. Unlike ‘debug’, this command does not program the flash.

This class provides an API for these commands. Every subclass is called a ‘runner’ for short. Each runner has a name (like ‘pyocd’), and declares commands it can handle (like ‘flash’). Boards (like ‘nrf52dk_nrf52832’) declare which runner(s) are compatible with them to the Zephyr build system, along with information on how to configure the runner to work with the board.

The build system will then place enough information in the build directory to create and use runners with this class’s create() method, which provides a command line argument parsing API. You can also create runners by instantiating subclasses directly.

In order to define your own runner, you need to:

  1. Define a ZephyrBinaryRunner subclass, and implement its abstract methods. You may need to override capabilities().

  2. Make sure the Python module defining your runner class is imported, e.g. by editing this package’s __init__.py (otherwise, get_runners() won’t work).

  3. Give your runner’s name to the Zephyr build system in your board’s board.cmake.

Additional advice:

  • If you need to import any non-standard-library modules, make sure to catch ImportError and defer complaints about it to a RuntimeError if one is missing. This avoids affecting users that don’t require your runner, while still making it clear what went wrong to users that do require it that don’t have the necessary modules installed.

  • If you need to ask the user something (e.g. using input()), do it in your create() classmethod, not do_run(). That ensures your __init__() really has everything it needs to call do_run(), and also avoids calling input() when not instantiating within a command line application.

  • Use self.logger to log messages using the standard library’s logging API; your logger is named “runner.<your-runner-name()>”

For command-line invocation from the Zephyr build system, runners define their own argparse-based interface through the common add_parser() (and runner-specific do_add_parser() it delegates to), and provide a way to create instances of themselves from a RunnerConfig and parsed runner-specific arguments via create().

Runners use a variety of host tools and configuration values, the user interface to which is abstracted by this class. Each runner subclass should take any values it needs to execute one of these commands in its constructor. The actual command execution is handled in the run() method.

classmethod add_parser(parser)

Adds a sub-command parser for this runner.

The given object, parser, is a sub-command parser from the argparse module. For more details, refer to the documentation for argparse.ArgumentParser.add_subparsers().

The lone common optional argument is:

  • –dt-flash (if the runner capabilities includes flash_addr)

Runner-specific options are added through the do_add_parser() hook.

call(cmd: List[str]) → int

Subclass subprocess.call() wrapper.

Subclasses should use this method to run command in a subprocess and get its return code, rather than using subprocess directly, to keep accurate debug logs.

classmethod capabilities()runners.core.RunnerCaps

Returns a RunnerCaps representing this runner’s capabilities.

This implementation returns the default capabilities.

Subclasses should override appropriately if needed.

cfg

RunnerConfig for this instance.

check_call(cmd: List[str])

Subclass subprocess.check_call() wrapper.

Subclasses should use this method to run command in a subprocess and check that it executed correctly, rather than using subprocess directly, to keep accurate debug logs.

check_output(cmd: List[str], **kwargs) → bytes

Subclass subprocess.check_output() wrapper.

Subclasses should use this method to run command in a subprocess and check that it executed correctly, rather than using subprocess directly, to keep accurate debug logs.

classmethod create(cfg: runners.core.RunnerConfig, args: argparse.Namespace)runners.core.ZephyrBinaryRunner

Create an instance from command-line arguments.

  • cfg: runner configuration (pass to superclass __init__)

  • args: arguments parsed from execution environment, as specified by add_parser().

abstract classmethod do_add_parser(parser)

Hook for adding runner-specific options.

abstract classmethod do_create(cfg: runners.core.RunnerConfig, args: argparse.Namespace)runners.core.ZephyrBinaryRunner

Hook for instance creation from command line arguments.

abstract do_run(command: str, **kwargs)

Concrete runner; run() delegates to this. Implement in subclasses.

In case of an unsupported command, raise a ValueError.

classmethod get_flash_address(args: argparse.Namespace, build_conf: runners.core.BuildConfiguration, default: int = 0) → int

Helper method for extracting a flash address.

If args.dt_flash is true, get the address from the BoardConfiguration, build_conf. (If CONFIG_HAS_FLASH_LOAD_OFFSET is n in that configuration, it returns CONFIG_FLASH_BASE_ADDRESS. Otherwise, it returns CONFIG_FLASH_BASE_ADDRESS + CONFIG_FLASH_LOAD_OFFSET.)

Otherwise (when args.dt_flash is False), the default value is returned.

static get_runners() → List[Type[runners.core.ZephyrBinaryRunner]]

Get a list of all currently defined runner classes.

logger

logging.Logger for this instance.

abstract classmethod name() → str

Return this runner’s user-visible name.

When choosing a name, pick something short and lowercase, based on the name of the tool (like openocd, jlink, etc.) or the target architecture/board (like xtensa etc.).

popen_ignore_int(cmd: List[str]) → subprocess.Popen

Spawn a child command, ensuring it ignores SIGINT.

The returned subprocess.Popen object must be manually terminated.

static require(program: str)

Require that a program is installed before proceeding.

Parameters

program – name of the program that is required, or path to a program binary.

If program is an absolute path to an existing program binary, this call succeeds. Otherwise, try to find the program by name on the system PATH.

On error, raises MissingProgram.

run(command: str, **kwargs)

Runs command (‘flash’, ‘debug’, ‘debugserver’, ‘attach’).

This is the main entry point to this runner.

run_server_and_client(server, client)

Run a server that ignores SIGINT, and a client that handles it.

This routine portably:

  • creates a Popen object for the server command which ignores SIGINT

  • runs client in a subprocess while temporarily ignoring SIGINT

  • cleans up the server after the client exits.

It’s useful to e.g. open a GDB server and client.

Doing it By Hand

If you prefer not to use West to flash or debug your board, simply inspect the build directory for the binaries output by the build system. These will be named something like zephyr/zephyr.elf, zephyr/zephyr.hex, etc., depending on your board’s build system integration. These binaries may be flashed to a board using alternative tools of your choice, or used for debugging as needed, e.g. as a source of symbol tables.

By default, these West commands rebuild binaries before flashing and debugging. This can of course also be accomplished using the usual targets provided by Zephyr’s build system (in fact, that’s how these commands do it).

Footnotes