构建独立的 Source SDK 消费工程¶
本教程验证独立 C 和 C++ 工程能够消费搬移后的 Nexus Source SDK。需要 Linux 主机、Python 3.11+、CMake 3.31+、Ninja、C/C++ 编译器和 Git。本教程不刷写或执行 MCU。
先取得固定源码与递归依赖。以下命令从该 checkout 执行,不能使用任意默认分支。
执行维护中的完整消费者¶
从仓库根目录执行,先初始化锁定的递归依赖。输出置于源码 checkout 外。下面这个确切测试调用生产打包器和维护中的消费者模板,将前缀搬移到含空格的路径,配置独立工程,构建两种语言并运行 Native 可执行文件:
python tests/contracts/test_source_sdk.py SourceSDKTests.test_relocated_c_and_cpp_consumer_build_and_run_without_original_source
成功要求非空 unittest 执行以 OK 结束,退出码为零。测试自动清理临时目录。失败保留实际命令诊断;编译器缺失不能记作消费者通过。夹具明确打包开发快照并显式允许消费;这验证消费流程,不建立可发布软件候选。
完整消费者由生产验证器维护,本页不复制实现。以下真实输入就是该测试执行的模板:
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)\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)
(source / "main.cpp").write_text(
'#include "nexus/components/log.h"\n'
'#include "nexus/components/bmp280.h"\n'
'#include "nexus/components/storage.h"\n'
'#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'
' 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
准备可发布的前缀¶
从已初始化、干净且已提交的 checkout 开始,选择自己拥有的新输出路径:
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
消费前搬移整个前缀,包括身份和许可。Source SDK 合同定义版本与接口 epoch 检查。内容文件缺失、额外、修改或符号链接文件会拒绝配置。使用 --development-fixture 生成的包保持不可发布,消费者必须显式设置 NEXUS_ALLOW_SOURCE_SDK_FIXTURE=ON。不得用该选项绕过发布资格。
正式安装前缀验证器除主机工具外,还要求锁定的 ARM 编译器位于 PATH。它构建 Native C/C++ 和维护中的 MCU 消费者:
python cmake/package/verify_consumers.py --prefix /tmp/nexus-source-sdk --output /tmp/nexus-source-sdk-verification
检查 verification.json、每个实际退出码、Native 运行结果、MCU ELF/BIN/map 和资源报告。MCU 链接不执行硬件。验证器拥有其输出目录;已有无关目录会拒绝替换。保留所需证据后,只删除自己创建的输出。
选择产品装配¶
产品在外部仓库维护 assembly.toml、main、执行任务和私有 Board 数据。通过 nexus_add_firmware() 消费 Nexus::Platform;该辅助函数不生成应用或启动调度器。使用装配参考选择复核绑定,通过factory 说明取得接口面。公开产品示例位于 nexus-examples。
FIG-10 表示当前软件消费流程。Runtime 与 MCU 链接证据和物理资格分别记录。