Dependency model
An app declares a short list of modules. It builds a much longer one. This page is about what happens in between.
Three inputs
Section titled “Three inputs”The resolved set comes from three places:
- The board’s baseline. Choosing a board implies a starter profile: the SoC HAL, the
BSP, the CMSIS startup pieces and the runtime core. None of it appears in your
modules:list because you did not choose it.baseline: noneinnsx.ymlopts out entirely. - Your direct declarations. The
modules:list innsx.yml, layered additively on top of the baseline. - What those need. Each module’s own manifest declares
depends.required, and resolution follows those edges until nothing new appears.
Required is resolved, optional is not
Section titled “Required is resolved, optional is not”Module manifests can declare depends.optional as well as depends.required. Only
required dependencies are traversed. Optional ones are documentation: they are searchable,
they appear on the module’s catalog page, and they tell you what a module is designed to
work alongside. Nothing installs them.
This is worth knowing because a module can look like it should have pulled something in and not pulled it in. If you want an optional dependency, declare it yourself.
Once you have, the optional edge does matter for order. NSX adds modules to the CMake build in closure order, and a present optional dependency is placed before the module that lists it, so that module’s CMake can test for the dependency’s target. Required dependencies always come first: if honoring an optional edge would contradict them, that edge is ignored for ordering.
Two limits apply. NSX reads optional dependencies only from modules it has metadata for, so
a local or vendored module’s own optional dependencies do not affect the order (it is still
moved earlier when a registry module lists it as optional). And the order is recorded in
nsx.lock: an app locked by an earlier NSX keeps its old order, and nsx lock --check
does not flag the difference, until you run nsx lock again.
The closure lives only in the lock
Section titled “The closure lives only in the lock”nsx.yml never grows to include transitive dependencies. It stays the list of things you
asked for. The full resolved set is written to nsx.lock, per target board.
That split is what makes the two files readable for different purposes. nsx.yml is a
statement of intent that you maintain and review. nsx.lock is a generated record you
read when you need to know exactly what a binary was built from, and it is exact: project,
kind, the constraint it resolved from, the commit, and a content hash of the tree.
A consequence people hit: nsx module list shows direct dependencies, so a module that is
present in modules/ and being compiled may not appear there. That is the closure, not a
bug.
Resolution is per board
Section titled “Resolution is per board”Each board in targets.supported gets its own resolved section in the lock. Two boards on
different SoC families legitimately resolve different module sets and can even land on
different SDK revisions, because the registry pins an SoC family to the first SDK release
that supports it.
Resolution is therefore also where a portability problem surfaces. Adding a board and
running nsx lock tells you whether that board’s closure can be satisfied, before any
compiler runs.
What NSX rejects
Section titled “What NSX rejects”Four rules are enforced during resolution, so all four fail at lock time rather than at link time:
- No cycles.
Dependency cycle detected at module '<name>'. Break it by moving the shared piece into a third module. - At most one SDK provider. A module can require a specific one through
constraints.required_sdk_provider; a closure with two providers is a conflict. See SDK foundation and overrides. - A board module depends on exactly one SoC module. A board that claims two SoCs is not a board.
- Declared compatibility must hold. A module whose
compatibility.boards,compatibility.socsorcompatibility.toolchainsexcludes your target is rejected rather than built.
When resolution cannot run
Section titled “When resolution cannot run”The manifests that describe the graph live in the module repositories, so resolution needs to reach them:
Unable to resolve dependency metadata for nsx.yml modules [nsx-audio]: ...Run `nsx lock` with the required module sources available so nsx.lock can record thefull dependency closure.In practice that is a network failure, a private repository, or a revision that has gone
away upstream. If a module’s remote is unreachable during a re-lock, NSX records it as
unresolved and keeps its last known content hash rather than dropping it from the
graph.
Where overrides come from
Section titled “Where overrides come from”Two layers of registry exist. NSX ships one, and an app can add its own under
module_registry in nsx.yml. The app’s entries win, and a module-level entry is more
specific than a project-level one.
That precedence is how you test a fork without changing NSX, and it travels with the app: anyone who clones your repository resolves through your overrides too. See Custom modules.