Build an independent Source SDK consumer¶
Start a real external C11 application from a reviewed template, then build and run its Native model. This path needs Linux, Python 3.11+, CMake 3.31+, Ninja, a C compiler and Git. The product owns its assembly and application sources; Nexus owns reusable mechanisms. No MCU is flashed by this tutorial.
Create and run an application¶
Obtain a verified Source SDK and the external
nexus-examples checkout. That
repository is private and requires its own GitHub access; public Nexus or SDK
access does not grant template access. The independent SDK verification below
does not require that private repository. Set
NEXUS_SDK_PREFIX to the installed SDK prefix, NEXUS_EXAMPLES_DIR to that
checkout and NEXUS_EXAMPLES_REVISION to the exact 40-character examples commit
recorded with the chosen platform delivery. Set NEXUS_EXPECTED_REVISION to
the exact 40-character platform commit from that verified delivery. These are
separate identities: the template’s expected platform revision does not prove
which SDK was used. Run these commands from
$NEXUS_SDK_PREFIX/share/nexus/src; choose a new product directory outside both
input checkouts:
: "${NEXUS_EXPECTED_REVISION:?select the reviewed platform commit}"
python cmake/package/package_source_sdk.py \
--verify "$NEXUS_SDK_PREFIX/share/nexus/src"
python tools/dev/dev.py create --templates "$NEXUS_EXAMPLES_DIR" \
--revision "$NEXUS_EXAMPLES_REVISION" --template native \
--output /tmp/my-nexus-product
cmake -S /tmp/my-nexus-product -B /tmp/my-nexus-product-build -G Ninja \
-DNexus_DIR="$NEXUS_SDK_PREFIX/lib/cmake/Nexus" \
-DNEXUS_EXPECTED_SOURCE_REVISION="$NEXUS_EXPECTED_REVISION"
cmake --build /tmp/my-nexus-product-build --parallel 2
ctest --test-dir /tmp/my-nexus-product-build \
--output-on-failure --no-tests=error
Creation reads committed template blobs and their SHA-256 manifest without
network access. It rejects a different Git HEAD, changed template bytes, a
symlink or an existing destination. It resolves the copied assembly before
success; failure removes only its newly created destination. Inspect
.nexus-template.json: expected_platform is the template’s lock expectation;
validation records the resolver and input bytes actually checked.
The generated product contains CMakeLists.txt, assembly.toml, main.c and
README.md. A successful Native test prints
Nexus starter: transfer settled; platform stopped and exits zero. Native UART
is a software model, not a host serial port. The example uses strong factory
references, provides request storage, observes settlement and stops the platform.
A timeout is an error, not permission to release outstanding storage.
For a development checkout, replace both installed-SDK CMake arguments with
-DNEXUS_SOURCE=/absolute/path/to/nexus. This exercises that checkout and
does not qualify an SDK. Select qiming, sky or liangshan for the maintained
MCU template. SDK consumers add
-DCMAKE_TOOLCHAIN_FILE="$NEXUS_SDK_PREFIX/share/nexus/src/cmake/toolchains/arm-gcc.cmake";
checkout consumers instead select
-DCMAKE_TOOLCHAIN_FILE=/absolute/path/to/nexus/cmake/toolchains/arm-gcc.cmake.
Use the locked ARM compiler. An MCU link is software evidence; board bring-up
and electrical verification remain separate.
If creation rejects configuration, read the reported file, key and constraint. Check the selected SDK/interface and fixed template revision first. A template correction requires a newly reviewed commit, not edits to pinned template bytes. After successful creation, edit the product’s authored assembly and configure again; use the configuration and ownership guide to inspect reviewed bindings. Do not repair generated files or suppress a rejected selection. Keep product sources; remove only your own build output after retaining needed logs.
Upgrade an existing product¶
Select a newly qualified Source SDK and its reviewed 40-character platform
commit. Set NEXUS_NEXT_SDK_PREFIX and NEXUS_NEXT_REVISION to those inputs;
use a new build directory outside the product and SDK:
: "${NEXUS_NEXT_SDK_PREFIX:?select the next verified SDK prefix}"
: "${NEXUS_NEXT_REVISION:?select the reviewed next platform commit}"
python "$NEXUS_NEXT_SDK_PREFIX/share/nexus/src/cmake/package/package_source_sdk.py" \
--verify "$NEXUS_NEXT_SDK_PREFIX/share/nexus/src"
cmake -S /tmp/my-nexus-product -B /tmp/my-nexus-product-upgrade-build -G Ninja \
-DNexus_DIR="$NEXUS_NEXT_SDK_PREFIX/lib/cmake/Nexus" \
-DNEXUS_EXPECTED_SOURCE_REVISION="$NEXUS_NEXT_REVISION"
cmake --build /tmp/my-nexus-product-upgrade-build --parallel 2
ctest --test-dir /tmp/my-nexus-product-upgrade-build \
--output-on-failure --no-tests=error
Keep the product’s authored sources and .nexus-template.json; that file records
its template origin, not the current SDK selection. Do not reuse the old CMake
cache across SDK prefixes. Two source revisions may advertise the same semantic
version; the explicit source pin rejects a different revision. A pin mismatch is
a configuration error: preserve its diagnostic and select the reviewed inputs.
Review the product’s find_package() version requirement and changed assembly
constraints; do not automatically loosen that version requirement. Rerun the
product’s required Native tests and MCU links before accepting the upgrade. An older SDK’s results do not qualify
the new SDK, and a successful link does not qualify physical hardware.
Maintainer: verify the complete SDK consumer¶
To verify the examples repository itself, use its maintained
scripts/local_ci.py and bilingual docs/local-ci.md at the fixed examples
revision. The default scope runs the complete mapped local workflow from a new
output directory; --scope native, --scope arm and preset selection remain
partial scopes. Its explicit --nexus-source must match the examples lock.
An optional --installed-sdk must match the same pin. This workflow does not
turn a blocked hosted CI run into a passed one, nor establish physical HIL.
The remaining sections are platform maintenance tasks. First obtain the fixed source and recursive dependencies. Run from that checkout, not an arbitrary default branch. The maintained contract below checks independent C and C++ consumption, relocation and package identity.
Work from the repository root with the pinned recursive dependencies initialized. Keep output outside the source checkout. This exact test calls the production packager and maintained consumer template, moves the prefix into a path containing spaces, configures independent projects, builds both languages and runs the Native executables:
python tests/contracts/test_source_sdk.py SourceSDKTests.test_relocated_c_and_cpp_consumer_build_and_run_without_original_source
Success is a nonempty unittest execution ending in OK, with exit code zero.
The test’s temporary directory is removed automatically. Failure preserves its
actual command diagnostic; do not count a missing compiler as a passed consumer.
The fixture deliberately packages a development snapshot and explicitly opts in;
this validates consumption, not a publishable software candidate.
The complete consumer is maintained in the production verifier rather than copied into this page. These literal inputs are the same templates exercised by the test:
def prepare_consumer(prefix, directory, name, run_checked, *, fixture=False,
arm=False, chip="stm32", expected_revision=None,
version=None, contracts=False):
"""One public consumer template shared by qualification and boundary tests."""
prefix = Path(prefix)
sdk = prefix / "share/nexus/src"
if version is None:
version = load_policy(sdk)["semantic_version"]
source = directory / (name + " source")
build = directory / (name + " build")
source.mkdir()
# A parent Git identity must never become the SDK source identity.
run_checked(["git", "init", "--quiet", str(source)])
(source / "CMakeLists.txt").write_text(
'cmake_minimum_required(VERSION 3.31)\n'
'project(installed_source_consumer LANGUAGES C CXX ASM)\n'
'set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n'
f'find_package(Nexus {version} EXACT CONFIG REQUIRED)\n'
'find_package(Nexus CONFIG REQUIRED)\n'
'add_executable(parent_plain parent.c)\n'
'nexus_add_firmware(installed_c SOURCES main.c)\n'
'nexus_add_firmware(installed_cpp SOURCES main.cpp LIBRARIES '
'Nexus::Log Nexus::BMP280 Nexus::Storage Nexus::MappedStorage Nexus::AMPIPC Nexus::I3CController Nexus::UserIoBroker'
+ (' Nexus::STM32F407Handoff' if arm and chip == "stm32" else '') + ')\n'
'target_compile_options(installed_cpp PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions;-fno-rtti>")\n'
'get_target_property(sdk_root Nexus::Config NEXUS_SOURCE_DIR)\n'
'file(WRITE "${CMAKE_BINARY_DIR}/resolved-sdk.txt" "${sdk_root}")\n')
(source / "parent.c").write_text(
'#ifdef NEXUS_EFFECTIVE_CONFIG\n#error SDK leaked into unrelated parent target\n#endif\n'
'int main(void) { return 0; }\n')
code = ('#include "nexus_bindings.h"\n'
'#include "nexus_factory.h"\n'
'#include "nexus_config.h"\n'
'#ifndef NEXUS_PLATFORM_VERSION\n'
'#error "Missing source interface version"\n'
'#endif\n'
'#if NEXUS_FACTORY_ABI_EPOCH != 1U\n'
'#error "Unsupported factory interface epoch"\n'
'#endif\n'
'#if __has_include("stm32f4xx.h") || __has_include("gd32f4xx.h")\n'
'#error Vendor SDK leaked into product source\n#endif\n'
'#if __has_include("stm32f407_provider.h") || __has_include("gd32f470_provider.h")\n'
'#error Concrete provider leaked into product source\n#endif\n'
'int main(void) {\n'
' if (nx_factory_gpio(NX_GPIO_ID_COUNT) != 0) return 7;\n'
' nx_platform_start_result_t result = nx_platform_start();\n'
' if (result.primary != NX_SUCCESS) return 1;\n'
' return nx_platform_stop() == NX_SUCCESS ? 0 : 2;\n}\n')
(source / "main.c").write_text(code)
handoff = arm and chip == "stm32"
(source / "main.cpp").write_text(
'#include "nexus/components/log.h"\n'
'#include "nexus/components/bmp280.h"\n'
'#include "nexus/components/storage.h"\n'
'#include "nexus/components/mapped_storage.h"\n'
'#include "nexus/components/mapped_storage_owner.h"\n'
'#include "nexus/components/amp_ipc_storage.h"\n'
'#include "nexus/components/i3c_controller_storage.h"\n'
'#include "nexus/components/user_io_async_storage.h"\n'
+ ('#include "nexus/components/stm32f407_handoff.h"\n' if handoff else '')
+ '#include <cstring>\n' + code.replace(
' nx_platform_start_result_t result',
' nx_log_sink_port_t sink;\n std::memset(&sink, 0, sizeof(sink));\n'
' if (nx_log_init(nullptr, sink, NX_LOG_INFO) != NX_ERROR_INVALID) return 3;\n'
' if (nx_bmp280_compensate(nullptr, 0, 0, nullptr) != NX_ERROR_INVALID) return 4;\n'
' if (nx_mapped_storage_stop(nullptr) != NX_ERROR_INVALID) return 5;\n'
' if (nx_amp_ipc_construct(nullptr, nullptr) != NX_ERROR_INVALID) return 6;\n'
' if (nx_i3c_controller_construct(nullptr, nullptr) != NX_ERROR_INVALID) return 9;\n'
' if (nx_user_io_async_construct(nullptr, nullptr, 0U, nullptr) '
'!= NX_ERROR_INVALID) return 10;\n'
+ (' if (nx_stm32f407_handoff_initialize(0) != NX_ERROR_INVALID) return 8;\n'
if handoff else '') + ' nx_platform_start_result_t result'))
arguments = ["cmake", "-S", str(source), "-B", str(build), "-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Release", f"-DNexus_DIR={prefix / 'lib/cmake/Nexus'}",
"-DNEXUS_BUILD_TESTS=OFF", "-DNEXUS_BUILD_WORKLOAD=OFF"]
if fixture:
arguments.append("-DNEXUS_ALLOW_SOURCE_SDK_FIXTURE=ON")
if contracts:
arguments.append("-DNEXUS_BUILD_WORKLOAD=ON")
if expected_revision:
arguments.append("-DNEXUS_EXPECTED_SOURCE_REVISION=" + expected_revision)
arm_assembly = "liangshan-baremetal.toml" if chip == "gd32" else "sky-baremetal.toml"
assembly = sdk / "tools/configure/assemblies" / (arm_assembly if arm else "native.toml")
arguments.append(f"-DNEXUS_ASSEMBLY_FILE={assembly}")
if arm:
arguments.append(f"-DCMAKE_TOOLCHAIN_FILE={sdk / 'cmake/toolchains/arm-gcc.cmake'}")
return source, build, arguments
Maintainer: prepare a publishable prefix¶
From an initialized, clean, committed checkout, choose a new owned output path:
python cmake/package/package_source_sdk.py --source . --output /tmp/nexus-source-sdk
python cmake/package/package_source_sdk.py --verify /tmp/nexus-source-sdk/share/nexus/src
Move the entire prefix, including its identity and licenses, before consumption.
The Source SDK contract defines
version and interface epoch checks. Missing, extra, modified or symlinked payload
files reject configuration. A package made with --development-fixture remains
nonpublishable and requires NEXUS_ALLOW_SOURCE_SDK_FIXTURE=ON in its consumer.
Never use that opt-in to bypass release qualification.
The SDK retains the complete recursive CMSIS-Core Include tree and its
Apache-2.0 LICENSE from the locked official v6.4.0 source. It does not include
the entire CMSIS repository or automatically enable CMSIS-RTOS, CMSIS-Driver,
DSP or NN. These private implementation headers are distinct from the public
Nexus::Core mechanisms. Device/startup and HAL/LL remain separately locked;
older bundled GigaDevice Core files retain their original bytes and notices
without becoming the selected common Core. Verify the complete moved prefix,
not a manually copied subset of headers. This dependency update does not expand
supported chips or replace current-source compiler and consumer evidence.
The formal installed-prefix verifier requires the locked ARM compiler on PATH
in addition to host tools. It builds Native C/C++ and the maintained MCU consumers:
python cmake/package/verify_consumers.py --prefix /tmp/nexus-source-sdk --output /tmp/nexus-source-sdk-verification
Check verification.json, every recorded exit code, Native runtime results, MCU
ELF/BIN/map and resource reports. MCU linking does not execute hardware. The
verifier’s output is owned; an unrelated existing directory rejects replacement.
The SDK prefix and output must be canonical paths without symlink aliases, and
neither may contain the other. A rejected path leaves SDK bytes and previous
reports untouched; read the failure diagnostic on stderr. An admitted owned
output is cleared before the next attempt.
Remove only outputs you created after retaining required evidence.
Choose a product assembly¶
Products keep assembly.toml, main, workers and private Board data in an external
repository. They consume Nexus::Platform through nexus_add_firmware(); the
helper does not generate an application or start a scheduler. Use the
assembly reference to select reviewed
bindings and the factory explanation to obtain faces.
Public product examples live in
nexus-examples.
FIG-10 shows the current software consumption flow. Runtime and MCU link evidence remain separate from physical qualification.
Maintainer: recover delivery without moving an existing tag¶
Release maintainers normally use the same commit for SDK source and the
delivery workflow. If only delivery tooling needs repair after the source has
been tagged, keep that tag fixed. The recover_tagged_source input defaults to
false; only an explicit recovery with publish=true allows the tagged source
and the current main workflow builder to differ. Same-source runs do not
require builder_ci_run_id.
Before recovery, open this repository’s Settings → Secrets and variables →
Actions → New repository secret and create NEXUS_RELEASE_TOKEN. Use an
approved credential restricted to this repository with Actions read, Contents
write and Workflows write permissions. See the
publishing contract
for token types, expiry and approval. Enter the value only in GitHub’s secret
field; never send it in chat. Connecting GitHub to the assistant does not create
this Actions secret.
Recovery rejects a missing secret before SDK work. This check proves only that the secret is configured, not that its permissions, approval or expiry are valid. Only the final recovery publication step uses it; SDK preparation, downloads and attestation retain the built-in workflow credentials. An ordinary publication from one source and builder commit always uses the built-in token.
From the repository root, use an authenticated GitHub CLI with Actions write
permission. Set NEXUS_RELEASE_SOURCE to the tagged 40-character source
commit. Set NEXUS_CI_RUN_ID to its successful full CI run on main and
NEXUS_BUILDER_CI_RUN_ID to the successful full CI run for the current main
builder. Both runs must have been created within the last 24 hours; source CI
cannot qualify a different builder. After each delivery-tool repair, select the
new builder’s CI run, keeping the source commit and tag unchanged. Then dispatch:
gh workflow run release.yml --ref main \
-f expected_commit="$NEXUS_RELEASE_SOURCE" \
-f ci_run_id="$NEXUS_CI_RUN_ID" \
-f builder_ci_run_id="$NEXUS_BUILDER_CI_RUN_ID" \
-f publish=true -f recover_tagged_source=true
The workflow derives the builder from its actual main workflow and checkout.
Recovery requires identical source and builder version-policy bytes, the
existing tag pointing to the source, and the live main reference pointing to
the builder. The SDK, documentation and source CI retain the tagged source
identity; the delivery receipt records the builder separately. Policy
mismatches reject admission, and both references are checked before and after
asset uploads.
Keep every original evidence file when repairing packaging limits. Do not move the tag, omit evidence or substitute a different source CI run to obtain success. A dispatch alone does not establish publication: inspect the completed workflow, release manifest and assets, then independently download and verify the SDK. Recovery does not establish physical HIL or production qualification.