Embedded & C/C++ Ecosystems

The CRA pushes SBOM coverage into embedded firmware, where classic SBOM tools go blind: dependencies live in build-system manifests, not package-manager lockfiles. kunnus carries its own extractors for these formats. Two design rules govern all of them — no network (a declared version or git ref is recorded verbatim; resolving it against a registry is out of scope) and no guessing (if identity fields contain unresolvable variables, the component is dropped rather than invented).

Zephyr (west.yml)

Each manifest project is resolved per west's rules — explicit URL, else remote plus repo-path, with defaults applied. github.com projects become pkg:github components with the pinned revision (tag or SHA) verbatim. Manifest import: resolution, which pulls further manifests from other repositories, is deliberately out of scope.

ESP-IDF (dependencies.lock / idf_component.yml)

The lock file wins: it pins exact versions for direct and transitive components, including the IDF framework itself, and its SHA-256 component hashes land in the SBOM. Manifests under a locked project are skipped to avoid duplicating every component; manifest-only projects fall back to the declared constraints verbatim.

PlatformIO (platformio.ini)

lib_deps entries across all sections are parsed: registry specs keep their declared version or range verbatim; source URLs with a #ref become pkg:github for github.com and pkg:generic otherwise. file:// paths and ${...} interpolations are dropped.

vcpkg (vcpkg.json)

Every dependency becomes a pkg:vcpkg component. Version resolution uses the best offline data in order: an overrides[] pin, else the dependency's own version>= floor, else versionless. builtin-baseline is deliberately ignored — resolving the baseline commit to concrete port versions requires the vcpkg registry, i.e. network access. Feature-conditional dependencies are not walked, since whether a feature is enabled is unknowable from the manifest alone.

CMake declares (FetchContent, ExternalProject, CPM)

Dependencies pinned directly in CMake source — FetchContent_Declare / ExternalProject_Add with a git URL and GIT_TAG, tarball URL with URL_HASH, and CPM.cmake calls — are surfaced. This is a declaration scanner, not a CMake interpreter: any identity field containing an unresolved ${...} variable drops the declare, which doubles as the false-positive control. URL_HASH digests ride into the SBOM as component hashes.

Arduino (library.properties, sketch.yaml)

Vendored libraries are read from their own library.properties (name and version — installed state), and arduino-cli sketch.yaml profiles contribute their pinned libraries and platform cores. A library's depends= field is deliberately not emitted: those unpinned transitive declarations usually duplicate libraries vendored right next to it.

CMSIS-Solution (*.csolution.yml)

solution.packs specs (Vendor::Pack@constraint) become vendor-namespaced components with the constraint kept verbatim. Local path: packs are in-development code and wildcard selections name no single component; both are dropped.

ModusToolbox (*.mtb)

Each one-line .mtb manifest names a GitHub repository and a git ref; the ref becomes the component version, verbatim — refs are tags like release-v6.1.0, not semver. No hashes or licences: a .mtb pins a tag, not a checksum, and resolving either would need the network.

Git submodules (.gitmodules)

Submodule remotes on github.com become pkg:github components; everything else pkg:generic. The pinned commit is decoded from the superproject's .git/index — no submodule checkout and no git subprocess needed. An exported tree without .git yields versionless components.

Conan projects need none of this — the C/C++ ecosystem runs scalibr's conan.lock extractor. And if your stack uses a format kunnus does not cover yet, the repository's adding-an-ecosystem guide shows how to contribute one.