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:

Maintained Native/F407/F470 consumer preparation; template code is not a shell command.
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.

Source SDK preparation, relocation and independent consumer verification

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.