2023 Feb 7

Automating in-tree dependencies with Meson

System-installed libraries don’t make sense in a microcontroller project, which naturally wants all code to be in the project tree. Vendoring1 creates the tree by hand, offering cumbersome ways to specify the version of a dependency and to update the tree when it changes. This habit may struggle to keep a pace of development that supports software over a long lifecycle.

Git submodules simplify specifying a version and updating the tree automatically. However, their reputation for fragility has been earned fairly.

Meson wraps provide a workable alternative to Git submodules. While both support specifying a commit for a dependency, an important step toward a reproducible build2, a wrap can also specify the version of a dependency whose origin isn’t a Git repository.

A wrap file subprojects/freertos.wrap specifies a dependency:

[wrap-git]
url = https://github.com/FreeRTOS/FreeRTOS-Kernel
revision = V10.5.1
depth = 1

This compares favorably with adding a Git submodule:

git submodule add https://github.com/FreeRTOS/FreeRTOS-Kernel freertos
cd freertos
git checkout V10.5.1
cd ..
git commit -m 'Add FreeRTOS V10.5.1'

which integrates the source tree but not the build system.

A wrap reduces maintenance burden through declarative syntax and ephemeral state. The dependency is checked out not upon clone, but when you tell Meson to set up a new build directory. Having the build system own the dependency frees the version control system to focus on its own content.

Wraps interoperate with other projects using Meson or CMake. A microcontroller project integrates FreeRTOS by specifying the path of FreeRTOSConfig.h and the name of the subproject as indicated by the wrap file. There are a few CMake options to set.

cmake = import('cmake')
freertos_options = cmake.subproject_options()

freertos_options.append_compile_args(
    'c',
    '-I' + meson.global_source_root() / 'include',
)

freertos_options.add_cmake_defines({
    'CMAKE_TRY_COMPILE_TARGET_TYPE': 'STATIC_LIBRARY',
    'FREERTOS_CONFIG_FILE_DIRECTORY': 'include',
    'FREERTOS_PORT': 'GCC_ARM_CM4F',
})

executable(
    'firmware',
    'main.c',
    dependencies: cmake.subproject(
        'freertos',
        options: freertos_options,
    ).dependency('freertos_kernel'),
)

Meson’s easy syntax and immutable data structures enable simpler engineering of build systems. Wraps help projects taking advantage of C to move through the lifecycle gracefully.

A caveat of wraps is the lack of support for legacy build systems. But porting a build system to Meson can be its own reward.


  1. I enjoyed Tom MacWright’s comparison of a range of well-known dependency strategies. ↩︎

  2. A reproducible build satisfies the criteria of a pure mathematical function: if the inputs haven’t changed, then the output must be the same. Automation aids reproducibility and quick incorporation of newcomers to the project. ↩︎


Feedback

Discuss this page by emailing my public inbox. Please note the etiquette guidelines.

© 2024 Karl Schultheisz