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 runwest 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.
Build tool arguments
Use -o
to pass options to the underlying build tool.
This works with both ninja
(the default)
and make
based build systems.
For example, to pass -dexplain
to ninja
:
west build -o=-dexplain
As another example, to pass --keep-going
to make
:
west build -o=--keep-going
Note that using -o=--foo
instead of -o --foo
is required to prevent
--foo
from being treated as a west build
option.
Build parallelism
By default, ninja
uses all of your cores to build, while make
uses only
one. You can control this explicitly with the -j
option supported by both
tools.
For example, to build with 4 cores:
west build -o=-j4
The -o
option is described further in the previous section.
Configuration Options
You can configure west build
using these options.
Option |
Description |
---|---|
|
String. If given, this the board used by west build when |
|
Boolean, default |
|
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. |
|
String, default
|
|
String, default |
|
String, instructs west whether to try to guess what build folder to use
when
|
|
String. Controls the way in which
|
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
You can override the default flash runner at build time by using the
BOARD_FLASH_RUNNER
CMake variable, and the debug runner with
BOARD_DEBUG_RUNNER
.
For example:
# Set the default runner to "jlink", overriding the board's
# usual default.
west build [...] -- -DBOARD_FLASH_RUNNER=jlink
See One-Time CMake Arguments and Permanent CMake Arguments for more information on setting CMake arguments.
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 zephyr 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 --hex-file 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 zephyr 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 --elf-file path/to/some/other.elf
west debugserver --elf-file 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).
- getboolean(option)
If a boolean option is explicitly set to y or n, returns its value. Otherwise, falls back to False.
- class runners.core.DeprecatedAction(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)
- 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'}, dev_id: bool = False, 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’}.
dev_id: whether the runner supports device identifiers, in the form of an -i, –dev-id option. This is useful when the user has multiple debuggers connected to a single computer, in order to select which one will be used with the command provided.
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: Optional[str], hex_file: Optional[str], bin_file: Optional[str], gdb: Optional[str] = None, openocd: Optional[str] = None, openocd_search: List[str] = [])
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: Optional[str]
Alias for field number 4
- board_dir: str
Alias for field number 1
- build_dir: str
Alias for field number 0
- elf_file: Optional[str]
Alias for field number 2
- gdb: Optional[str]
Alias for field number 5
- hex_file: Optional[str]
Alias for field number 3
- openocd: Optional[str]
Alias for field number 6
- openocd_search: List[str]
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:
Define a ZephyrBinaryRunner subclass, and implement its abstract methods. You may need to override capabilities().
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).
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.
- property build_conf: runners.core.BuildConfiguration
Get a BuildConfiguration for the build directory.
- call(cmd: List[str], **kwargs) 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], **kwargs)
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 byadd_parser()
.
- classmethod dev_id_help() str
Get the ArgParse help text for the –dev-id option.
- 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.
- ensure_output(output_type: str) None
Ensure self.cfg has a particular output artifact.
For example, ensure_output(‘bin’) ensures that self.cfg.bin_file refers to an existing file. Errors out if it’s missing or undefined.
- Parameters
output_type – string naming the output type
- static flash_address_from_build_conf(build_conf: runners.core.BuildConfiguration)
If CONFIG_HAS_FLASH_LOAD_OFFSET is n in build_conf, return the CONFIG_FLASH_BASE_ADDRESS value. Otherwise, return CONFIG_FLASH_BASE_ADDRESS + CONFIG_FLASH_LOAD_OFFSET.
- static 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, returns the address obtained from ZephyrBinaryRunner.flash_address_from_build_conf(build_conf).
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) 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.If the program can be found, its path is returned. Otherwise, raises MissingProgram.
- run(command: str, **kwargs)
Runs command (‘flash’, ‘debug’, ‘debugserver’, ‘attach’).
This is the main entry point to this runner.
- run_client(client)
Run a client that handles SIGINT.
- 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 SIGINTruns
client
in a subprocess while temporarily ignoring SIGINTcleans up the server after the client exits.
It’s useful to e.g. open a GDB server and client.
- property thread_info_enabled: bool
Returns True if self.build_conf has CONFIG_DEBUG_THREAD_INFO enabled. This supports the CONFIG_OPENOCD_SUPPORT fallback as well for now.
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