Skip to content
neuralSPOT-X
User guide
HELIA HUB

Lock and sync

Two commands sit under everything else. nsx lock decides what the app depends on and writes it down. nsx sync makes modules/ match what was written down. Keeping them separate is what lets a build be checked without being changed.

nsx configure runs both for you: it locks if there is no nsx.lock, then syncs. You only reach for them directly when you want one without the other, which is most of the time in CI.

nsx lock walks the graph from the board’s baseline plus your declared modules, resolves every dependency, and records the result per target board in nsx.lock.

Each module gets a project, a kind, the constraint it was resolved from, and a resolved block. For a git-backed module that block carries the URL, the tag, the exact commit and a content hash of the tree at that commit. The lock also records the NSX version that wrote it and a hash of the nsx.yml it read, which is how drift is detected later.

Five kinds appear, and they differ in where the content comes from:

Kind Source of truth Re-fetched by sync
git A repository at a resolved commit Yes
packaged A source tree shipped inside the NSX package Yes
local An external directory you named with source.path Yes, mirrored
vendored modules/<name>/ in your own repository No
unresolved Last known content, when upstream could not be reached No, hash verified

Because resolution happens per board, two targets on different SoC families can legitimately land on different module sets in the same lock file. The full schema is on the generated nsx.lock reference.

By default a module resolves to whatever revision NSX’s registry pins for it. To hold one module somewhere else, give the entry a revision in nsx.yml:

modules:
- name: nsx-audio
revision: v0.4.1

revision accepts a tag, a branch or a commit. NSX resolves it through the remote at lock time and records the commit it landed on, so the lock is exact even when the constraint is not:

  • A commit is already exact. It resolves to itself and never moves.
  • A tag is exact in practice. It resolves to the commit the tag points at, and it is recorded alongside the commit so the lock says which tag you asked for.
  • A branch is a moving constraint. It resolves to the tip at lock time, and re-running nsx lock --update moves it. Between updates the lock still pins a single commit, so the build does not shift underneath you.

Pin a branch only when you are tracking something you also control. nsx outdated is the tool for noticing that a pinned module has fallen behind, rather than a floating ref that moves without telling you.

nsx sync reads nsx.lock and makes modules/ match it. It never writes the lock: if syncing could change what you depend on, the split between the two commands would be pointless.

It is idempotent and cheap on the second run, because it hashes what is already on disk and skips anything that matches. --force re-vendors every fetchable module regardless.

Three flags turn this pair into a build check.

Terminal window
nsx lock --check # report manifest and resolution drift, write nothing
nsx sync --frozen # fail if modules/ differs from the lock; fetch only what is missing
nsx outdated --exit-code # fail if a git module lags its upstream

nsx lock --check is read-only. It prints what would change and exits non-zero if anything would, which catches an nsx.yml edit that was never locked.

nsx sync --frozen refuses to correct. Every form of drift becomes an error naming the module:

Vendored module 'my-sensor' content drifted from lock ...
Local source for 'my-sensor' at /home/you/src/my-sensor has drifted ...
Module 'my-sensor' at modules/my-sensor does not match nsx.lock ...

A module that is missing, as on a fresh clone where modules/ is gitignored, is not drift. --frozen fetches it at the commit in nsx.lock (git modules need network access) and fails if its content hash differs from the lock. The same holds for the cmake/nsx tree that ships with neuralspotx: after upgrading neuralspotx, run nsx lock once before using --frozen.

--frozen is also accepted by configure, build, flash and view, where it applies to the implicit sync those commands would otherwise perform.

Terminal window
nsx outdated # what has moved upstream
nsx lock --update # re-resolve everything to upstream tip
nsx lock --update --module nsx-audio # re-resolve one module
nsx update # re-resolve and sync in one step

nsx outdated only reports on git-backed modules, because a packaged or vendored module has no upstream tip to compare against. It tells you which modules it skipped and why, and --json gives checked, skipped and outdated_count for a script.

nsx update is lock --update followed by sync. It prompts before changing the lock unless you pass --yes.

Review the diff to nsx.lock before committing an update. It is the only record of what your firmware is built from.

If a module’s remote cannot be reached during a lock, NSX records the module as unresolved and keeps the last known content hash rather than dropping it. The app still builds from what is on disk, and the lock says plainly that the entry was not confirmed against upstream. Re-run nsx lock with network access to resolve it properly.