Yocto: why static libraries are a nightmare!
A post-Halloween horror story about my struggles to generate SBOMs and be compliant with open source license attributions requirements.
Yocto: why static libraries are a nightmare!
A post-Halloween horror story about my struggles to generate SBOMs and be compliant with open source license attributions requirements.

Google Gemini generated
Intro
I love working with Yocto! Between the incredible tooling support and the build caching, it is truly a joy to use it to get work done. There is this aura about it that all edge cases have been figured out by wise gray beards. That is, as long as you don’t try to color outside the lines by trying to statically link libraries. Here be dragons!
I’m getting ahead of myself though. Let me back up a bit and give some context. My team and I are currently in the middle of porting a bunch of products and associated Linux platform from a propitiatory closed source build system to Yocto. The said platform is more than a decade old with some apps being old enough to legally drink. We don’t have bandwidth to re-write core apps, so, this is less of a green-field product bring up and more like a build system porting for existing apps and scripts. Thus, we often find ourself making unidiomatic choices in order to have our battle tested code work with minimum modifications.
As part of this porting effort, we wanted to keep open source license attribution (OSLA) and software bill-of-material (SBOM) generation in the forefront of our minds; making sure that the compliance documents that Yocto kindly generates for us are inline with what we expect. It is while doing one such document audit that we realized that some things were off with how Yocto handles libraries.
OSLA and SBOMs
Let me back up again and give you a quick summery of what OSLA and SBOMs are.
Open source license attribution (OSLA): If you use open source software, you must comply with their terms. While different licenses have different terms, most require that you provide proper attribution by listing the open source software you used along with their license types and license texts. For Linux firmware images, we only care about the components that make it to the final binary firmware image. i.e. only the apps and libraries on the firmware; not the host build tools like cross compilers and CMake. OSLA is a legal requirement.
Software bill-of-materials (SBOM): This is an artifact that lists all the components that make up a piece of software like a firmware image. This is akin to the ingredients list in a food product’s nutrients label. Naturally, a SBOM will contain a list of open source components and as such, it can be used to achieve OSLA compliance. But it’s primary purpose is for vulnerability scanning, i.e. CVE detection. SBOMs can get very detailed. For instance it could include checksums of all the components and even the host OS. This is to allow you to audit builds and catch supply chain attacks. There are two common SBOM formats; SPDX and CycloneDX. SPDX is the the more open standard.
Unlike OSLA, there currently is no legal requirement to produce verbose SBOMs, but that may change in the future with government compliance standards like the EU’s Cyber Resilience Acts (CRA) coming into effect in late 2027.
Yocto manifest files and SPDX files
It should go without saying that any build system worth considering should have the ability to automatically generate SBOMs in a way that allows us to be compliant with OSLA requirements. Yocto obviously provides this support. It does so by generate two different sets of documents that can be used for this purpose.
- A collection of “manifest” files
- A collection of SPDX files
The manifest files are not a full SBOM and doesn’t conform to any standard SBOM formats. But they do list the packages, their versions and their licenses in a very human readable way. I’m not a lawyer, but depending on who is asking, it can work as a SBOM. Though, their primary purpose is supposed to be for internal developer auditing. At my day job, we save and generate diffs of manifests files as part of our CI pipelines and automatically post the results to pull requests. This allows reviewers to sign-off on the root filesystem changes caused by the said pull request. Beyond auditing, some CVE vulnerability scanners use these manifest files instead of the more verbose SPDX SBOM files. When you build an image recipe, the manifest for the image will be at build/tmp/deploy/images/<machine>/<image>-<machine>.rootfs.manifest. This is your “pseudo SBOM”. The manifest for license attribution will be at build/tmp/deploy/licenses/<machine>/<image>-<machine>.rootfs/license.manifest
Here are some excerpts:
# rootfs.manifest
base-files cortexa8hf-neon 3.0.14-r0
base-passwd cortexa8hf-neon 3.6.3-r0
busybox cortexa8hf-neon 1.36.1-r0
busybox-hwclock cortexa8hf-neon 1.36.1-r0
busybox-inittab cortexa8hf-neon 1.36.1-r0
ca-certificates all 20211016-r0
chrony cortexa8hf-neon 4.5-r0
cronie cortexa8hf-neon 1.7.2-r0
dhcpcd cortexa8hf-neon 10.0.6-r0
...
# license.manifest
PACKAGE NAME: base-files
PACKAGE VERSION: 3.0.14
RECIPE NAME: base-files
LICENSE: GPL-2.0-only
PACKAGE NAME: base-passwd
PACKAGE VERSION: 3.6.3
RECIPE NAME: base-passwd
LICENSE: GPL-2.0-only
PACKAGE NAME: busybox
PACKAGE VERSION: 1.36.1
RECIPE NAME: busybox
LICENSE: GPL-2.0-only & bzip2-1.0.4
PACKAGE NAME: busybox-hwclock
PACKAGE VERSION: 1.36.1
RECIPE NAME: busybox
LICENSE: GPL-2.0-only & bzip2-1.0.4
...
To generate a proper standards conforming SBOM, you need to add INHERIT += "create-spdx" to one of your global config files, ideally to your distro conf. This will generate a whole heap of SPDX files. I’m not exaggerating; expect Yocto to generate more than 100MB of SPDX files for a simple 20MB root filesystem. The SBOM will be at build/tmp/deploy/images/<machine>/<image>-<machine>.rootf.spdx.tar.zst. Extract this and you’ll find a ton of files including an index.json file which is the root doc. There will also be SPDX files for recipes, packages and the runtime dependencies of said packages. These files are not really meant to be consumed by humans. If you want to attempt to, add SPDX_PRETTY = “1”to make it a bit more readable. Yocto Scarthgap will generate SPDX version 2.2 formatted files, while newer Yocto versions will produce version 3.0 files. I hear that SPDX 3.0 is better at describing build dependencies and also has support to include CVE information, but my hands-on experience is limited to Scarthgap’s 2.2 version format.

https://www.youtube.com/watch?v=rXPpkzdS-q4
Trouble in paradise
Okay, now that we got the primer out of the way, let’s get to the crux of the problem.
[embed]How different types of libraries are handled
Let’s break it down. My expectation is that no matter what type of library I use, it needs to be listed as a line item in the manifest files and also listed appropriately in the the SPDX SBOM. For Dynamic libraries, they are correctly listed in the image manifest and also in the image license manifest. But it is only listed as a build time dependency in the SPDX. That is with something that looks like:
{
"relationshipType": "BUILD_DEPENDENCY_OF",
"spdxElementId": "DocumentRef-runtime-dependency-my-dynamic-library:SPDXRef-Package-my-dynamic-library"
}
In essence, looking at the SPDX SBOM, it is not easy to distinguish between a dynamically linked library vs a build host tool like CMake or a cross compiler. I would have like to have seen the library listed as a ”relationshipType”: “RUNTIME_DEPENDENCY_OF”, which would have made the relationship more clear and explicit. From what I can gather the SPDX generation logic just maps DEPENDS listed in a recipe as BUILD_DEPENDENCY_OF in the SPDX and RDEPENDS as RUNTIME_DEPENDENCY_OF. But the manifest generation logic is immune from this. Even though a dynamic library is only listed in DEPENDS, Yocto knows that libraries need to be copied to the root filesystem and is also able to update the image manifest accordingly. However the SPDX generation logic doesn’t get that memo.
Now before we move on to static libraries, it’s worth mentioning some licensing “complications” when you use static vs dynamic linking with proprietary code.
[embed]License “complications”
With that aside, let’s checkout how static libraries get handled in Yocto manifests and SPDX SBOM files. Just like with dynamic libraries we use DEPENDS to list them in the recipes that use them. But unlike with dynamic libraries where Yocto had to copy over the libraries to the root filesystem and had an account of it to update the manifest; there is no such feature here for static libraries. There is literally no mention of the static libraries in the manifest files. Ouch! The SPDX files are a bit better in some sense in that they list the static libraries with aBUILD_DEPENDENCY_OF relationship. But as mention before, that is not sufficient to distinguish it from host build tools that are exempt from OSLA requirements.
The story is the pretty much the same for static libraries. They are completely MIA from the manifests files. SPDX does a little bit better still here. They will list all the individual header files that got compiled in with a GENERATED_FROM relationship. For example if you use gRPC headers, you’ll see a bunch line like:
{
"comment": "/usr/include/grpc/byte_buffer.h",
"relatedSpdxElement": "NOASSERTION",
"relationshipType": "GENERATED_FROM",
"spdxElementId": "SPDXRef-PackagedFile-my-app"
},
Solutions / Workarounds
Right, so what do we do about these shortcomings? The obvious recommendation is to use dynamical linking exclusively. There is a reason why it is the default. You’ll get accurate manifest files and not many people must have accurate SPDX files right now in 2025. I’d suspect that with advancements in the SPDX format standard, the short comings we mentioned here will get ironed out in the near future.
If you find yourself having to use static linking or header-only libraries (like I do), here are some possible workarounds.
One suggestion I see in forum posts is to add the third party library’s license to the recipe of the app linking it. As in, if your app’s license is LICENSE = “Proprietary” and the library is MIT, update the app toLICENSE = “Proprietary & MIT”. This is to convey that part of your app is under MIT license (the library part) and the rest is proprietary. But this doesn’t add the third party library to the manifests. So, it’s an incomplete solution at best.
What I ended up doing was to add empty packages as RDEPENDS. Hear me out… Lets take the gRPC library as an example. If you build it as a dynamic library, in the package split, the libs will go to both the “grpc” and “grpc-dev” packages. Like so:

gRPC build as a dynamic library
To build with it, you’d add DEPENDS = grpc to your app and call it a day. When you build the same recipe as a static library, “grpc” will be empty and instead the libraries will go to “grpc-staticdev” package. Like so:

gRPC build as a static lib
So, now I can do a double include; i.e. do both DEPENDS = grpc and RDEPENDS = grpc. Since the “grpc” package is empty, nothing actually gets installed to the root filesystem, but you will get it listed in the image and license manifests. Even the SPDX now lists it with a RUNTIME_DEPENDENCY_OF. Win! You many need to add ALLOW_EMPTY:${PN} = “1” to your library’s recipe to get this to work.
Is this hacky as hell? yes, absolutely! But it does tick off the OSLA requirements and your CVE scanner will be now be able to easily detect the static and header-only library. I’ll let you the reader pick your poison.
메타데이터
- post_id
- f0a5db0dc90a
- slug
- yocto-why-static-libraries-are-a-nightmare-f0a5db0dc90a
- url
- https://medium.com/@ruvi-d/yocto-why-static-libraries-are-a-nightmare-f0a5db0dc90a
- canonical_url
- https://medium.com/@ruvi-d/yocto-why-static-libraries-are-a-nightmare-f0a5db0dc90a
- author_url
- https://medium.com/@ruvi-d
- status
- ok
- fetched_at
- 2026-07-15 09:39:26