From 3cfc9981535193966f6b9dff7fcafb6f7d175aa4 Mon Sep 17 00:00:00 2001 From: Jax Calder Date: Sun, 4 Oct 2026 14:50:16 +0100 Subject: [PATCH 1/2] Add dmod-dev SDK package with CMake package discovery --- .github/workflows/debian-package.yml | 23 ++++-- CMakeLists.txt | 4 + README.md | 5 ++ cmake/dmodConfig.cmake.in | 41 ++++++++++ cmake/runtime/CMakeLists.txt | 25 ++++++ docs/debian-package.md | 7 +- docs/development-package.md | 76 +++++++++++++++++++ scripts/CMakeLists.txt | 7 +- scripts/debian-package.cmake | 22 ++++-- scripts/install-sdk.cmake | 43 +++++++++++ tests/integration/sdk/CMakeLists.txt | 16 ++++ tests/integration/sdk/arm-toolchain.cmake | 9 +++ tests/integration/sdk/external/CMakeLists.txt | 6 ++ tests/integration/sdk/include/library.h | 5 ++ tests/integration/sdk/library.c | 20 +++++ tests/integration/sdk/main.c | 13 ++++ tests/integration/sdk/ordinary.c | 7 ++ tests/integration/sdk/run.sh | 50 ++++++++++++ tests/integration/sdk/sdk_math.dmr | 3 + tests/integration/sdk/serve.py | 9 +++ tests/integration/sdk/test.c | 6 ++ tests/integration/test_development_package.sh | 23 ++++++ tests/integration/test_installed_sdk.sh | 24 ++++++ 23 files changed, 429 insertions(+), 15 deletions(-) create mode 100644 cmake/dmodConfig.cmake.in create mode 100644 cmake/runtime/CMakeLists.txt create mode 100644 docs/development-package.md create mode 100644 scripts/install-sdk.cmake create mode 100644 tests/integration/sdk/CMakeLists.txt create mode 100644 tests/integration/sdk/arm-toolchain.cmake create mode 100644 tests/integration/sdk/external/CMakeLists.txt create mode 100644 tests/integration/sdk/include/library.h create mode 100644 tests/integration/sdk/library.c create mode 100644 tests/integration/sdk/main.c create mode 100644 tests/integration/sdk/ordinary.c create mode 100644 tests/integration/sdk/run.sh create mode 100644 tests/integration/sdk/sdk_math.dmr create mode 100644 tests/integration/sdk/serve.py create mode 100644 tests/integration/sdk/test.c create mode 100644 tests/integration/test_development_package.sh create mode 100644 tests/integration/test_installed_sdk.sh diff --git a/.github/workflows/debian-package.yml b/.github/workflows/debian-package.yml index 941fad3..b89569a 100644 --- a/.github/workflows/debian-package.yml +++ b/.github/workflows/debian-package.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install build dependencies - run: sudo apt-get update && sudo apt-get install -y build-essential cmake dpkg-dev libcurl4-openssl-dev unzip + run: sudo apt-get update && sudo apt-get install -y build-essential cmake dpkg-dev libcurl4-openssl-dev unzip bubblewrap python3 - name: Bootstrap host tools run: | cmake -S . -B build-deb -DDMOD_TOOLS_NAME=arch/x86_64 \ @@ -36,15 +36,26 @@ jobs: -DDMOD_BUILD_TESTS=OFF -DDMOD_BUILD_TOOLS=OFF -DDMOD_BUILD_TEMPLATES=OFF cmake --build build-modules --target example_app dmodex --parallel 2 - name: Validate package - run: bash tests/integration/test_debian_package.sh build-deb/dmod_*.deb build-modules/dmf/example_app.dmf - - name: Install and remove package run: | - sudo apt-get install -y ./build-deb/dmod_*.deb + bash tests/integration/test_debian_package.sh build-deb/dmod_*.deb build-modules/dmf/example_app.dmf + bash tests/integration/test_development_package.sh build-deb/dmod_*.deb build-deb/dmod-dev_*.deb + - name: Install both packages and test the SDK without a checkout + run: | + sudo apt-get install -y ./build-deb/dmod_*.deb ./build-deb/dmod-dev_*.deb /usr/bin/dmod_loader --help /usr/bin/dmf-get --help - sudo apt-get remove -y dmod + sudo bash tests/integration/test_installed_sdk.sh /usr + - name: Cross compile with the installed SDK + if: matrix.examples == 'OFF' + run: | + sudo apt-get install -y --no-install-recommends gcc-arm-none-eabi libnewlib-arm-none-eabi + sudo bash tests/integration/test_installed_sdk.sh /usr --cross + - name: Remove packages + run: | + sudo apt-get remove -y dmod-dev dmod test ! -e /usr/bin/dmod_loader + test ! -e /usr/share/cmake/dmod/dmodConfig.cmake - uses: actions/upload-artifact@v4 with: name: dmod-deb-examples-${{ matrix.examples }} - path: build-deb/dmod_*.deb + path: build-deb/*.deb diff --git a/CMakeLists.txt b/CMakeLists.txt index 576f5cb..e7ca3aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,6 +281,10 @@ if(DMOD_MODULE AND DEFINED DMOD_PACKAGE_NAME AND NOT "${DMOD_PACKAGE_NAME}" STRE endif() endif() +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR AND DMOD_SYSTEM) + include(scripts/install-sdk.cmake) +endif() + if(DMOD_BUILD_DEB) include(scripts/debian-package.cmake) endif() diff --git a/README.md b/README.md index 6d69733..73aa66b 100644 --- a/README.md +++ b/README.md @@ -787,6 +787,11 @@ tools and `dmod_loader` using `DMOD_BUILD_DEB=ON` and CMake's `package` target. See [Building and installing the Debian package](docs/debian-package.md) for prerequisites, clean-build commands and installation instructions. +The same build produces a `dmod-dev` SDK package. With both packages installed, +module projects can use `find_package(dmod CONFIG REQUIRED)` followed by +`dmod_add_library` or `dmod_add_executable`, without fetching the DMOD sources. +See [Building modules with dmod-dev](docs/development-package.md). + #### Installing with CMake To install the tools using CMake, build the project in SYSTEM mode with tools enabled, then use the install target: diff --git a/cmake/dmodConfig.cmake.in b/cmake/dmodConfig.cmake.in new file mode 100644 index 0000000..a0d39d0 --- /dev/null +++ b/cmake/dmodConfig.cmake.in @@ -0,0 +1,41 @@ +@PACKAGE_INIT@ + +if(DEFINED DMOD_MODE AND NOT DMOD_MODE STREQUAL "DMOD_MODULE") + set(dmod_FOUND FALSE) + set(dmod_NOT_FOUND_MESSAGE "The installed DMOD SDK supports DMOD_MODULE builds only") + return() +endif() +set(DMOD_MODE DMOD_MODULE) +set(DMOD_MODULE ON) +set(DMOD_SYSTEM OFF) +set_and_check(DMOD_DIR "@PACKAGE_DMOD_SDK_INSTALL_DIR@") +set_and_check(DMOD_INC_DIR "@PACKAGE_DMOD_SDK_INCLUDE_DIR@") +set(DMOD_SCRIPTS_DIR "${DMOD_DIR}/scripts") +set(DMOD_SRC_DIR "${DMOD_DIR}/src") +set(DMOD_TOOLS_BIN_DIR "${PACKAGE_PREFIX_DIR}/bin") +set(DMOD_MODULE_HEADER_SOURCE_IN_FILE_PATH "${DMOD_SCRIPTS_DIR}/dmod_header.c.in") +set(DMOD_VERSION_IN_FILE_PATH "${DMOD_SCRIPTS_DIR}/version.txt.in") +set(dmod_VERSION_MAJOR @PROJECT_VERSION_MAJOR@) +set(dmod_VERSION_MINOR @PROJECT_VERSION_MINOR@) +set(DMOD_BUILD_DIR "${CMAKE_BINARY_DIR}/dmod-sdk" CACHE PATH "DMOD runtime build directory") +set(DMOD_DMF_DIR "${CMAKE_BINARY_DIR}/dmf" CACHE PATH "DMF output directory") +set(DMOD_DMFC_DIR "${CMAKE_BINARY_DIR}/dmfc" CACHE PATH "Compressed DMF output directory") +set(DMOD_REPO_DIR "${DMOD_DMF_DIR}" CACHE PATH "Module installation directory") +set(DMOD_ARRAY_SEP ":") +set(DMOD_PATH_SEP "/") +set(DMOD_REPO_PATHS "${DMOD_DMF_DIR}:${DMOD_DMFC_DIR}" CACHE STRING "Module search paths") +include("${DMOD_DIR}/dmod-defaults.cmake") +include("${DMOD_SCRIPTS_DIR}/CMakeLists.txt") + +# These programs run on the build host, including when the module is compiled +# with a cross toolchain whose program search is rooted in a target sysroot. +find_program(TODMFC todmfc HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) +find_program(TODMD todmd HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) +find_program(TODMM todmm HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) +find_program(MKDMRPKG mkdmrpkg HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) +find_program(DMF_GET dmf-get HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) + +if(NOT TARGET dmod) + add_subdirectory("${DMOD_DIR}/cmake/runtime" "${DMOD_BUILD_DIR}") +endif() +check_required_components(dmod) diff --git a/cmake/runtime/CMakeLists.txt b/cmake/runtime/CMakeLists.txt new file mode 100644 index 0000000..ff3d3ad --- /dev/null +++ b/cmake/runtime/CMakeLists.txt @@ -0,0 +1,25 @@ +# Build only the module-side runtime, using the consumer's compiler and flags. +set(DMOD_GENERATE_MAKEFILES OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +add_compile_definitions(DMOD_MODULE ON=1 OFF=0) +add_compile_options(-ffunction-sections -fdata-sections -fno-builtin -fno-stack-protector) +configure_file("${DMOD_DIR}/dmod-config.h.in" "${DMOD_BUILD_DIR}/dmod-config.h") + +add_library(dmod_inc INTERFACE) +target_include_directories(dmod_inc INTERFACE "${DMOD_INC_DIR}" "${DMOD_BUILD_DIR}") +target_compile_definitions(dmod_inc INTERFACE DMOD_MODULE ON=1 OFF=0) +if(DEFINED DMOD_ARCH) + target_compile_definitions(dmod_inc INTERFACE DMOD_ARCH="${DMOD_ARCH}") +endif() +if(DEFINED DMOD_CPU) + target_compile_definitions(dmod_inc INTERFACE DMOD_CPU="${DMOD_CPU}") +endif() +add_subdirectory("${DMOD_SRC_DIR}/common" common) +add_subdirectory("${DMOD_SRC_DIR}/module" module) + +add_library(dmod INTERFACE) +target_link_libraries(dmod INTERFACE dmod_inc dmod_common dmod_module) +if(DMOD_USE_FASTLZ) + add_subdirectory("${DMOD_DIR}/lib/third-party/FastLZ" fastlz) + target_link_libraries(dmod INTERFACE dmod_fastlz) +endif() diff --git a/docs/debian-package.md b/docs/debian-package.md index ad380ab..e8d8d08 100644 --- a/docs/debian-package.md +++ b/docs/debian-package.md @@ -1,6 +1,6 @@ # Debian package -DMOD can build a single native Debian package named `dmod`, containing: +DMOD builds a native Debian package named `dmod`, containing: - `dmod_loader` - `dmf-get`, `dmf-man`, `whereisdmf` @@ -10,6 +10,11 @@ Executables are installed in `/usr/bin`, with the license and these instructions in `/usr/share/doc/dmod`. SDK headers, static libraries and example modules are not included. Ordinary CMake installation still defaults to `/usr/local`. +The same build also produces `dmod-dev__all.deb`, containing the +target-neutral module SDK. Install it alongside `dmod` to build modules with +`find_package(dmod CONFIG REQUIRED)` without a source checkout. See +[the development package guide](development-package.md). + ## Build Use a native Linux build on Debian or Ubuntu. Install the build dependencies: diff --git a/docs/development-package.md b/docs/development-package.md new file mode 100644 index 0000000..1a9151d --- /dev/null +++ b/docs/development-package.md @@ -0,0 +1,76 @@ +# Building modules with dmod-dev + +The `dmod-dev` Debian package supplies headers, CMake functions, linker scripts, +templates and module-side runtime sources. It depends on the matching `dmod` +host-tools package, CMake and the native C/C++ build tools. Install both locally +built packages with: + +```sh +sudo apt-get install ./build-deb/dmod_*.deb ./build-deb/dmod-dev_*.deb +``` + +Both packages are produced by the existing `DMOD_BUILD_DEB=ON` configuration and +`cmake --build build-deb --target package`; see [the package build guide](debian-package.md). +The SDK package has Debian architecture `all`: its runtime is compiled from +source using the consuming project's compiler. No DMOD checkout or network +access is needed to build a self-contained module. + +## Minimal project + +```cmake +cmake_minimum_required(VERSION 3.18) +project(my_module LANGUAGES C CXX) + +find_package(dmod 1.0 CONFIG REQUIRED) +dmod_add_library(my_module "0.1" src/my_module.c) +``` + +```sh +cmake -S . -B build +cmake --build build --parallel +``` + +The normal DMOD functions are available, including `dmod_add_executable`, +`dmod_add_test` and `dmod_link_modules`. Outputs default to `build/dmf` (`.dmf` +and `.dmd`) and `build/dmfc` (`.dmfc`). `dmod-config.h`, generated API headers +and compiled runtime objects are created in the consumer's build directory; +the installed SDK remains read-only. Existing module API declaration rules +still apply; see the [CMake function reference](cmake-functions.md). + +Set DMOD options before `find_package`, for example `DMOD_USE_FASTLZ`, +`DMOD_DMF_DIR`, `DMOD_DMFC_DIR`, `DMOD_REPO_DIR` and `DMOD_ARCH`. Set per-module +options such as `DMOD_AUTHOR_NAME`, `DMOD_STACK_SIZE`, `DMOD_MAL_IMPLS` and +`DMOD_DMR_PATH` before calling `dmod_add_library` or `dmod_add_executable`. +`dmod_link_modules` uses the installed `dmf-get` to obtain dependency headers; +registry downloads still require network access unless the resources are local +or already cached. + +To migrate an existing CMake module, replace the DMOD `FetchContent` block, +`include(.../paths.cmake)` and `dmod_setup_external_module()` with +`find_package(dmod CONFIG REQUIRED)` after `project()`. Retain the module's +sources, options, API declarations and calls to the module-building functions. +The installed SDK supports MODULE mode; building the SYSTEM runtime/firmware +or using the legacy Make integration still requires the source checkout. + +## Cross compilation and custom prefixes + +Select your target compiler with CMake's `CMAKE_TOOLCHAIN_FILE` **before** +`project()` (normally via the configure command). Install that compiler +separately; `build-essential` only supplies the host compiler. Set the target's +CPU/FPU flags in the toolchain and, if needed, `DMOD_ARCH`/`DMOD_CPU` to match +the firmware's module architecture. The SDK does not select a board for you. + +```sh +cmake -S . -B build-arm -DCMAKE_TOOLCHAIN_FILE=arm-toolchain.cmake +cmake --build build-arm --parallel +``` + +When a toolchain searches only a target sysroot for packages, set +`dmod_DIR=/usr/share/cmake/dmod` explicitly to select this target-neutral SDK. +Compression and dependency tools continue to run on the host. Cross-built +modules must be tested on their target, not with the host `dmod_loader`. + +For a source installation under a different prefix, use `cmake --install` +with `--prefix`, then pass that prefix through `CMAKE_PREFIX_PATH`. SDK paths +are resolved relative to the installed CMake configuration, so the prefix can +also be relocated without embedding a source-checkout path. diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 47f9d08..a430b60 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -38,6 +38,11 @@ function( create_library_makefile targetName ) # the generated Makefile for non-CMake builds. dmod_set_current_allocator(${targetName}) + # Installed SDK sources are read-only and only used by CMake consumers. + if(DEFINED DMOD_GENERATE_MAKEFILES AND NOT DMOD_GENERATE_MAKEFILES) + return() + endif() + # Configure the Makefile if(DMOD_MODE STREQUAL "DMOD_SYSTEM") set(DMOD_SYSTEM ON) @@ -1406,4 +1411,4 @@ function(dmod_add_tool toolName) RUNTIME DESTINATION bin COMPONENT tools ) -endfunction() \ No newline at end of file +endfunction() diff --git a/scripts/debian-package.cmake b/scripts/debian-package.cmake index a31b2fd..fba312a 100644 --- a/scripts/debian-package.cmake +++ b/scripts/debian-package.cmake @@ -1,16 +1,15 @@ -# Package only the existing tools install component, excluding SDK headers, -# static libraries, examples and any install rules from test dependencies. +# Keep host executables and the target-neutral SDK in separate packages. set(CPACK_GENERATOR DEB) set(CPACK_DEB_COMPONENT_INSTALL ON) -set(CPACK_COMPONENTS_ALL tools) -set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) +set(CPACK_COMPONENTS_ALL tools development) +set(CPACK_COMPONENTS_GROUPING IGNORE) set(CPACK_PACKAGING_INSTALL_PREFIX "/usr") set(CPACK_PACKAGE_NAME "dmod") set(CPACK_PACKAGE_VENDOR "Choco Technologies") set(CPACK_PACKAGE_CONTACT "Patryk Kubiak ") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "DMOD host tools and dynamic module loader") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Dynamic Modules (DMOD)") set(CPACK_PACKAGE_DESCRIPTION "Tools for creating, compressing, locating and installing DMOD modules, and the dmod_loader host runtime.") set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/choco-technologies/dmod") @@ -19,9 +18,18 @@ set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_DEBIAN_PACKAGE_SECTION "devel") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") # Resolve libc/libcurl (and any other linked libraries) on the build distro. -set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_TOOLS_PACKAGE_NAME "dmod") +set(CPACK_DEBIAN_TOOLS_PACKAGE_SHLIBDEPS ON) # dmf-get invokes unzip when installing module and resource archives. -set(CPACK_DEBIAN_PACKAGE_DEPENDS "unzip") +set(CPACK_DEBIAN_TOOLS_PACKAGE_DEPENDS "unzip") + +set(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_NAME "dmod-dev") +set(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_ARCHITECTURE "all") +set(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_SHLIBDEPS OFF) +set(CPACK_DEBIAN_DEVELOPMENT_PACKAGE_DEPENDS + "dmod (= ${CPACK_PACKAGE_VERSION}), cmake (>= 3.18), build-essential") +set(CPACK_DEBIAN_DEVELOPMENT_DESCRIPTION + "DMOD module development SDK\n Headers, CMake functions, linker scripts and target-neutral runtime sources for building DMOD modules with find_package(dmod CONFIG REQUIRED).") install(FILES "${DMOD_DIR}/license.md" DESTINATION share/doc/dmod RENAME copyright COMPONENT tools) diff --git a/scripts/install-sdk.cmake b/scripts/install-sdk.cmake new file mode 100644 index 0000000..e394fb7 --- /dev/null +++ b/scripts/install-sdk.cmake @@ -0,0 +1,43 @@ +include(CMakePackageConfigHelpers) + +# The SDK contains target-neutral sources, not host-built static archives. +set(DMOD_SDK_INSTALL_DIR share/dmod) +set(DMOD_SDK_INCLUDE_DIR include/dmod) +set(DMOD_CMAKE_INSTALL_DIR share/cmake/dmod) +configure_package_config_file( + "${DMOD_DIR}/cmake/dmodConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/dmodConfig.cmake" + INSTALL_DESTINATION "${DMOD_CMAKE_INSTALL_DIR}" + PATH_VARS DMOD_SDK_INSTALL_DIR DMOD_SDK_INCLUDE_DIR) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/dmodConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/dmodConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/dmodConfigVersion.cmake" + DESTINATION "${DMOD_CMAKE_INSTALL_DIR}" COMPONENT development) +install(DIRECTORY "${DMOD_DIR}/inc/" + DESTINATION "${DMOD_SDK_INCLUDE_DIR}" COMPONENT development + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp") +install(FILES "${DMOD_DIR}/dmod-defaults.cmake" "${DMOD_DIR}/dmod-config.h.in" + DESTINATION "${DMOD_SDK_INSTALL_DIR}" COMPONENT development) +install(DIRECTORY "${DMOD_DIR}/src/common" "${DMOD_DIR}/src/module" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/src" COMPONENT development + FILES_MATCHING PATTERN "*.c" PATTERN "CMakeLists.txt") +install(DIRECTORY "${DMOD_DIR}/lib/third-party/FastLZ" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/lib/third-party" COMPONENT development + FILES_MATCHING PATTERN "*.c" PATTERN "*.h" PATTERN "CMakeLists.txt" + PATTERN "LICENSE.MIT") +install(DIRECTORY "${DMOD_DIR}/scripts/" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/scripts" COMPONENT development + FILES_MATCHING PATTERN "CMakeLists.txt" PATTERN "*.in" PATTERN "*.ld" + PATTERN "memory_analysis.cmake") +install(DIRECTORY "${DMOD_DIR}/cmake/runtime" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/cmake" COMPONENT development) +install(FILES "${DMOD_DIR}/license.md" + DESTINATION share/doc/dmod-dev RENAME copyright COMPONENT development) +install(FILES "${DMOD_DIR}/lib/third-party/FastLZ/LICENSE.MIT" + DESTINATION share/doc/dmod-dev RENAME copyright.FastLZ COMPONENT development) +install(FILES "${DMOD_DIR}/docs/development-package.md" + DESTINATION share/doc/dmod-dev COMPONENT development) diff --git a/tests/integration/sdk/CMakeLists.txt b/tests/integration/sdk/CMakeLists.txt new file mode 100644 index 0000000..2c82a6c --- /dev/null +++ b/tests/integration/sdk/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.18) +project(sdk_consumer LANGUAGES C CXX) +find_package(dmod 1.0 CONFIG REQUIRED) +# Finding the same package twice must not create duplicate runtime targets. +find_package(dmod 1.0 CONFIG REQUIRED) + +set(DMOD_DMR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sdk_math.dmr") +dmod_add_library(sdk_math "0.1" library.c) +unset(DMOD_DMR_PATH) +target_include_directories(sdk_math_if INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") +dmod_add_executable(sdk_app "0.1" main.c) +target_link_libraries(sdk_app sdk_math_if) +dmod_add_test(sdk_test "0.1" test.c) + +# SDK settings must not turn unrelated consumer targets into DMOD modules. +add_library(ordinary STATIC ordinary.c) diff --git a/tests/integration/sdk/arm-toolchain.cmake b/tests/integration/sdk/arm-toolchain.cmake new file mode 100644 index 0000000..242a557 --- /dev/null +++ b/tests/integration/sdk/arm-toolchain.cmake @@ -0,0 +1,9 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR arm) +set(CMAKE_C_COMPILER arm-none-eabi-gcc) +set(CMAKE_CXX_COMPILER arm-none-eabi-g++) +set(CMAKE_OBJCOPY arm-none-eabi-objcopy) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m7 -mthumb -mfloat-abi=soft") +set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-m7 -mthumb -mfloat-abi=soft") +set(DMOD_ARCH ARMv7 CACHE STRING "Module architecture") diff --git a/tests/integration/sdk/external/CMakeLists.txt b/tests/integration/sdk/external/CMakeLists.txt new file mode 100644 index 0000000..7b48cf7 --- /dev/null +++ b/tests/integration/sdk/external/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.18) +project(sdk_external LANGUAGES C CXX) +find_package(dmod 1.0 CONFIG REQUIRED) +configure_file(../main.c main.c COPYONLY) +dmod_add_executable(sdk_external "0.1" "${CMAKE_CURRENT_BINARY_DIR}/main.c") +dmod_link_modules(sdk_external sdk_math@0.1) diff --git a/tests/integration/sdk/include/library.h b/tests/integration/sdk/include/library.h new file mode 100644 index 0000000..bf1d1e4 --- /dev/null +++ b/tests/integration/sdk/include/library.h @@ -0,0 +1,5 @@ +#ifndef SDK_MATH_H +#define SDK_MATH_H +#include "sdk_math_defs.h" +dmod_sdk_math_api(0.1, int, _add, (int a, int b)); +#endif diff --git a/tests/integration/sdk/library.c b/tests/integration/sdk/library.c new file mode 100644 index 0000000..6f8c311 --- /dev/null +++ b/tests/integration/sdk/library.c @@ -0,0 +1,20 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod.h" +#include "library.h" + +int dmod_init(const Dmod_Config_t *config) +{ + (void)config; + Dmod_Printf("SDK library initialized\n"); + return 0; +} + +int dmod_deinit(void) +{ + return 0; +} + +dmod_sdk_math_api_declaration(0.1, int, _add, (int a, int b)) +{ + return a + b; +} diff --git a/tests/integration/sdk/main.c b/tests/integration/sdk/main.c new file mode 100644 index 0000000..61a8429 --- /dev/null +++ b/tests/integration/sdk/main.c @@ -0,0 +1,13 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod.h" +#include "library.h" + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + if (sdk_math_add(20, 22) != 42) + return 1; + Dmod_Printf("SDK application passed\n"); + return 0; +} diff --git a/tests/integration/sdk/ordinary.c b/tests/integration/sdk/ordinary.c new file mode 100644 index 0000000..fc9d98b --- /dev/null +++ b/tests/integration/sdk/ordinary.c @@ -0,0 +1,7 @@ +#if defined(DMOD_MODULE) || defined(DMOD_SYSTEM) +#error "The SDK leaked module definitions into an unrelated target" +#endif +int ordinary(void) +{ + return 42; +} diff --git a/tests/integration/sdk/run.sh b/tests/integration/sdk/run.sh new file mode 100644 index 0000000..8b5b051 --- /dev/null +++ b/tests/integration/sdk/run.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail +prefix=$1 +work=$2 +shift 2 +cross=false +package_args=() +if [[ $prefix != /usr ]]; then + package_args+=("-DCMAKE_PREFIX_PATH=$prefix") +fi +if [[ ${1:-} == --cross ]]; then + cross=true + shift + set -- "-DCMAKE_TOOLCHAIN_FILE=$work/source/arm-toolchain.cmake" "$@" + package_args+=("-Ddmod_DIR=$prefix/share/cmake/dmod") +fi + +cmake -S "$work/source" -B "$work/build" \ + "${package_args[@]}" "$@" +cmake --build "$work/build" --parallel 2 +for module in sdk_math sdk_app sdk_test; do + test -s "$work/build/dmf/$module.dmf" + test -s "$work/build/dmf/$module.dmd" + test -s "$work/build/dmfc/$module.dmfc" +done + +python3 "$work/source/serve.py" "$work/build/packages" "$work/port" & +server=$! +trap 'kill "$server"; wait "$server" || true' EXIT +for attempt in {1..50}; do + [[ -s "$work/port" ]] && break + kill -0 "$server" + sleep 0.1 +done +printf 'sdk_math@0.1 http://127.0.0.1:%s/sdk_math.zip\n' "$(cat "$work/port")" > "$work/manifest.dmm" +cmake -S "$work/source/external" -B "$work/external-build" \ + "${package_args[@]}" \ + -DDMOD_DMM_URL="$work/manifest.dmm" "$@" +test -s "$work/external-build/dmf/inc/sdk_math/include/library.h" +test -s "$work/external-build/dmf/inc/sdk_math/include/sdk_math_defs.h" +cmake --build "$work/external-build" --parallel 2 + +if ! $cross; then + export DMOD_REPO_PATHS="$work/build/dmf:$work/build/dmfc" + "$prefix/bin/dmod_loader" "$work/build/dmf/sdk_app.dmf" + "$prefix/bin/dmod_loader" "$work/build/dmfc/sdk_app.dmfc" + "$prefix/bin/dmod_loader" "$work/build/dmf/sdk_test.dmf" + "$prefix/bin/dmod_loader" "$work/external-build/dmf/sdk_external.dmf" +fi +echo "Installed SDK test passed (cross=$cross)" diff --git a/tests/integration/sdk/sdk_math.dmr b/tests/integration/sdk/sdk_math.dmr new file mode 100644 index 0000000..e3768ba --- /dev/null +++ b/tests/integration/sdk/sdk_math.dmr @@ -0,0 +1,3 @@ +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] +inc=./include => ${destination}/${module}/include [origin=${repo_dir}/include] [origin=${build_dir}/${module}_defs.h] +dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/sdk_math.dmr] diff --git a/tests/integration/sdk/serve.py b/tests/integration/sdk/serve.py new file mode 100644 index 0000000..35a8aa5 --- /dev/null +++ b/tests/integration/sdk/serve.py @@ -0,0 +1,9 @@ +"""Serve the test module from loopback inside the isolated network namespace.""" +from functools import partial +from http.server import HTTPServer, SimpleHTTPRequestHandler +from pathlib import Path +import sys + +server = HTTPServer(("127.0.0.1", 0), partial(SimpleHTTPRequestHandler, directory=sys.argv[1])) +Path(sys.argv[2]).write_text(str(server.server_port)) +server.serve_forever() diff --git a/tests/integration/sdk/test.c b/tests/integration/sdk/test.c new file mode 100644 index 0000000..ec709dc --- /dev/null +++ b/tests/integration/sdk/test.c @@ -0,0 +1,6 @@ +#include "dmod_test.h" + +DMOD_TEST_STEP(arithmetic) +{ + DMOD_TEST_EXPECT_EQ(20 + 22, 42); +} diff --git a/tests/integration/test_development_package.sh b/tests/integration/test_development_package.sh new file mode 100644 index 0000000..a0a3915 --- /dev/null +++ b/tests/integration/test_development_package.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +if [[ $# != 2 ]]; then + echo "Usage: $0 dmod.deb dmod-dev.deb" >&2 + exit 1 +fi +runtime_version=$(dpkg-deb --field "$1" Version) +[[ $(dpkg-deb --field "$2" Package) == dmod-dev ]] +[[ $(dpkg-deb --field "$2" Architecture) == all ]] +[[ $(dpkg-deb --field "$2" Depends) == *"dmod (= $runtime_version)"* ]] +root=$(mktemp -d) +trap 'rm -rf "$root"' EXIT +dpkg-deb --extract "$2" "$root" +for file in include/dmod/dmod.h include/dmod/Dmod.hpp \ + share/cmake/dmod/dmodConfig.cmake share/cmake/dmod/dmodConfigVersion.cmake \ + share/dmod/scripts/module.ld share/dmod/scripts/api.h.in \ + share/dmod/src/module/dmod_module.c share/dmod/src/module/dmod_test_main.c \ + share/doc/dmod-dev/copyright share/doc/dmod-dev/copyright.FastLZ; do + test -s "$root/usr/$file" +done +test ! -e "$root/usr/bin" +test -z "$(find "$root" -type f \( -name '*.o' -o -name '*.a' -o -name '*.so*' \) -print)" +echo "Development package metadata and contents passed" diff --git a/tests/integration/test_installed_sdk.sh b/tests/integration/test_installed_sdk.sh new file mode 100644 index 0000000..4aad86d --- /dev/null +++ b/tests/integration/test_installed_sdk.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Requires bubblewrap; on systems restricting user namespaces, invoke via sudo. +set -euo pipefail +if [[ $# -lt 1 ]]; then + echo "Usage: $0 installed-prefix [--cross] [CMake options...]" >&2 + exit 1 +fi +prefix=$(realpath "$1") +shift +checkout=$(realpath "$(dirname "$0")/../..") +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +cp -r "$checkout/tests/integration/sdk" "$work/source" +mkdir "$work/home" +cd "$work" + +# No checkout, no network, a read-only SDK and source fixture, and no inherited +# DMOD variables/tool caches. Only the consumer build directory is writable. +bwrap --die-with-parent --unshare-net --ro-bind / / --tmpfs /tmp \ + --tmpfs "$checkout" --bind "$work" "$work" \ + --ro-bind "$work/source" "$work/source" --ro-bind "$prefix" "$prefix" \ + --dev /dev --proc /proc --chdir "$work" \ + /usr/bin/env -i PATH="$prefix/bin:$PATH" HOME="$work/home" \ + /bin/bash "$work/source/run.sh" "$prefix" "$work" "$@" From 3289065afad627b119dfc758467232a693537024 Mon Sep 17 00:00:00 2001 From: Jax Calder Date: Sun, 4 Oct 2026 15:36:17 +0100 Subject: [PATCH 2/2] Preserve DMOD tools profiles and cover rebased asset conversion --- .github/workflows/debian-package.yml | 8 +-- cmake/dmodConfig.cmake.in | 4 ++ cmake/runtime/CMakeLists.txt | 4 ++ cmake/select-profile.cmake | 30 ++++++++++++ docs/development-package.md | 49 ++++++++++++++++--- scripts/install-sdk.cmake | 5 ++ tests/integration/prepare_sdk_assets.sh | 10 ++++ tests/integration/sdk/CMakeLists.txt | 2 +- tests/integration/sdk/assets/CMakeLists.txt | 10 ++++ tests/integration/sdk/assets/assets.dmr | 2 + .../sdk/assets/fixtures/font.ttf.ini | 7 +++ .../sdk/assets/fixtures/pixel.png.ini | 4 ++ .../sdk/assets/fixtures/screen.dmvs | 6 +++ tests/integration/sdk/assets/main.c | 8 +++ tests/integration/sdk/assets/run.py | 29 +++++++++++ tests/integration/sdk/external/CMakeLists.txt | 2 +- tests/integration/sdk/run.sh | 8 +++ tests/integration/test_development_package.sh | 2 + tests/integration/test_installed_sdk.sh | 15 +++++- 19 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 cmake/select-profile.cmake create mode 100644 tests/integration/prepare_sdk_assets.sh create mode 100644 tests/integration/sdk/assets/CMakeLists.txt create mode 100644 tests/integration/sdk/assets/assets.dmr create mode 100644 tests/integration/sdk/assets/fixtures/font.ttf.ini create mode 100644 tests/integration/sdk/assets/fixtures/pixel.png.ini create mode 100644 tests/integration/sdk/assets/fixtures/screen.dmvs create mode 100644 tests/integration/sdk/assets/main.c create mode 100644 tests/integration/sdk/assets/run.py diff --git a/.github/workflows/debian-package.yml b/.github/workflows/debian-package.yml index b89569a..b29d89f 100644 --- a/.github/workflows/debian-package.yml +++ b/.github/workflows/debian-package.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install build dependencies - run: sudo apt-get update && sudo apt-get install -y build-essential cmake dpkg-dev libcurl4-openssl-dev unzip bubblewrap python3 + run: sudo apt-get update && sudo apt-get install -y build-essential cmake dpkg-dev libcurl4-openssl-dev unzip bubblewrap python3 fonts-dejavu-core - name: Bootstrap host tools run: | cmake -S . -B build-deb -DDMOD_TOOLS_NAME=arch/x86_64 \ @@ -44,11 +44,13 @@ jobs: sudo apt-get install -y ./build-deb/dmod_*.deb ./build-deb/dmod-dev_*.deb /usr/bin/dmod_loader --help /usr/bin/dmf-get --help - sudo bash tests/integration/test_installed_sdk.sh /usr + DMOD_CACHE_DIR="$RUNNER_TEMP/asset-cache" bash tests/integration/prepare_sdk_assets.sh /usr "$RUNNER_TEMP/asset-tools" arch/x86_64 + sudo env DMOD_SDK_TEST_ASSETS="$RUNNER_TEMP/asset-tools" bash tests/integration/test_installed_sdk.sh /usr -DDMOD_TOOLS_NAME=arch/x86_64 - name: Cross compile with the installed SDK if: matrix.examples == 'OFF' run: | - sudo apt-get install -y --no-install-recommends gcc-arm-none-eabi libnewlib-arm-none-eabi + sudo apt-get install -y --no-install-recommends gcc-arm-none-eabi libnewlib-arm-none-eabi gdb-multiarch + sudo env DMOD_SDK_TEST_ASSETS="$RUNNER_TEMP/asset-tools" bash tests/integration/test_installed_sdk.sh /usr --cross-profile arch/armv7/cortex-m7 sudo bash tests/integration/test_installed_sdk.sh /usr --cross - name: Remove packages run: | diff --git a/cmake/dmodConfig.cmake.in b/cmake/dmodConfig.cmake.in index a0d39d0..2c4c3a6 100644 --- a/cmake/dmodConfig.cmake.in +++ b/cmake/dmodConfig.cmake.in @@ -13,6 +13,9 @@ set_and_check(DMOD_INC_DIR "@PACKAGE_DMOD_SDK_INCLUDE_DIR@") set(DMOD_SCRIPTS_DIR "${DMOD_DIR}/scripts") set(DMOD_SRC_DIR "${DMOD_DIR}/src") set(DMOD_TOOLS_BIN_DIR "${PACKAGE_PREFIX_DIR}/bin") +set(DMOD_CONFIGS_DIR "${DMOD_DIR}/configs") +# Like the source build, select the compiler/profile before enabling languages. +include("${DMOD_DIR}/cmake/select-profile.cmake") set(DMOD_MODULE_HEADER_SOURCE_IN_FILE_PATH "${DMOD_SCRIPTS_DIR}/dmod_header.c.in") set(DMOD_VERSION_IN_FILE_PATH "${DMOD_SCRIPTS_DIR}/version.txt.in") set(dmod_VERSION_MAJOR @PROJECT_VERSION_MAJOR@) @@ -34,6 +37,7 @@ find_program(TODMD todmd HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH R find_program(TODMM todmm HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) find_program(MKDMRPKG mkdmrpkg HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) find_program(DMF_GET dmf-get HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) +find_program(DMOD_LOADER dmod_loader HINTS "${DMOD_TOOLS_BIN_DIR}" NO_CMAKE_FIND_ROOT_PATH REQUIRED) if(NOT TARGET dmod) add_subdirectory("${DMOD_DIR}/cmake/runtime" "${DMOD_BUILD_DIR}") diff --git a/cmake/runtime/CMakeLists.txt b/cmake/runtime/CMakeLists.txt index ff3d3ad..8b4672e 100644 --- a/cmake/runtime/CMakeLists.txt +++ b/cmake/runtime/CMakeLists.txt @@ -1,4 +1,8 @@ # Build only the module-side runtime, using the consumer's compiler and flags. +if(NOT CMAKE_C_COMPILER_LOADED) + # Mirrors the nested project used by FetchContent_MakeAvailable(dmod). + project(dmod_sdk LANGUAGES C CXX) +endif() set(DMOD_GENERATE_MAKEFILES OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_definitions(DMOD_MODULE ON=1 OFF=0) diff --git a/cmake/select-profile.cmake b/cmake/select-profile.cmake new file mode 100644 index 0000000..ccf0c30 --- /dev/null +++ b/cmake/select-profile.cmake @@ -0,0 +1,30 @@ +# Use the same profiles as the source/FetchContent build, without modifying them. +set(_dmod_profile "") +if(DEFINED DMOD_TOOLS AND NOT DMOD_TOOLS STREQUAL "") + get_filename_component(_dmod_profile "${DMOD_TOOLS}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") +elseif(DEFINED DMOD_TOOLS_NAME AND NOT DMOD_TOOLS_NAME STREQUAL "") + set(_dmod_profile "${DMOD_CONFIGS_DIR}/${DMOD_TOOLS_NAME}/tools-cfg.cmake") +endif() + +if(NOT _dmod_profile STREQUAL "") + if(NOT EXISTS "${_dmod_profile}") + message(FATAL_ERROR "DMOD tools configuration does not exist: ${_dmod_profile}") + endif() + get_property(_dmod_loaded_profile GLOBAL PROPERTY DMOD_SDK_LOADED_PROFILE) + if(_dmod_loaded_profile AND NOT _dmod_loaded_profile STREQUAL _dmod_profile) + message(FATAL_ERROR "All modules in a build must use the same DMOD tools configuration") + endif() + if(NOT _dmod_loaded_profile) + if(CMAKE_C_COMPILER_LOADED OR CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "DMOD_TOOLS_NAME/DMOD_TOOLS selects the compiler. Move find_package(dmod CONFIG REQUIRED) before project(), as with the source build's FetchContent_MakeAvailable(dmod).") + endif() + if(DEFINED DMOD_SDK_CONFIGURED_PROFILE AND NOT DMOD_SDK_CONFIGURED_PROFILE STREQUAL _dmod_profile) + message(FATAL_ERROR "Changing DMOD tools configuration requires a fresh build directory") + endif() + set(DMOD_SDK_CONFIGURED_PROFILE "${_dmod_profile}" CACHE INTERNAL "SDK tools configuration") + set(DMOD_TOOLS "${_dmod_profile}") + include("${DMOD_TOOLS}") + set_property(GLOBAL PROPERTY DMOD_SDK_LOADED_PROFILE "${_dmod_profile}") + endif() +endif() diff --git a/docs/development-package.md b/docs/development-package.md index 1a9151d..a70e68c 100644 --- a/docs/development-package.md +++ b/docs/development-package.md @@ -19,9 +19,9 @@ access is needed to build a self-contained module. ```cmake cmake_minimum_required(VERSION 3.18) +find_package(dmod 1.0 CONFIG REQUIRED) project(my_module LANGUAGES C CXX) -find_package(dmod 1.0 CONFIG REQUIRED) dmod_add_library(my_module "0.1" src/my_module.c) ``` @@ -47,24 +47,41 @@ or already cached. To migrate an existing CMake module, replace the DMOD `FetchContent` block, `include(.../paths.cmake)` and `dmod_setup_external_module()` with -`find_package(dmod CONFIG REQUIRED)` after `project()`. Retain the module's +`find_package(dmod CONFIG REQUIRED)` before `project()`. Retain the module's sources, options, API declarations and calls to the module-building functions. The installed SDK supports MODULE mode; building the SYSTEM runtime/firmware or using the legacy Make integration still requires the source checkout. ## Cross compilation and custom prefixes -Select your target compiler with CMake's `CMAKE_TOOLCHAIN_FILE` **before** -`project()` (normally via the configure command). Install that compiler -separately; `build-essential` only supplies the host compiler. Set the target's -CPU/FPU flags in the toolchain and, if needed, `DMOD_ARCH`/`DMOD_CPU` to match -the firmware's module architecture. The SDK does not select a board for you. +All existing `DMOD_TOOLS_NAME` profiles are installed with the SDK. They load +the **same** `configs//tools-cfg.cmake` as source/FetchContent builds, +selecting the compiler, binutils, architecture, CPU/FPU flags and DMOD feature +settings, and selecting the dependency registry configuration. For example: ```sh -cmake -S . -B build-arm -DCMAKE_TOOLCHAIN_FILE=arm-toolchain.cmake +cmake -S . -B build-arm -DDMOD_TOOLS_NAME=arch/armv7/cortex-m7 cmake --build build-arm --parallel ``` +Place `find_package` before `project()`, just as the existing flow places +`FetchContent_MakeAvailable(dmod)` before `project()`. CMake must choose the +compiler before enabling languages. Selecting a profile afterwards reports an +error instead of silently building with the host compiler. Repeated +`find_package` calls after initialization are supported. Use a fresh build +directory when changing profiles. + +The existing `DMOD_TOOLS` override can select a custom tools configuration. +Profile-specific overrides such as `FPU_FLAGS`, `CROSS_COMPILE` and +`CPUCONFIG_CFLAGS` retain their original profile semantics. Install the chosen +compiler separately; `build-essential` only supplies the native compiler, and +ARM profiles also look for the target debugger or `gdb-multiarch`. + +Alternatively, omit `DMOD_TOOLS_NAME`/`DMOD_TOOLS` and select your own compiler +with `CMAKE_TOOLCHAIN_FILE`. In that case `find_package` may also follow +`project()`. Set CPU/FPU flags in the toolchain and `DMOD_ARCH`/`DMOD_CPU` if +needed to match the firmware's module architecture. + When a toolchain searches only a target sysroot for packages, set `dmod_DIR=/usr/share/cmake/dmod` explicitly to select this target-neutral SDK. Compression and dependency tools continue to run on the host. Cross-built @@ -74,3 +91,19 @@ For a source installation under a different prefix, use `cmake --install` with `--prefix`, then pass that prefix through `CMAKE_PREFIX_PATH`. SDK paths are resolved relative to the installed CMake configuration, so the prefix can also be relocated without embedding a source-checkout path. + +## Assets and existing projects + +The SDK includes the current shared `dmod_setup_assets` implementation: +`DMOD_ASSETS_PATHS` converts views with `todmv`, images with `todmvi` and the +appropriate decoder, and fonts with `todmvf`, including font `tracking` and +multi-section INI files. The older `DMOD_FIXTURES_PATHS` name still works with +its existing deprecation warning. `.dmr` packaging receives `DMOD_VIEWS_DIR`. +Asset tools run on the **host**, selected by `DMOD_VIEWS_TOOLS_NAME` (default +`arch/x86_64`), independently of the target's `DMOD_TOOLS_NAME`. The SDK locates +the host `dmf-get` and `dmod_loader` outside cross-compilation sysroots. + +Using the SDK is optional. Existing projects using a checkout, FetchContent, +`paths.cmake`/`dmod_setup_external_module`, or Make continue to use their +existing flow without any source changes. Installing the packages does not +redirect or replace those flows. diff --git a/scripts/install-sdk.cmake b/scripts/install-sdk.cmake index e394fb7..c158e12 100644 --- a/scripts/install-sdk.cmake +++ b/scripts/install-sdk.cmake @@ -35,6 +35,11 @@ install(DIRECTORY "${DMOD_DIR}/scripts/" PATTERN "memory_analysis.cmake") install(DIRECTORY "${DMOD_DIR}/cmake/runtime" DESTINATION "${DMOD_SDK_INSTALL_DIR}/cmake" COMPONENT development) +install(FILES "${DMOD_DIR}/cmake/select-profile.cmake" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/cmake" COMPONENT development) +install(DIRECTORY "${DMOD_DIR}/configs/" + DESTINATION "${DMOD_SDK_INSTALL_DIR}/configs" COMPONENT development + FILES_MATCHING PATTERN "*.cmake") install(FILES "${DMOD_DIR}/license.md" DESTINATION share/doc/dmod-dev RENAME copyright COMPONENT development) install(FILES "${DMOD_DIR}/lib/third-party/FastLZ/LICENSE.MIT" diff --git a/tests/integration/prepare_sdk_assets.sh b/tests/integration/prepare_sdk_assets.sh new file mode 100644 index 0000000..57e1193 --- /dev/null +++ b/tests/integration/prepare_sdk_assets.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +prefix=$1 +destination=$2 +profile=$3 +mkdir -p "$destination" +for tool in todmv todmvi todmvf; do + "$prefix/bin/dmf-get" "$tool" -t "$profile" --type dmf -y -o "$destination/$tool" +done +"$prefix/bin/dmf-get" dmimg_png -t "$profile" --type dmf -y -o "$destination/todmvi" diff --git a/tests/integration/sdk/CMakeLists.txt b/tests/integration/sdk/CMakeLists.txt index 2c82a6c..c417ff4 100644 --- a/tests/integration/sdk/CMakeLists.txt +++ b/tests/integration/sdk/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18) -project(sdk_consumer LANGUAGES C CXX) find_package(dmod 1.0 CONFIG REQUIRED) +project(sdk_consumer LANGUAGES C CXX) # Finding the same package twice must not create duplicate runtime targets. find_package(dmod 1.0 CONFIG REQUIRED) diff --git a/tests/integration/sdk/assets/CMakeLists.txt b/tests/integration/sdk/assets/CMakeLists.txt new file mode 100644 index 0000000..04d4421 --- /dev/null +++ b/tests/integration/sdk/assets/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.18) +find_package(dmod CONFIG REQUIRED) +project(sdk_assets LANGUAGES C CXX) +if(OLD_ASSET_NAME) + set(DMOD_FIXTURES_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/fixtures") +else() + set(DMOD_ASSETS_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/fixtures") +endif() +set(DMOD_DMR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets.dmr") +dmod_add_executable(sdk_assets "0.1" main.c) diff --git a/tests/integration/sdk/assets/assets.dmr b/tests/integration/sdk/assets/assets.dmr new file mode 100644 index 0000000..3a14a41 --- /dev/null +++ b/tests/integration/sdk/assets/assets.dmr @@ -0,0 +1,2 @@ +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] +views=./views => ${destination}/${module}/views [origin=${views_dir}] diff --git a/tests/integration/sdk/assets/fixtures/font.ttf.ini b/tests/integration/sdk/assets/fixtures/font.ttf.ini new file mode 100644 index 0000000..2be0457 --- /dev/null +++ b/tests/integration/sdk/assets/fixtures/font.ttf.ini @@ -0,0 +1,7 @@ +chars=0x20-0x7E +tracking=-2.4 +[body] +size=14 +[title] +size=20 +tracking=1.0 diff --git a/tests/integration/sdk/assets/fixtures/pixel.png.ini b/tests/integration/sdk/assets/fixtures/pixel.png.ini new file mode 100644 index 0000000..fd71e3b --- /dev/null +++ b/tests/integration/sdk/assets/fixtures/pixel.png.ini @@ -0,0 +1,4 @@ +format=rgb565 +dither=off +[icon] +size=1x1 diff --git a/tests/integration/sdk/assets/fixtures/screen.dmvs b/tests/integration/sdk/assets/fixtures/screen.dmvs new file mode 100644 index 0000000..ec0aaf7 --- /dev/null +++ b/tests/integration/sdk/assets/fixtures/screen.dmvs @@ -0,0 +1,6 @@ +.view screen +.entry draw +draw: + FILL #123456 + RECT 1, 2, 3, 4, #FFFFFF + RET diff --git a/tests/integration/sdk/assets/main.c b/tests/integration/sdk/assets/main.c new file mode 100644 index 0000000..f69cc94 --- /dev/null +++ b/tests/integration/sdk/assets/main.c @@ -0,0 +1,8 @@ +#include "dmod.h" +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + Dmod_Printf("SDK assets application\n"); + return 0; +} diff --git a/tests/integration/sdk/assets/run.py b/tests/integration/sdk/assets/run.py new file mode 100644 index 0000000..be768bf --- /dev/null +++ b/tests/integration/sdk/assets/run.py @@ -0,0 +1,29 @@ +"""Exercise real host converters with both the current and legacy asset setting.""" +from pathlib import Path +import shutil +import subprocess +import sys +import zipfile + +prefix, work = map(Path, sys.argv[1:3]) +source = work / "source/assets" +outputs = {"screen.dmv", "icon.dmvi", "body.dmvf", "title.dmvf"} +for old in (False, True): + build = work / ("assets-old" if old else "assets-new") + shutil.copytree(work / "assets-tools", build) + subprocess.run(["cmake", "-S", str(source), "-B", str(build), + f"-DOLD_ASSET_NAME={'ON' if old else 'OFF'}", *sys.argv[3:]], check=True) + subprocess.run(["cmake", "--build", str(build), "--parallel", "2"], check=True) + for name in outputs: + assert (build / "views" / name).stat().st_size > 0, name + with zipfile.ZipFile(build / "packages/sdk_assets.zip") as package: + for name in outputs: + assert package.read("views/" + name) == (build / "views" / name).read_bytes() + # Make sure incremental builds do not rerun converters unnecessarily. + stamps = {name: (build / "views" / name).stat().st_mtime_ns for name in outputs} + subprocess.run(["cmake", "--build", str(build), "--parallel", "2"], check=True) + assert stamps == {name: (build / "views" / name).stat().st_mtime_ns for name in outputs} + if old: + for name in outputs: + assert (build / "views" / name).read_bytes() == (work / "assets-new/views" / name).read_bytes() +print("Real view/image/font converters, tracking, packaging and legacy asset alias passed") diff --git a/tests/integration/sdk/external/CMakeLists.txt b/tests/integration/sdk/external/CMakeLists.txt index 7b48cf7..94a507c 100644 --- a/tests/integration/sdk/external/CMakeLists.txt +++ b/tests/integration/sdk/external/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18) -project(sdk_external LANGUAGES C CXX) find_package(dmod 1.0 CONFIG REQUIRED) +project(sdk_external LANGUAGES C CXX) configure_file(../main.c main.c COPYONLY) dmod_add_executable(sdk_external "0.1" "${CMAKE_CURRENT_BINARY_DIR}/main.c") dmod_link_modules(sdk_external sdk_math@0.1) diff --git a/tests/integration/sdk/run.sh b/tests/integration/sdk/run.sh index 8b5b051..7ee34a1 100644 --- a/tests/integration/sdk/run.sh +++ b/tests/integration/sdk/run.sh @@ -13,6 +13,11 @@ if [[ ${1:-} == --cross ]]; then shift set -- "-DCMAKE_TOOLCHAIN_FILE=$work/source/arm-toolchain.cmake" "$@" package_args+=("-Ddmod_DIR=$prefix/share/cmake/dmod") +elif [[ ${1:-} == --cross-profile ]]; then + cross=true + profile=$2 + shift 2 + set -- "-DDMOD_TOOLS_NAME=$profile" "$@" fi cmake -S "$work/source" -B "$work/build" \ @@ -47,4 +52,7 @@ if ! $cross; then "$prefix/bin/dmod_loader" "$work/build/dmf/sdk_test.dmf" "$prefix/bin/dmod_loader" "$work/external-build/dmf/sdk_external.dmf" fi +if [[ -d "$work/assets-tools" ]]; then + python3 "$work/source/assets/run.py" "$prefix" "$work" "${package_args[@]}" "$@" +fi echo "Installed SDK test passed (cross=$cross)" diff --git a/tests/integration/test_development_package.sh b/tests/integration/test_development_package.sh index a0a3915..163b0a6 100644 --- a/tests/integration/test_development_package.sh +++ b/tests/integration/test_development_package.sh @@ -14,6 +14,8 @@ dpkg-deb --extract "$2" "$root" for file in include/dmod/dmod.h include/dmod/Dmod.hpp \ share/cmake/dmod/dmodConfig.cmake share/cmake/dmod/dmodConfigVersion.cmake \ share/dmod/scripts/module.ld share/dmod/scripts/api.h.in \ + share/dmod/configs/arch/armv7/cortex-m7/tools-cfg.cmake \ + share/dmod/configs/arch/x86_64/tools-cfg.cmake \ share/dmod/src/module/dmod_module.c share/dmod/src/module/dmod_test_main.c \ share/doc/dmod-dev/copyright share/doc/dmod-dev/copyright.FastLZ; do test -s "$root/usr/$file" diff --git a/tests/integration/test_installed_sdk.sh b/tests/integration/test_installed_sdk.sh index 4aad86d..6ccba6e 100644 --- a/tests/integration/test_installed_sdk.sh +++ b/tests/integration/test_installed_sdk.sh @@ -2,7 +2,7 @@ # Requires bubblewrap; on systems restricting user namespaces, invoke via sudo. set -euo pipefail if [[ $# -lt 1 ]]; then - echo "Usage: $0 installed-prefix [--cross] [CMake options...]" >&2 + echo "Usage: $0 installed-prefix [--cross | --cross-profile name] [CMake options...]" >&2 exit 1 fi prefix=$(realpath "$1") @@ -11,6 +11,19 @@ checkout=$(realpath "$(dirname "$0")/../..") work=$(mktemp -d) trap 'rm -rf "$work"' EXIT cp -r "$checkout/tests/integration/sdk" "$work/source" +if [[ -n ${DMOD_SDK_TEST_ASSETS:-} ]]; then + cp -r "$DMOD_SDK_TEST_ASSETS" "$work/assets-tools" + cp /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf "$work/source/assets/fixtures/font.ttf" + python3 - "$work/source/assets/fixtures/pixel.png" <<'PY' +import struct, sys, zlib +def chunk(kind, data): + return struct.pack('>I', len(data)) + kind + data + struct.pack('>I', zlib.crc32(kind + data)) +data = b'\x89PNG\r\n\x1a\n' +data += chunk(b'IHDR', struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0)) +data += chunk(b'IDAT', zlib.compress(b'\x00\xff\x00\x00')) + chunk(b'IEND', b'') +open(sys.argv[1], 'wb').write(data) +PY +fi mkdir "$work/home" cd "$work"