Versioning & Releases¶
heliaCORE uses release-please
to manage versions. Every merge to main triggers .github/workflows/release.yml:
release-please scans conventional-commit subjects since the last tag, decides whether to open a “Release PR” (bumping the version), or — when that PR is itself merged — to create a tag and a GitHub Release.
publish-staticlibs builds static libraries and SDK tarballs for each Cortex-M target across GCC, ATfE, and armclang, then publishes SDK tarballs plus per-toolchain staticlib bundles with SHA-256 checksums.
publish-ci-image builds and pushes
ghcr.io/ambiqai/ns-cmsis-nn-ci:vX.Y.Zso consumers can pin a reproducible build environment.publish-pack generates
Ambiq.NS-CMSIS-NN.<version>.pack, schema-validates it againstPACK.xsd, and uploads it.release-unit-tests and release-helia-core-tester run against the freshly-built artifacts to verify the release actually works before customers see it.
Tag scheme¶
v<MAJOR>.<MINOR>.<PATCH>
MAJORbumps onfeat!:/fix!:/BREAKING CHANGE:footers.MINORbumps on anyfeat:.PATCHbumps onfix:and similar.
Other conventional types (docs:, chore:, refactor:, test:,
ci:, build:, perf:) do not trigger a release on their own,
but their bodies still appear in CHANGELOG.md.
What’s in a release¶
Every GitHub Release contains:
Asset |
Purpose |
|---|---|
|
CMSIS-Pack |
|
SDK tarball (CMake users) |
|
Bare static archives by CPU |
|
SHA-256 of each artifact |
<cpu> is one of cortex-m0, cortex-m4, cortex-m55.
<toolchain> is one of gcc, atfe, armclang.
Note
Cortex-M0/M0+ naming
The release asset name uses cortex-m0 for the baseline ARMv6-M package. Use
that artifact for Cortex-M0/M0+ class Apollo targets.
Required vs optional assets¶
Not every asset is equally load-bearing. If a release is missing something, use this table to know whether that’s a release-blocking bug or an expected gap (see AmbiqAI/ns-cmsis-nn#228):
Asset |
Status |
Why |
|---|---|---|
|
Required |
Created by release-please; the release exists once this lands. |
|
Required |
Only depends on the |
|
Required |
Free toolchain, no license dependency. |
|
Required |
Free toolchain (LLVM-Embedded-Toolchain-for-Arm), no license dependency. |
|
Required |
Same as above. |
|
Required |
Same as above. |
|
Optional |
Promoted to required only when the repository variable |
|
Optional |
Same as above. |
|
Required |
Needed by |
GitHub Pages docs update |
Required |
Rides along with |
Armclang is licensed through Arm’s User-Based Licensing: the
ARM_UBL_LICENSE_IDENTIFIER secret is an activation code, redeemed once per
release run by armlm activate --code in the publish-staticlibs-armclang
job before any compilation happens. That job builds all three Cortex-M
targets on a single runner precisely so the licence is activated once and the
Arm Compiler archive is downloaded once.
The eight armclang assets are optional unless the repository variable
ARMCLANG_REQUIRED is set to true. Set it and release-verify promotes
them, failing the release if they are missing and taking the required bar
from 17 assets to 25; leave it unset and the bar stays at 17.
The gate is deliberately an operator variable rather than an inference from
whether ARM_UBL_LICENSE_IDENTIFIER is configured. That secret was configured
throughout the period in which armclang published nothing at all, so keying
off it would promote assets this pipeline has never once produced — and a
required asset the pipeline cannot produce makes every release red with no
way out, because the recovery such a failure prescribes re-runs the same job.
Turn ARMCLANG_REQUIRED on only after a release run has actually published
the eight, and turn it off to ride out an Arm licensing outage.
Independently of that switch: when no licence is configured,
resolve-release-capabilities reports the capability as unavailable, every
step of publish-staticlibs-armclang self-skips (the job succeeds as a
no-op and self-reports via ::notice::/::warning:: annotations), and the
armclang bundle is omitted from publish-staticlib-bundles. Either way the required gcc/atfe assets are
unaffected: they come from a different job entirely, and
publish-staticlibs-armclang is continue-on-error: true, so armclang can
never hold up the tag, pack, gcc/atfe libs, CI image, or tests.
Before v7.30.0 this gate read ARMLMD_LICENSE_FILE, the legacy FlexLM
licence-file variable, which has never been configured on this repository —
which is why no release from v7.24.1 onward published any armclang asset
(see AmbiqAI/ns-cmsis-nn#275).
Recovering assets for an existing tag¶
release.yml also accepts a manual, idempotent recovery run via
workflow_dispatch with a recover_tag input (e.g. v7.29.2). This
never creates, moves, or re-publishes a tag — it only re-runs the
staticlib/bundle/pack jobs against an already-published release and re-uploads
(gh release upload --clobber) any assets that are missing or need updating:
gh workflow run release.yml --ref main -f recover_tag=v7.29.2
The run fails fast if recover_tag isn’t already a published GitHub Release,
so it can’t be used to sneak out a brand-new tag. See
Contributing → Release recovery for
the full recovery runbook.
Every recovery run — and every normal release run — resolves recover_tag
(or the freshly-cut tag) to its exact target commit once, via the GitHub
API. A recovery dispatch’s own triggering ref (whatever branch was selected
when running the workflow, typically main) is never used to build release
assets; only the commit the tag itself points at is.
Historical recovery restores customer GitHub Release assets only:
static-library archives/bundles and the CMSIS-Pack. It deliberately skips
publish-ci-image, release-unit-tests, and release-helia-core-tester.
The CI image is build infrastructure rather than a release asset, and its
tool acquisition is qualified independently of historical asset recovery.
Skipping it also makes recovery incapable of publishing an old versioned image
or repointing :latest. Normal new releases continue to require the versioned
CI image and both container test suites.
Immutable CI tool acquisition¶
Normal-release CI images install their build and simulation tools from
ci/tools/manifest.json. Each entry records an exact version, vendor HTTPS
URL, SHA-256 digest, extraction rule, executable probe, environment export,
and license classification. scripts/install_ci_tools.sh validates the
manifest, verifies every archive before extraction, and emits
/opt/ns-cmsis-nn/tool-env.json with the resolved paths and provenance.
This repository-owned manifest replaces the retired vcpkg-artifacts/vcpkg-ce
registry. The image build has no package-solver or floating-registry dependency.
Armclang remains installed from Arm’s vendor-hosted archive, but licensed
execution is capability-gated separately through
ARM_UBL_LICENSE_IDENTIFIER.
Updating any tool requires a reviewed manifest version, URL, and digest change
plus a clean image build and version smoke test.
Recovering a genuinely old tag surfaces two more subtleties, both handled
automatically via an explicit two-tree checkout architecture: a
trusted/current tree (this repo’s current main, whatever ref/commit
triggered the run) supplies recovery tooling — helper scripts and the
hardened Dockerfile — while a distinct, immutable/historical tree (pinned
to the resolved historical commit) supplies the only source payload that is
ever built, packaged, or uploaded. The two trees always use different
on-disk paths (never the same checkout directory), so neither can silently
overwrite the other:
CI-image tooling vs. source. The reusable Docker build workflow checks out the repository twice, into two different directories: an unpinned checkout at
_tooling/(whatever ref/commit triggered the run) purely to obtainscripts/ci/resolve_image_tags.sh— a historical tag can predate that helper script entirely — and a second checkout pinned to the resolved historical commit at_source/for the actual Docker build context.docker buildis then invoked as-f _tooling/.devcontainer/Dockerfile _source: the Dockerfile always comes from the current, hardened tree (so a historical, pre-fix Dockerfile bootstrap bug can never resurface), while the build context is always the immutable historical source (so the built image still reflects exactly what that historical tag shipped). Giving each checkout its ownpath:is required, not cosmetic: an earlier revision of this recovery path had both checkouts default to the same directory, so the historical, pinned checkout silently clobbered the current tooling checkout’s files — including the Dockerfile — right before the build ran (AmbiqAI/ns-cmsis-nn#228). Normal push/direct-dispatch/schedule runs are unaffected by this split: with norecover_tag, both_toolingand_sourceresolve to the samegithub.sha, so the two trees are identical content living at two paths, and the build behaves exactly as it always has.Lightweight vs. annotated tags for
gen_pack.sh. Release Please creates lightweight tags, but Open-CMSIS-Pack’sgen-pack(run withPACK_CHANGELOG_MODE=tag) requires an annotated tag with a non-empty message.publish-pack’s only long-lived checkout is pinned directly to the resolved historical commit — that historical tree is the only sourcegen_pack.shever packages. Becausescripts/ci/ensure_local_tag_annotation.shis itself part of the current repository,publish-packalso checks out the current, merged repository into a second, unpinned directory (_tooling/) purely to obtain that helper, then invokes it explicitly against the historical checkout’s working directory (bash _tooling/scripts/ci/ensure_local_tag_annotation.sh "$TAG" "$GITHUB_REPOSITORY" "$GITHUB_WORKSPACE"). The helper — for a lightweight tag only — creates a local-only annotated tag at the exact same commit (message sourced from the existing GitHub Release body, XML-escaped, or a deterministic fallback) sogen_pack.shsucceeds. It never runsgit push; the remote/immutable tag is untouched, already-annotated tags are left completely unaltered, and_tooling/is never read bygen-pack-actionor packaged into the.packoutput — it exists solely to supply the helper script.
See also¶
Maintainer release notes in Contributing explain how to recover when the pipeline fails.
Toolchain identity is recorded in
manifest.jsoninside each tarball. See Toolchain Pinning.