Dependencies
This plugin fetches, builds, and links the libraries and modules your project depends on. It’s rare for an embedded project to be built completely free of other code. Some of it may be standard internal libraries, and some may be third-party libraries. Either way, it becomes part of the project’s ecosystem.
For each dependency, you tell this plugin where to fetch it from, how to build it, and what it produces. Ceedling fetches and builds it whenever those outputs are missing, then adds its headers, sources, and libraries to your test and release builds.
Most third-party libraries already have a build process of their own. Make? CMake? A custom series of scripts that only a mad scientist could possibly understand? No matter. You list the commands, and Ceedling runs them.
See the Cookbook section for real-world scenarios along with the example
:dependencies configurations and ceedling dependencies:... command lines to make
use of this plugin.
So how does all this magic work?
Enable the plugin in your project configuration, then list your dependencies under
:dependencies ↳ :deps. Each dependency has a series of fields, most of them optional.
If you don’t need a feature, leave its field out. In the end, it looks something like
this:
:plugins:
:enabled:
- dependencies
:dependencies:
:deps:
- :name: WolfSSL
:paths:
:fetch: third_party/wolfssl/source
:source: third_party/wolfssl/source
:build: third_party/wolfssl/build
:artifact: third_party/wolfssl/install
:fetch:
:method: :zip
:source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip
:environment:
- CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE
:build:
- autoreconf -i
- ./configure --enable-tls13 --enable-singlethreaded
- make
- make install
:artifacts:
:static_libraries:
- lib/wolfssl.a
:dynamic_libraries:
- lib/wolfssl.so
:includes:
- include/**
Name
As a YAML array of objects, each dependency begins with a dash, followed by its fields.
By convention, :name comes first, which makes each dependency easy to spot.
The name appears in Ceedling’s progress messages. Its letters, digits, and underscores
also name the dependency’s own tasks, so Worker Bees gets dependencies:make:WorkerBees.
A name therefore needs at least one letter or digit. Two dependencies whose names reduce
to the same task name stop the build with an error.
Working paths
A dependency’s paths are collected under its :paths. Each path you leave out falls back
to another, and with none configured every path is dependencies/<task name>.
| Path | Purpose | When left out |
|---|---|---|
:fetch |
Where the dependency is fetched to | :source |
:source |
Where build commands run from | :fetch |
:build |
Where the build places its files | :source, then :fetch |
:artifact |
Where the outputs listed under :artifacts are found |
:build |
:deploy |
Where dependencies:deploy copies dynamic libraries and executables |
Your release executable’s folder |
Cleaning a dependency never removes its :deploy path, which holds copies alongside your
project’s own files.
If the dependency is part of your repository (its fetch :method is :none), :source
is where its code already lives. Paths may lie outside your project, such as
../shared/lib, and headers found there work in test and release builds alike.
Every artifact is relative to the :artifact path. With several include directories,
choose the highest level and list the rest relative to it.
Fetching dependencies
The plugin fetches a dependency with one of several common methods, configured under its
:fetch:
:method, the way this dependency is fetched::none: the code is already part of the project, so nothing is fetched.:zip: unpack a zip archive into the fetch path.:tar_gzip: unpack a gzipped tar archive into the fetch path.:git: clone a git repository into the fetch path.:svn: check out a Subversion repository into the fetch path.:custom: run commands of your own in the fetch path.
:source: the archive or repository to fetch from (:zip,:tar_gzip,:git, and:svn, all of which require it).:tagor:branch: the tag or branch to clone (:gitonly, optional). A tag wins when both are given.:hash: the commit to check out (:gitonly, optional). It makes a full clone rather than a shallow one, so the commit is available.:revision: the revision to check out (:svnonly, optional).:executable: the list of commands to run (:customonly, required).
Fetching runs from inside the fetch path, so an archive :source given as a relative
path is relative to the fetch path.
A dependency is fetched once. Once its fetch path holds files, fetching it again, or
building it after a fetch, leaves those files alone. To fetch a fresh copy, clean the
dependency first with dependencies:clean.
The :zip, :tar_gzip, :git, and :svn methods use the plugin’s
tools (unzip, tar, git, and svn). Each is checked just before a
dependency fetches with it, so a missing tool stops only that fetch, and a machine that
never fetches, such as a CI agent working from fetched dependencies, needs none of them.
Environment variables
Many build systems take settings from environment variables. A dependency’s
:environment sets variables while that dependency’s fetch and build commands run, then
restores them, so they never reach another dependency or Ceedling itself.
Ceedling’s project configuration also has a top-level :environment section, which
applies to all of Ceedling. The two sections differ in format and abilities.
Each entry sets a variable, adds to it, or removes from it:
:dependencies:
:deps:
- :name: Engine
:environment:
- ARCHITECTURE=ARM9
- CFLAGS+= -DADD_AWESOMENESS
- CFLAGS-= -DWASTE
ARCHITECTURE=ARM9 sets ARCHITECTURE to ARM9. += appends to a variable, here
adding -DADD_AWESOMENESS to CFLAGS, and -= removes text from it, here removing
-DWASTE. An entry in any other form stops the build with an error.
When appending, Ceedling cannot know which delimiter you expect, so include it yourself:
a space for compiler flags, or : (; on Windows) for PATH. Text after += and -=
is used exactly as written, leading space included, while a value after = loses its
leading spaces. Removing text is case
sensitive and removes only an exact match. Appending to or removing from a variable that
is not set leaves it set, empty if nothing was appended.
Building dependencies
The heart of the plugin is the build process you specify for each dependency under
:build, a list of steps Ceedling runs in order. Any tools those steps use must be
installed. A failing step stops the build with its error.
Each step is a command line, which runs from the dependency’s :source path exactly as
written, quoting and spacing included. The source, build, and artifact paths are created
first if they do not exist. A step can instead be :build_lib, which builds a static
library with Ceedling’s own compiler and archiver (see Custom tools).
Artifacts
Artifacts are the outputs of the build process, listed under :artifacts. A dependency
may have any of them or none. Your dependency’s build process may produce many other
files, but these are the ones Ceedling acts on.
static_libraries
Static libraries Ceedling links into your test and release builds. If one does not exist, Ceedling fetches and builds the dependency to produce it.
dynamic_libraries
Dynamic libraries Ceedling links into your test and release builds. If one does not
exist, Ceedling fetches and builds the dependency to produce it. dependencies:deploy
copies them to the :deploy path, by default beside your release
executable, where it loads them at run time.
executables
Programs a dependency builds, such as a code generator, a flashing utility, or a tool
your release ships alongside it. Ceedling never links them. A release build, or
dependencies:make, fetches and builds the dependency when one does not exist, while
test builds do not wait on them. dependencies:deploy copies them, with any dynamic
libraries, to the :deploy path.
On Windows, list each executable with its .exe extension.
includes
The public headers a dependency provides. Ceedling adds their directories to its search paths, so your code can include them and your tests can mock them. An entry may be:
- a header file, such as
include/wolfssl.h, which adds that file’s directory; - a directory, such as
include; - a glob, such as
include/**, which adds every directory it finds.
source
Directories of C files a dependency produces, such as generated code. Ceedling adds the directories to its search paths and their C files to your release build.
Tasks
Once configured, the plugin works on its own: a release or test build fetches and builds
any dependency whose outputs are missing. These tasks let you act on dependencies
directly. Each also works on a single dependency by its task name, such as
dependencies:make:WorkerBees.
ceedling dependencies:fetch
Fetches each dependency that is not fetched yet. Dependencies without fetch settings, or
fetched by :none, are left alone.
ceedling dependencies:make
Fetches each dependency if needed, then builds any whose outputs are missing. This happens automatically in a release build, but is handy while you are configuring a dependency.
ceedling dependencies:clean
Removes a fetched dependency entirely, every one of its working paths, so the next build
fetches and builds it from scratch. A dependency fetched by :none keeps its source and
loses only its artifact path. A dependency without fetch settings is left alone.
ceedling dependencies:deploy
Copies every dynamic library and executable of each dependency to its
:deploy path, by default beside your release executable. An artifact
that has not been built is built first.
files:dependencies
Lists every static library, dynamic library, and executable your dependencies provide.
paths:include
Lists every header search path Ceedling uses, including those from dependencies.
files:include
Lists every header file Ceedling has found, including those from dependencies.
Custom tools
The plugin’s tools are configured like any other Ceedling tool, and you can replace them
under :tools:
| Tool | Default | Used for |
|---|---|---|
:deps_compiler |
gcc |
:build_lib compiling |
:deps_assembler |
none | :build_lib assembling, needed only for assembly files |
:deps_linker |
ar |
:build_lib archiving |
:deps_zip |
unzip |
:zip fetching |
:deps_targzip |
tar |
:tar_gzip fetching |
:deps_git_clone, :deps_git_checkout |
git |
:git fetching |
:deps_subversion |
svn |
:svn fetching |
The compiler, assembler, and archiver are checked just before :build_lib uses them, so
a project without :build_lib needs none of them.
:build_lib compiles every C file anywhere under the dependency’s :source path, with
every directory holding a header there as a search path. It then archives the objects
into the first library under :artifacts ↳ :static_libraries, built in the :build
path and copied to the :artifact path when the two differ. A dependency’s own
:defines and :flags apply to its compilation, and :libraries and :libpaths to its
archiving.
The compiler receives its search paths as ${5} and its defines as ${6}, as Ceedling’s
own compilers do:
:tools:
:deps_compiler:
:executable: gcc
:arguments:
- -g
- -I"${5}"
- -D${6}
- -c "${1}"
- -o "${2}"
:deps_linker:
:executable: ar
:arguments:
- rcs
- ${2}
- ${1}
A dependency built with :build_lib looks like this:
:dependencies:
:deps:
- :name: CaptainCrunch
:paths:
:source: ../cc/
:build: ../cc/build
:artifact: ../cc/build
:fetch:
:method: :none
:build:
- :build_lib
:artifacts:
:static_libraries:
- release/cc.a
:includes:
- ./cc.h
:defines:
- THESE_GET_USED_DURING_COMPILATION
Cookbook
Each recipe below pairs a dependency’s configuration with the commands that work with
it. Any of them can sit alongside the others in one :deps list.
A tagged release from git, built with Make
Your drone flight controller firmware uses an open-source sensor fusion library, which blends accelerometer and gyroscope readings into an orientation. Its maintainers publish tagged releases on GitHub and build it with a makefile.
Pin a library to a release tag so every developer and CI run builds the same code. The clone is shallow, which keeps it quick.
:dependencies:
:deps:
- :name: Sensor Fusion
:paths:
:fetch: third_party/fusion
:artifact: third_party/fusion/out
:fetch:
:method: :git
:source: https://github.com/example/sensor-fusion.git
:tag: v2.4.1
:build:
- make lib OUT=out
:artifacts:
:static_libraries:
- libfusion.a
:includes:
- ../include/**
ceedling dependencies:make # Clone v2.4.1 if needed, then build libfusion.a
ceedling release # Link it into the release build
To move to a new tag, change :tag, then fetch a fresh copy, since a fetched dependency
is not fetched again:
A vendor archive with environment settings
Your chip vendor ships its real-time operating system as a zip archive, which your team keeps on a shared file server. It builds with an autotools configure script, which must be told to cross-compile for your ARM Cortex-M4 target.
Unpack a zip from a shared location and build it with the vendor’s own configure script, passing settings through the environment of that build alone.
:dependencies:
:deps:
- :name: VendorRTOS
:paths:
:fetch: third_party/rtos/source
:build: third_party/rtos/source/build
:artifact: third_party/rtos/install
:fetch:
:method: :zip
:source: /mnt/vendor/rtos-5.2.zip
:environment:
- CC=arm-none-eabi-gcc
- CFLAGS+= -mcpu=cortex-m4
:build:
- ./configure --prefix=../install
- make install
:artifacts:
:static_libraries:
- lib/librtos.a
:includes:
- include
ceedling dependencies:fetch:VendorRTOS # Unpack the archive
ceedling dependencies:make:VendorRTOS # Configure and build with CC and CFLAGS set
A sibling Ceedling project
Your repository holds two Ceedling projects side by side: the main application and a supervisor module that watches over it. The supervisor is developed and unit tested in its own project, and the application links it as a library.
Build a library from another Ceedling project in the same repository, using that project’s own release build. It is never fetched, so cleaning removes only its build outputs.
:dependencies:
:deps:
- :name: Supervisor
:paths:
:source: ../supervisor
:build: ../supervisor/build
:artifact: ../supervisor/build/release
:fetch:
:method: :none
:build:
- ceedling clobber release
:artifacts:
:static_libraries:
- libsupervisor.a
:includes:
- ../../src/supervisor.h
ceedling dependencies:make:Supervisor # Run the sibling project’s release build
ceedling test:all # Test against its library and header
ceedling dependencies:clean:Supervisor # Remove only its release outputs
A shared library deployed beside the release
Your Linux-based product decodes audio with a third-party codec, built as a shared
library the application loads when it starts. You keep the codec’s source release in the
project’s downloads folder.
Build a dynamic library and copy it beside the release executable, which loads it at run time.
:dependencies:
:deps:
- :name: Codec
:paths:
:fetch: third_party/codec
:artifact: third_party/codec/lib
:fetch:
:method: :tar_gzip
:source: ../../downloads/codec-1.8.tar.gz
:build:
- make shared
:artifacts:
:dynamic_libraries:
- libcodec.so
:includes:
- ../include
The archive’s relative :source resolves from inside the fetch path, which is why it
climbs two folders.
Build tools deployed to a shared folder
packer is an in-house utility your tools team keeps in its own git repository. It
bundles a compiled firmware image with its version metadata into a signed update file,
and its companion packer-verify checks such a file. Your release scripts run both from
the project’s tools/bin folder, so every developer and CI agent needs them built from
the same tagged version and installed there.
Build tools from source and install them where your build scripts or CI agents expect them. Ceedling builds them for a release, never links them, and never cleans the deploy folder.
:dependencies:
:deps:
- :name: Packer
:paths:
:fetch: third_party/packer
:build: third_party/packer/build
:deploy: tools/bin
:fetch:
:method: :git
:source: https://github.com/example/firmware-packer.git
:tag: v1.3.0
:build:
- make all BUILD=build
:artifacts:
:executables:
- packer
- packer-verify
A small library compiled by Ceedling
A colleague’s CRC routines live in your company’s Subversion repository: a C file and a header, with no makefile and no library of their own. Your firmware checks its data packets with them.
Pin a few C files from Subversion and let :build_lib compile and archive them, with no
build system of their own.
:dependencies:
:deps:
- :name: CRC Utils
:paths:
:fetch: third_party/crc
:build: third_party/crc/build
:fetch:
:method: :svn
:source: https://svn.example.com/repos/crc/trunk
:revision: '1287'
:build:
- :build_lib
:artifacts:
:static_libraries:
- libcrc.a
:includes:
- ../crc.h
:defines:
- CRC_TABLE_IN_ROM
ceedling dependencies:make:CRCUtils # Check out revision 1287, then compile and archive
ceedling files:dependencies # Confirm third_party/crc/build/libcrc.a is listed