blob: ca5314ac05c0c413642c79cd90eb5bd3df77ea9a [file]
.. _module-pw_build_info:
=============
pw_build_info
=============
.. pigweed-module::
:name: pw_build_info
pw_build_info provides tooling, build integration, and libraries for generating,
embedding, and parsing build-related information that is embedded into
binaries. Simple numeric version numbering doesn't typically express things
like where the binary originated, what devices it's compatible with, whether
local changes were present when the binary was built, and more. pw_build_info
simplifies the process of integrating rich version metadata to answer more
complex questions about compiled binaries.
.. _module-pw_build_info-gnu-build-ids:
-------------
GNU build IDs
-------------
This module provides C++ and python libraries for reading GNU build IDs
generated by the link step of a C++ executable. These build IDs are essentially
hashes of the final linked binary, meaning two identical binaries will have
identical build IDs. This can be used to accurately identify matching
binaries.
Linux executables that depend on the ``build_id`` GN target will automatically
generate GNU build IDs. Windows and macOS binaries cannot use this target as
the implementation of GNU build IDs depends on the ELF file format.
A separate GN target ``build_id_or_noop`` is available which provides an empty
build ID on platforms where GNU build ID is not available while still providing
a real GNU build ID where supported.
Getting started
===============
To generate GNU build IDs as part of your firmware image, you'll need to update
your embedded target's linker script.
Updating your linker script
---------------------------
If your project has a custom linker script, you'll need to update it to include
a section to contain the generated build ID. This section should be placed
alongside the ``.text`` and ``.rodata`` sections, and named
``.note.gnu.build-id``.
.. code-block:: none
/* Main executable code. */
.code : ALIGN(4)
{
. = ALIGN(4);
/* Application code. */
*(.text)
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
...
} >FLASH
/* GNU build ID section. */
.note.gnu.build-id :
{
. = ALIGN(4);
gnu_build_id_begin = .;
*(.note.gnu.build-id);
} >FLASH
/* Explicitly initialized global and static data. (.data) */
.static_init_ram : ALIGN(4)
{
*(.data)
*(.data*)
...
} >RAM AT> FLASH
Alternatively, you can copy the following linker snippet into a pre-existing
section. This makes reading the build ID slower, so whenever possible prefer
creating a dedicated section for the build ID.
.. literalinclude:: build_id_linker_snippet.ld
An example of directly inserting a build ID into an existing section is
provided below:
.. code-block:: none
/* Main executable code. */
.code : ALIGN(4)
{
. = ALIGN(4);
/* Application code. */
*(.text)
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
gnu_build_id_begin = .;
*(.note.gnu.build-id);
...
} >FLASH
If your linker script is auto-generated, you may be able to use the
``INSERT AFTER`` linker script directive to append the build ID as seen in the
Linux host support for pw_build_info's build ID integration:
.. literalinclude:: add_build_id_to_default_linker_script.ld
Generating the build ID
-----------------------
When you depend on ``"$dir_pw_build_info:build_id``, a GNU build ID will be
generated at the final link step of any binaries that depend on that library
(whether directly or transitively). Those binaries will be able to read the
build ID by calling ``pw::build_info::BuildId()``. Note that the build ID
is not a string, but raw binary data, so to print it you'll need to convert
it to hex or base64. It is possible to call ``pw::build_info::LogBuildId()``
function to print it (as hexadecimal).
Python API reference
====================
.. py:function:: read_build_id_from_section(elf_file: BinaryIO) -> \
bytes | None
Reads a GNU build ID from an ELF binary by searching for a
``.note.gnu.build-id`` section.
.. py:function:: read_build_id_from_symbol(elf_file: BinaryIO) -> \
bytes | None
Reads a GNU build ID from an ELF binary by searching for a
``gnu_build_id_begin`` symbol. This can be a rather slow operation.
.. py:function:: read_build_id(elf_file: BinaryIO) -> bytes | None
Reads a GNU build ID from an ELF binary, first checking for a GNU build ID
section and then falling back to search for a ``gnu_build_id_begin`` symbol.
.. py:function:: find_matching_elf(uuid: bytes, search_dir: Path) -> \
Path | None
Recursively searches a directory for an ELF file with a matching UUID.
Warning: This can take on the order of several seconds.
Python utility
==============
GNU build IDs can be parsed out of ELF files using the ``build_id`` python tool.
Simply point the tool to a binary with a GNU build ID and the build ID will be
printed out if it is found.
.. code-block:: sh
$ python -m pw_build_info.build_id my_device_image.elf
d43cce74f18522052f77a1fa3fb7a25fe33f40dd
------------------------------
Bazel Workspace Status Command
------------------------------
Bazel supports running a single command to inspect the current workspace status
each build. This is configured via the ``--workspace_status_command`` flag,
typically in ``.bazelrc``, e.g.:
.. code-block::
build --workspace_status_command=path/to/pigweed/pw_build_info/git_workspace_status_command.sh
More info about `bazel workspace status commands <https://bazel.build/docs/user-manual#workspace-status-command>`__.
pw_build_info provides a script and bazel tool for substituting variables from
the workspace_status_command into source file templates. See
``substitute_workspace_status.bzl``.
Auto-generated headers in this module leverage this facility to expose
workspace status information to C++.
Downstream projects may wish to provide their own workspace status script which
invokes Pigweed-provided workspace status scripts, e.g.:
.. code-block:: sh
#!/bin/bash
pigweed/pw_build_info/git_workspace_status_command.sh || exit $?
echo "STABLE_CUSTOM_ATTRIBUTE pizza" || exit $?
------------
Build Origin
------------
You can use the ``//pw_build_info:build_origin`` header to embed a "build
origin" into your firmware.
The build origin is a string identifying the unique "build" from which this
firmware originates. This will typically refer to a CI/CD build or job
identifier.
The recommended format is ``<build_system>/<unique_id>`` but the exact format
of this string is up to the user. Examples:
* GitHub Actions: ``github/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}``
* GitLab CI/CD: ``gitlab/${CI_PIPELINE_ID}``
* Jenkins: ``jenkins/${BUILD_NUMBER}``
* LUCI: ``luci/<build-id>``
Local builds can leave this field empty or specify ``local``.
Projects using Pigweed's LUCI build system should use the LUCI format specified
above.
This is only supported in Bazel.
Downstream projects must use a Bazel workspace_status_command to set the
``STABLE_PW_BUILD_INFO_BUILD_ORIGIN`` key.
This is intended for use in the ``pw.snapshot.Metadata.build_origin`` proto field.
---------------
Git Commit Info
---------------
If your project uses ``git`` and builds with ``bazel``, you can use the
auto-generated ``//pw_build_info:git_build_info`` header to embed which git
commit your binary was built from. This requires a bit of setup to use.
Bazel Workspace Status Command
==============================
If you are using no other ``workspace_status_command``, simply add the
following to your ``.bazelrc,`` replacing the ``path/to/pigweed`` portion with
where you have pigweed checked out.
.. code-block::
build --workspace_status_command=path/to/pigweed/pw_build_info/git_workspace_status_command.sh
Otherwise, *invoke* this script from your own.
Use ``pw_build_info/git_build_info.h`` Header
=============================================
Add a dependency on ``//pw_build_info:git_build_info``:
.. code-block:: python
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = [
"@pigweed//pw_build_info:git_build_info",
"//pw_log",
],
)
Include the header. The following constants are available:
* ``pw::InlineBasicString pw::build_info::kGitCommit``: The git commit this
binary was built from. Includes ``-dirty`` suffix if ``kGitTreeDirty`` is
true.
* ``bool pw::build_info::kGitTreeDirty``: True if there were any uncommitted
changes.
.. code-block:: cpp
#include "pw_build_info/git_build_info.h"
#include "pw_log/log.h"
#include "pw_string/string.h"
int main() {
PW_LOG_INFO("kGitCommit %s", pw::build_info::kGitCommit.c_str());
PW_LOG_INFO("kGitTreeDirty %d", pw::build_info::kGitTreeDirty);
return 0;
}