From 0b74d30bbb23d4cf4d6bb2013c521210f9f567a1 Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Tue, 2 Jun 2026 22:25:53 +0800 Subject: [PATCH 1/7] Decouple emulator build from launcher submodule + multi-arch CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three structural changes to make the emulator robust against upstream launcher reshuffles and to ship cross-platform release artifacts: 1. cmake/launcher_sources.cmake — single source-of-truth for which files and include paths the emulator pulls from vendor/M5CardputerZero-Launcher. Probes for keyboard_input.c at both old (src/) and new (hal/) locations. Probes for the APPLAUNCH images dir at both old (ui/images) and new (APPLaunch/share/images) locations. Adding a launcher third-party component (e.g. miniaudio) is now a one-line edit here, not three target_include_directories calls scattered across CMakeLists.txt. 2. cmake/third_party.cmake — emu::compat_stubs / emu::miniaudio / emu::launcher_apply INTERFACE libraries. Centralizes platform audio framework links (CoreAudio/AudioToolbox/AudioUnit on macOS, asound/dl/pthread on Linux). Gates src/compat to Windows-only so the sys/wait.h stub doesn't shadow real system headers on Unix. Picks up the launcher's own thpool.h stub if shipped. 3. cmake/patches/apply_vendor_workarounds.cmake — temporary, narrowly scoped patches against the launcher submodule with explicit upstream PR references. Idempotent: re-running cmake on an already-patched tree is a no-op; once upstream merges the fix, the entry self-skips and is removed in the same emu commit that bumps the submodule. Currently patches: - ui_app_setup.hpp: drop the guard so UISetupPage compiles on emu (the class body already uses HAL, residual ioctl/i2c are inside #ifdef __linux__). Tracking PR TBD against M5CardputerZero-Launcher. CI rewrite: - macOS arm64 (macos-14) + macOS x64 (macos-13) parallel build matrix - macOS Universal: lipo-merge of arm64 + x64 artifacts - Windows x64 (MinGW), Linux x64 (ubuntu-22.04), Web (WASM) - actions/checkout bumped to v5 (Node 20 deprecation) - macOS dylibbundler bundles non-system dylibs next to the binary - Windows DLL walker now does a transitive pass for nested deps - Linux installs libasound2-dev (miniaudio backend) - Pre-release artifacts renamed with explicit arch suffixes --- .github/workflows/build.yml | 402 +++++++++++-------- .gitignore | 6 +- CMakeLists.txt | 61 ++- cmake/launcher_sources.cmake | 94 +++++ cmake/patches/apply_vendor_workarounds.cmake | 83 ++++ cmake/third_party.cmake | 82 ++++ 6 files changed, 527 insertions(+), 201 deletions(-) create mode 100644 cmake/launcher_sources.cmake create mode 100644 cmake/patches/apply_vendor_workarounds.cmake create mode 100644 cmake/third_party.cmake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4db4186..190e216 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,191 +8,261 @@ on: workflow_dispatch: jobs: - # ── macOS ─────────────────────────────────────────────────── + # ── macOS arm64 + x64 (matrix) ────────────────────────────────────────────── macos: - runs-on: macos-latest + name: macos-${{ matrix.arch }} + strategy: + fail-fast: false + matrix: + include: + - arch: arm64 + runner: macos-14 + - arch: x64 + runner: macos-13 + runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dependencies - run: brew install sdl2 sdl2_image freetype pkg-config - - name: Build - run: | - mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make -j$(sysctl -n hw.ncpu) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ - cp -r build/assets dist/M5CardputerZero-Emulator/ - cp -r build/share dist/M5CardputerZero-Emulator/ - cd dist && tar czf ../M5CardputerZero-Emulator-macOS.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-macos - path: M5CardputerZero-Emulator-macOS.tar.gz + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install dependencies + run: | + brew update >/dev/null + brew install sdl2 sdl2_image freetype pkg-config + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(sysctl -n hw.ncpu) + - name: Bundle dylibs + run: | + set -euxo pipefail + DIST=dist/M5CardputerZero-Emulator + mkdir -p "$DIST" + cp build/cardputer-emu "$DIST/" + cp -r build/apps "$DIST/" + cp -r build/assets "$DIST/" + cp -r build/share "$DIST/" + # Copy non-system dylib deps next to the binary and rewrite rpaths. + if ! command -v dylibbundler >/dev/null 2>&1; then + brew install dylibbundler + fi + dylibbundler -od -b \ + -x "$DIST/cardputer-emu" \ + -x "$DIST/apps/libAPPLaunch.dylib" \ + -x "$DIST/apps/libUserDemo.dylib" \ + -d "$DIST/libs" -p "@executable_path/libs/" || \ + echo "[warn] dylibbundler returned non-zero (continuing — dependencies may already resolve)" + - name: Package + run: | + cd dist && tar czf ../M5CardputerZero-Emulator-macOS-${{ matrix.arch }}.tar.gz M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-macos-${{ matrix.arch }} + path: M5CardputerZero-Emulator-macOS-${{ matrix.arch }}.tar.gz - # ── Windows (MinGW-w64) ───────────────────────────────────── + # ── macOS Universal (lipo merge of arm64 + x64) ──────────────────────────── + macos-universal: + name: macos-universal + needs: macos + runs-on: macos-14 + steps: + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + pattern: emulator-macos-* + path: artifacts + - name: Build universal bundle via lipo + run: | + set -euxo pipefail + mkdir -p arm64 x64 universal/M5CardputerZero-Emulator + tar xzf artifacts/emulator-macos-arm64/M5CardputerZero-Emulator-macOS-arm64.tar.gz -C arm64 + tar xzf artifacts/emulator-macos-x64/M5CardputerZero-Emulator-macOS-x64.tar.gz -C x64 + + ARM=arm64/M5CardputerZero-Emulator + X64=x64/M5CardputerZero-Emulator + UNI=universal/M5CardputerZero-Emulator + + # Mirror arm64 layout, then lipo-merge every Mach-O. + rsync -a "$ARM/" "$UNI/" + while IFS= read -r -d '' rel; do + file_arm="$ARM/$rel" + file_x64="$X64/$rel" + file_uni="$UNI/$rel" + if [ -f "$file_x64" ] && file "$file_arm" | grep -q "Mach-O"; then + lipo -create "$file_arm" "$file_x64" -output "$file_uni" 2>/dev/null \ + || cp "$file_arm" "$file_uni" + fi + done < <(cd "$ARM" && find . -type f -print0) + + # Sanity: confirm executable is now fat + file "$UNI/cardputer-emu" + lipo -info "$UNI/cardputer-emu" + + cd universal && tar czf ../M5CardputerZero-Emulator-macOS-universal.tar.gz M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-macos-universal + path: M5CardputerZero-Emulator-macOS-universal.tar.gz + + # ── Windows x64 (MinGW-w64) ──────────────────────────────────────────────── windows: + name: windows-x64 runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - install: >- - mingw-w64-x86_64-gcc - mingw-w64-x86_64-cmake - mingw-w64-x86_64-SDL2 - mingw-w64-x86_64-SDL2_image - mingw-w64-x86_64-freetype - mingw-w64-x86_64-winpthreads-git - make - git - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Build - run: | - mkdir build && cd build - cmake .. -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release - make -j$(nproc) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu.exe dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ 2>/dev/null || true - cp -r build/assets dist/M5CardputerZero-Emulator/ 2>/dev/null || true - cp -r build/share dist/M5CardputerZero-Emulator/ 2>/dev/null || true - # Copy all DLL dependencies automatically - ldd build/cardputer-emu.exe | grep mingw64 | awk '{print $3}' | while read dll; do - cp "$dll" dist/M5CardputerZero-Emulator/ - done - cd dist && tar czf ../M5CardputerZero-Emulator-Windows.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-windows - path: M5CardputerZero-Emulator-Windows.tar.gz + - uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: >- + mingw-w64-x86_64-gcc + mingw-w64-x86_64-cmake + mingw-w64-x86_64-SDL2 + mingw-w64-x86_64-SDL2_image + mingw-w64-x86_64-freetype + mingw-w64-x86_64-winpthreads-git + make + git + zip + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Build + run: | + mkdir build && cd build + cmake .. -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release + make -j$(nproc) + - name: Bundle DLLs + run: | + set -euxo pipefail + DIST=dist/M5CardputerZero-Emulator + mkdir -p "$DIST" + cp build/cardputer-emu.exe "$DIST/" + cp -r build/apps "$DIST/" 2>/dev/null || true + cp -r build/assets "$DIST/" 2>/dev/null || true + cp -r build/share "$DIST/" 2>/dev/null || true + # Walk the dependency tree and copy every non-system DLL next to the exe. + ldd build/cardputer-emu.exe | awk '/mingw64/ {print $3}' | while read -r dll; do + cp "$dll" "$DIST/" + done + # Second pass for transitive deps of the copied DLLs. + for dll in "$DIST"/*.dll; do + ldd "$dll" 2>/dev/null | awk '/mingw64/ {print $3}' | while read -r d; do + [ -f "$DIST/$(basename "$d")" ] || cp "$d" "$DIST/" + done + done + - name: Package + run: | + cd dist && zip -rq ../M5CardputerZero-Emulator-Windows-x64.zip M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-windows-x64 + path: M5CardputerZero-Emulator-Windows-x64.zip - # ── Linux ─────────────────────────────────────────────────── + # ── Linux x64 ─────────────────────────────────────────────────────────────── linux: - runs-on: ubuntu-latest + name: linux-x64 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y libsdl2-dev libsdl2-image-dev libfreetype-dev libinput-dev libxkbcommon-dev pkg-config - - name: Build - run: | - mkdir build && cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make -j$(nproc) - - name: Package - run: | - mkdir -p dist/M5CardputerZero-Emulator - cp build/cardputer-emu dist/M5CardputerZero-Emulator/ - cp -r build/apps dist/M5CardputerZero-Emulator/ - cp -r build/assets dist/M5CardputerZero-Emulator/ - cp -r build/share dist/M5CardputerZero-Emulator/ - cd dist && tar czf ../M5CardputerZero-Emulator-Linux.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-linux - path: M5CardputerZero-Emulator-Linux.tar.gz + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsdl2-dev libsdl2-image-dev libfreetype-dev \ + libasound2-dev libinput-dev libxkbcommon-dev pkg-config + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(nproc) + - name: Package + run: | + mkdir -p dist/M5CardputerZero-Emulator + cp build/cardputer-emu dist/M5CardputerZero-Emulator/ + cp -r build/apps dist/M5CardputerZero-Emulator/ + cp -r build/assets dist/M5CardputerZero-Emulator/ + cp -r build/share dist/M5CardputerZero-Emulator/ + cd dist && tar czf ../M5CardputerZero-Emulator-Linux-x64.tar.gz M5CardputerZero-Emulator + - uses: actions/upload-artifact@v4 + with: + name: emulator-linux-x64 + path: M5CardputerZero-Emulator-Linux-x64.tar.gz - # ── Web (Emscripten/WASM) ─────────────────────────────────── + # ── Web (Emscripten/WASM) ─────────────────────────────────────────────────── web: - runs-on: ubuntu-latest + name: web-wasm + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Setup Emscripten - uses: mymindstorm/setup-emsdk@v14 - with: - version: 3.1.73 - - name: Build - run: | - mkdir build-web && cd build-web - emcmake cmake .. -DCMAKE_BUILD_TYPE=Release - emmake make -j$(nproc) - - name: Package - run: | - # Standalone emulator - mkdir -p dist-web - cp build-web/index.html dist-web/ - cp build-web/index.js dist-web/ - cp build-web/index.wasm dist-web/ - cp build-web/index.data dist-web/ - cd dist-web && zip -r ../M5CardputerZero-Emulator-Web.zip . - cd .. - # Playground (embeds emulator in iframe) - mkdir -p dist-playground/emulator - cp web-playground/index.html dist-playground/ - cp build-web/index.html dist-playground/emulator/ - cp build-web/index.js dist-playground/emulator/ - cp build-web/index.wasm dist-playground/emulator/ - cp build-web/index.data dist-playground/emulator/ - cd dist-playground && zip -r ../M5CardputerZero-Playground.zip . - - uses: actions/upload-artifact@v4 - with: - name: emulator-web - path: M5CardputerZero-Emulator-Web.zip - - uses: actions/upload-artifact@v4 - with: - name: playground - path: M5CardputerZero-Playground.zip + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Setup Emscripten + uses: mymindstorm/setup-emsdk@v14 + with: + version: 3.1.73 + - name: Build + run: | + mkdir build-web && cd build-web + emcmake cmake .. -DCMAKE_BUILD_TYPE=Release + emmake make -j$(nproc) + - name: Package + run: | + mkdir -p dist-web + cp build-web/index.html build-web/index.js build-web/index.wasm build-web/index.data dist-web/ + (cd dist-web && zip -rq ../M5CardputerZero-Emulator-Web.zip .) + mkdir -p dist-playground/emulator + cp web-playground/index.html dist-playground/ + cp build-web/index.html build-web/index.js build-web/index.wasm build-web/index.data dist-playground/emulator/ + (cd dist-playground && zip -rq ../M5CardputerZero-Playground.zip .) + - uses: actions/upload-artifact@v4 + with: + name: emulator-web + path: M5CardputerZero-Emulator-Web.zip + - uses: actions/upload-artifact@v4 + with: + name: playground + path: M5CardputerZero-Playground.zip - # ── Pre-release (runs after all 4 builds succeed) ─────────── + # ── Pre-release (after every native + web build is green) ─────────────────── release: - needs: [macos, windows, linux, web] - runs-on: ubuntu-latest + needs: [macos, macos-universal, windows, linux, web] + runs-on: ubuntu-22.04 if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: write steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Create pre-release - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="dev-$(date -u +%Y%m%d-%H%M%S)-$(echo $GITHUB_SHA | head -c 7)" - echo "Tag: $TAG" - - # Delete previous dev release if exists - gh release delete dev-latest --yes 2>/dev/null || true - git push origin :refs/tags/dev-latest 2>/dev/null || true - - # Create new pre-release - gh release create "$TAG" \ - --title "Dev Build $(date -u +%Y-%m-%d) (${GITHUB_SHA:0:7})" \ - --notes "Automated pre-release from commit ${GITHUB_SHA:0:7} - - **Downloads:** - - macOS (arm64): \`M5CardputerZero-Emulator-macOS.tar.gz\` - - Linux (x86_64): \`M5CardputerZero-Emulator-Linux.tar.gz\` - - Windows (x64, MinGW): \`M5CardputerZero-Emulator-Windows.tar.gz\` - - Web (WASM): \`M5CardputerZero-Emulator-Web.zip\` - - - **Playground** (hosted): \`M5CardputerZero-Playground.zip\` — unzip and open index.html + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Create pre-release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euxo pipefail + TAG="dev-$(date -u +%Y%m%d-%H%M%S)-${GITHUB_SHA:0:7}" + echo "Tag: $TAG" + gh release create "$TAG" \ + --title "Dev Build $(date -u +%Y-%m-%d) (${GITHUB_SHA:0:7})" \ + --notes "Automated pre-release from commit ${GITHUB_SHA:0:7} - _Windows build includes UserDemo only (APPLaunch requires Unix APIs)._" \ - --prerelease \ - artifacts/emulator-macos/M5CardputerZero-Emulator-macOS.tar.gz \ - artifacts/emulator-linux/M5CardputerZero-Emulator-Linux.tar.gz \ - artifacts/emulator-windows/M5CardputerZero-Emulator-Windows.tar.gz \ - artifacts/emulator-web/M5CardputerZero-Emulator-Web.zip \ - artifacts/playground/M5CardputerZero-Playground.zip + **Downloads:** + - macOS arm64: \`M5CardputerZero-Emulator-macOS-arm64.tar.gz\` + - macOS x64: \`M5CardputerZero-Emulator-macOS-x64.tar.gz\` + - macOS Universal: \`M5CardputerZero-Emulator-macOS-universal.tar.gz\` + - Linux x64: \`M5CardputerZero-Emulator-Linux-x64.tar.gz\` + - Windows x64 (MinGW): \`M5CardputerZero-Emulator-Windows-x64.zip\` + - Web (WASM): \`M5CardputerZero-Emulator-Web.zip\` + - Playground (hosted): \`M5CardputerZero-Playground.zip\`" \ + --prerelease \ + artifacts/emulator-macos-arm64/M5CardputerZero-Emulator-macOS-arm64.tar.gz \ + artifacts/emulator-macos-x64/M5CardputerZero-Emulator-macOS-x64.tar.gz \ + artifacts/emulator-macos-universal/M5CardputerZero-Emulator-macOS-universal.tar.gz \ + artifacts/emulator-linux-x64/M5CardputerZero-Emulator-Linux-x64.tar.gz \ + artifacts/emulator-windows-x64/M5CardputerZero-Emulator-Windows-x64.zip \ + artifacts/emulator-web/M5CardputerZero-Emulator-Web.zip \ + artifacts/playground/M5CardputerZero-Playground.zip diff --git a/.gitignore b/.gitignore index 274bcd5..d4db850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ build/ +build-*/ .DS_Store *.o *.a *.dylib -build-web/ + +# Local-only vendored headers (legacy from previous vendor layout). Picked up +# by cmake/third_party.cmake when present but not required for the build. +/vendor/nlohmann/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 441870e..afe2875 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,15 @@ set(CMAKE_C_STANDARD_REQUIRED ON) # Paths set(VENDOR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/M5CardputerZero-Launcher) -set(APPLAUNCH ${VENDOR_DIR}/projects/APPLaunch/main) -set(USERDEMO ${VENDOR_DIR}/projects/UserDemo/main) + +# Source-of-truth for which Launcher files / paths the emulator pulls in. +# Edit cmake/launcher_sources.cmake when the upstream submodule reshuffles things. +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +# Apply temporary patches against the vendor submodule. Each patch links to +# its tracking PR; entries are removed when upstream merges. +include(patches/apply_vendor_workarounds) +include(launcher_sources) +include(third_party) # --------------------------------------------------------------------------- # SDL2 + Freetype (skip for Emscripten — uses its own ports) @@ -112,14 +119,9 @@ if(NOT EMSCRIPTEN) option(EMU_SKIP_APPLAUNCH "Skip APPLaunch (Unix-only deps)" OFF) -# APPLaunch source files (used by both shared lib and Windows static link) -file(GLOB_RECURSE APPLAUNCH_UI_C ${APPLAUNCH}/ui/*.c) -file(GLOB_RECURSE APPLAUNCH_UI_CPP ${APPLAUNCH}/ui/*.cpp) -file(GLOB APPLAUNCH_KBD ${APPLAUNCH}/src/keyboard_input.c) - -# HAL platform sources (SDL for emulator builds) -file(GLOB APPLAUNCH_HAL_SDL_C ${APPLAUNCH}/hal/sdl/*.c) -file(GLOB APPLAUNCH_HAL_SDL_CPP ${APPLAUNCH}/hal/sdl/*.cpp) +# APPLaunch source files come from cmake/launcher_sources.cmake (already included above): +# APPLAUNCH_UI_C / APPLAUNCH_UI_CPP / APPLAUNCH_KBD +# APPLAUNCH_HAL_SDL_C / APPLAUNCH_HAL_SDL_CPP # --------------------------------------------------------------------------- # APP: APPLaunch shared lib (Unix only — Windows statically links into exe) @@ -141,11 +143,9 @@ target_include_directories(APPLaunch PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib ${CMAKE_CURRENT_SOURCE_DIR}/vendor - ${APPLAUNCH}/ui - ${APPLAUNCH}/include - ${APPLAUNCH} - ${APPLAUNCH}/hal + ${APPLAUNCH_INCLUDES} ) +target_link_libraries(APPLaunch PRIVATE emu::launcher_apply) target_compile_definitions(APPLaunch PRIVATE HAL_PLATFORM_SDL=1) if(WIN32) target_compile_options(APPLaunch PRIVATE @@ -169,16 +169,13 @@ endif() # NOT EMU_SKIP_APPLAUNCH # --------------------------------------------------------------------------- # APP: UserDemo.dylib +# (USERDEMO_UI_C / USERDEMO_INCLUDES come from cmake/launcher_sources.cmake) # --------------------------------------------------------------------------- -file(GLOB_RECURSE USERDEMO_UI_C ${USERDEMO}/ui/*.c) - add_library(UserDemo SHARED ${USERDEMO_UI_C}) target_include_directories(UserDemo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib - ${USERDEMO}/ui - ${USERDEMO}/include - ${USERDEMO} + ${USERDEMO_INCLUDES} ) if(MSVC) target_compile_options(UserDemo PRIVATE /FI${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat.h) @@ -205,7 +202,8 @@ if(WIN32 AND NOT EMU_SKIP_APPLAUNCH) target_include_directories(cardputer-emu PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/compat # win32_stubs.h, sys/queue.h ${CMAKE_CURRENT_SOURCE_DIR}/vendor - ${APPLAUNCH}/ui ${APPLAUNCH}/include ${APPLAUNCH}) + ${APPLAUNCH_INCLUDES}) + target_link_libraries(cardputer-emu PRIVATE emu::launcher_apply) if(WIN32) target_compile_options(cardputer-emu PRIVATE "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat_win.h" @@ -217,8 +215,7 @@ if(WIN32 AND NOT EMU_SKIP_APPLAUNCH) elseif(WIN32) # Fallback: UserDemo only add_executable(cardputer-emu src/main.cpp src/device_skin.cpp ${USERDEMO_UI_C}) - target_include_directories(cardputer-emu PRIVATE - ${USERDEMO}/ui ${USERDEMO}/include ${USERDEMO}) + target_include_directories(cardputer-emu PRIVATE ${USERDEMO_INCLUDES}) target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1) else() add_executable(cardputer-emu src/main.cpp src/device_skin.cpp) @@ -310,7 +307,7 @@ add_custom_command(TARGET cardputer-emu POST_BUILD $/apps/ COMMAND ${CMAKE_COMMAND} -E make_directory $/share/images COMMAND ${CMAKE_COMMAND} -E copy_directory - ${APPLAUNCH}/ui/images + ${APPLAUNCH_IMAGES_DIR} $/share/images COMMAND ${CMAKE_COMMAND} -E make_directory $/assets COMMAND ${CMAKE_COMMAND} -E copy @@ -332,16 +329,13 @@ endif() # NOT EMSCRIPTEN — end native targets # --------------------------------------------------------------------------- if(EMSCRIPTEN) # App sources statically linked (no dlopen in wasm) - file(GLOB_RECURSE APPLAUNCH_UI_C_WEB ${APPLAUNCH}/ui/*.c) - file(GLOB_RECURSE APPLAUNCH_UI_CPP_WEB ${APPLAUNCH}/ui/*.cpp) - file(GLOB APPLAUNCH_KBD_WEB ${APPLAUNCH}/src/keyboard_input.c) - + # Reuse APPLAUNCH_UI_C / APPLAUNCH_UI_CPP / APPLAUNCH_KBD from launcher_sources.cmake add_executable(cardputer-emu-web src/main_web.cpp src/device_skin.cpp - ${APPLAUNCH_UI_C_WEB} - ${APPLAUNCH_UI_CPP_WEB} - ${APPLAUNCH_KBD_WEB} + ${APPLAUNCH_UI_C} + ${APPLAUNCH_UI_CPP} + ${APPLAUNCH_KBD} ) target_include_directories(cardputer-emu-web PRIVATE @@ -350,10 +344,9 @@ if(EMSCRIPTEN) ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/lib ${CMAKE_CURRENT_SOURCE_DIR}/vendor - ${APPLAUNCH}/ui - ${APPLAUNCH}/include - ${APPLAUNCH} + ${APPLAUNCH_INCLUDES} ) + target_link_libraries(cardputer-emu-web PRIVATE emu::launcher_apply) target_compile_options(cardputer-emu-web PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat.h @@ -373,7 +366,7 @@ if(EMSCRIPTEN) -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=67108864 "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/assets/device_skin.png@/assets/device_skin.png" - "--preload-file=${APPLAUNCH}/ui/images@/share/images" + "--preload-file=${APPLAUNCH_IMAGES_DIR}@/share/images" --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/web/shell.html ) diff --git a/cmake/launcher_sources.cmake b/cmake/launcher_sources.cmake new file mode 100644 index 0000000..e48d3eb --- /dev/null +++ b/cmake/launcher_sources.cmake @@ -0,0 +1,94 @@ +# ───────────────────────────────────────────────────────────────────────────── +# launcher_sources.cmake +# ───────────────────────────────────────────────────────────────────────────── +# Single source of truth for "which Launcher files / paths the emulator pulls in". +# When the upstream M5CardputerZero-Launcher repo adds new components or moves +# files around, this file is the ONLY place that needs an update — the rest of +# CMakeLists.txt consumes the variables defined here. +# +# Inputs (must be set by caller before include()-ing this file): +# VENDOR_DIR — path to vendor/M5CardputerZero-Launcher +# +# Outputs: +# APPLAUNCH — APPLaunch project main/ dir +# USERDEMO — UserDemo project main/ dir +# APPLAUNCH_UI_C / _CPP — UI sources to compile +# APPLAUNCH_KBD — keyboard_input.c (path varies across launcher revs) +# APPLAUNCH_HAL_SDL_C / _CPP — SDL HAL sources +# USERDEMO_UI_C — UserDemo UI sources +# APPLAUNCH_INCLUDES — list of include dirs needed to compile APPLaunch +# APPLAUNCH_THIRDPARTY_INCLUDES — list of third-party include dirs (miniaudio, …) +# ───────────────────────────────────────────────────────────────────────────── + +if(NOT DEFINED VENDOR_DIR) + message(FATAL_ERROR "launcher_sources.cmake: VENDOR_DIR must be set before include()") +endif() + +set(APPLAUNCH ${VENDOR_DIR}/projects/APPLaunch/main) +set(USERDEMO ${VENDOR_DIR}/projects/UserDemo/main) + +# ── source globs ───────────────────────────────────────────────────────────── +file(GLOB_RECURSE APPLAUNCH_UI_C ${APPLAUNCH}/ui/*.c) +file(GLOB_RECURSE APPLAUNCH_UI_CPP ${APPLAUNCH}/ui/*.cpp) + +# keyboard_input.c moved between revs (src/ → hal/). Pick whichever exists. +set(APPLAUNCH_KBD "") +foreach(_kbd + ${APPLAUNCH}/hal/keyboard_input.c + ${APPLAUNCH}/src/keyboard_input.c) + if(EXISTS "${_kbd}") + list(APPEND APPLAUNCH_KBD "${_kbd}") + endif() +endforeach() + +file(GLOB APPLAUNCH_HAL_SDL_C ${APPLAUNCH}/hal/sdl/*.c) +file(GLOB APPLAUNCH_HAL_SDL_CPP ${APPLAUNCH}/hal/sdl/*.cpp) + +file(GLOB_RECURSE USERDEMO_UI_C ${USERDEMO}/ui/*.c) + +# ── include dirs ───────────────────────────────────────────────────────────── +set(APPLAUNCH_INCLUDES + ${APPLAUNCH}/ui + ${APPLAUNCH}/include + ${APPLAUNCH} + ${APPLAUNCH}/hal +) + +set(USERDEMO_INCLUDES + ${USERDEMO}/ui + ${USERDEMO}/include + ${USERDEMO} +) + +# ── runtime asset directories ──────────────────────────────────────────────── +# Where APPLaunch expects to find PNG/font assets at runtime. Path has moved +# between launcher revs (ui/images → APPLaunch/share/images), so probe for it. +set(APPLAUNCH_IMAGES_DIR "") +foreach(_candidate + ${VENDOR_DIR}/projects/APPLaunch/APPLaunch/share/images + ${APPLAUNCH}/ui/images) + if(IS_DIRECTORY "${_candidate}") + set(APPLAUNCH_IMAGES_DIR "${_candidate}") + break() + endif() +endforeach() +if(NOT APPLAUNCH_IMAGES_DIR) + message(WARNING + "launcher_sources.cmake: could not locate APPLaunch images directory. " + "The emulator will run but app icons will be missing.") +endif() + +# ── third-party includes shipped inside the launcher submodule ─────────────── +# These are kept in a separate variable so the emulator can decide per-target +# whether to expose them. +set(APPLAUNCH_THIRDPARTY_INCLUDES "") + +# Miniaudio (header-only; ui_events.c does #define MINIAUDIO_IMPLEMENTATION) +set(_miniaudio_inc ${VENDOR_DIR}/ext_components/Miniaudio/include) +if(EXISTS "${_miniaudio_inc}/miniaudio.h") + list(APPEND APPLAUNCH_THIRDPARTY_INCLUDES "${_miniaudio_inc}") +else() + message(WARNING + "launcher_sources.cmake: miniaudio.h not found at ${_miniaudio_inc}. " + "APPLaunch likely will not compile. Update the launcher submodule.") +endif() diff --git a/cmake/patches/apply_vendor_workarounds.cmake b/cmake/patches/apply_vendor_workarounds.cmake new file mode 100644 index 0000000..7646c3f --- /dev/null +++ b/cmake/patches/apply_vendor_workarounds.cmake @@ -0,0 +1,83 @@ +# ───────────────────────────────────────────────────────────────────────────── +# apply_vendor_workarounds.cmake +# ───────────────────────────────────────────────────────────────────────────── +# Temporary, narrowly-scoped patches against the launcher submodule so the +# emulator can build until the upstream HAL-decouple PR lands. +# +# These patches MODIFY the submodule working tree in place (not the index). +# `git -C vendor/M5CardputerZero-Launcher status` will therefore show modified +# files — that is expected. The patches are idempotent (re-running cmake on +# an already-patched tree is a no-op) and a `git -C vendor/... checkout .` +# undoes them safely. +# +# Why working-tree edits, not an include-path overlay: +# Overlay headers are shadowed by `#include "..."` quote-form lookups that +# resolve relative to the including file's directory before consulting -I +# paths. Most launcher headers are pulled in via such relative includes, so +# an overlay can't reliably win. Modifying the vendor file in place is the +# only mechanism that reaches every consumer. +# +# Each entry below MUST link to the upstream issue/PR that will retire it. +# When the upstream PR merges and the emulator bumps the submodule SHA, the +# corresponding entry below MUST be deleted in the same commit. The function +# prints "already-fixed upstream (skip)" once that's true, which is the cue. +# +# Inputs: +# VENDOR_DIR — path to vendor/M5CardputerZero-Launcher +# ───────────────────────────────────────────────────────────────────────────── + +if(NOT DEFINED VENDOR_DIR) + message(FATAL_ERROR "apply_vendor_workarounds.cmake: VENDOR_DIR must be set") +endif() + +# Helper: regex-replace inside a vendor file, writing back in place. Skipped +# silently if the input regex no longer matches (post-upstream-fix safe). +function(_emu_patch_vendor_file relpath match replace why_url) + set(_path "${VENDOR_DIR}/${relpath}") + if(NOT EXISTS "${_path}") + message(STATUS "[vendor-patch] skip (file gone): ${relpath}") + return() + endif() + file(READ "${_path}" _content) + string(REGEX MATCH "${match}" _hit "${_content}") + if(NOT _hit) + # Either upstream has fixed it, or we already patched it on a prior + # cmake run — either way nothing to do. + return() + endif() + string(REGEX REPLACE "${match}" "${replace}" _patched "${_content}") + file(WRITE "${_path}" "${_patched}") + message(STATUS "[vendor-patch] applied: ${relpath} (see ${why_url})") +endfunction() + +set(_any_patch_active FALSE) + +# ─── Workaround #1: ui_app_setup.hpp wrapped in `#if !defined(HAL_PLATFORM_SDL)` ─── +# The whole UISetupPage class is excluded under SDL builds, but ui_app_launch.cpp +# still references `page_v`. The class body is in fact already HAL- +# clean (uses hal_wifi_*, hal_battery_*); residual i2c ioctl spots are already +# inside #ifdef __linux__, so dropping the SDL guard is safe. +# +# Upstream fix: drop the SDL guard, route remaining raw syscalls through HAL. +# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD +_emu_patch_vendor_file( + "projects/APPLaunch/main/ui/components/page_app/ui_app_setup.hpp" + "#if !defined\\(HAL_PLATFORM_SDL\\)" + "#if 1 // emu-workaround: SDL guard disabled — apply_vendor_workarounds.cmake" + "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD" +) +set(_any_patch_active TRUE) + +# ─── Future entries follow the same pattern ────────────────────────────────── +# _emu_patch_vendor_file( +# "projects/APPLaunch/main/path/to/file.cpp" +# "regex-of-broken-line" +# "replacement" +# "https://github.com/.../pull/NNN" +# ) + +if(_any_patch_active) + message(STATUS "[vendor-patch] one or more launcher submodule files patched in working tree.") + message(STATUS "[vendor-patch] `git -C ${VENDOR_DIR} status` will show modifications — this is expected.") + message(STATUS "[vendor-patch] Run `git -C ${VENDOR_DIR} checkout .` to revert.") +endif() diff --git a/cmake/third_party.cmake b/cmake/third_party.cmake new file mode 100644 index 0000000..30aa6d4 --- /dev/null +++ b/cmake/third_party.cmake @@ -0,0 +1,82 @@ +# ───────────────────────────────────────────────────────────────────────────── +# third_party.cmake +# ───────────────────────────────────────────────────────────────────────────── +# Owns every third-party / compat dependency APPLaunch needs but the emulator's +# own source tree doesn't ship with directly. Centralized so that adding a new +# Launcher dependency = one entry here, not three target_include_directories +# calls scattered across CMakeLists.txt. +# +# Provides three INTERFACE libraries: +# emu::compat_stubs — thpool stub, sys/wait.h pass-through, nlohmann shim +# emu::miniaudio — miniaudio.h include + per-platform audio framework links +# emu::launcher_apply — convenience: apply both, plus emu_compat.h force-include +# +# Inputs expected: +# APPLAUNCH_THIRDPARTY_INCLUDES (from launcher_sources.cmake) +# ───────────────────────────────────────────────────────────────────────────── + +# ── compat stubs (thpool, sys/wait, nlohmann shim) ─────────────────────────── +add_library(emu_compat_stubs INTERFACE) +add_library(emu::compat_stubs ALIAS emu_compat_stubs) + +# IMPORTANT: src/compat houses Windows-only overrides for system headers +# (sys/wait.h, sys/queue.h). Adding it to the include path on macOS/Linux +# would shadow the real system headers and break compilation. Gate it. +if(WIN32) + target_include_directories(emu_compat_stubs INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/src/compat + ) +endif() + +# thpool stub from the launcher submodule's emu sub-project — header-only, +# safe to expose on every platform (declares a no-op threadpool). +set(_launcher_emu_compat_stubs + ${VENDOR_DIR}/projects/CardputerZero-Emulator/src/compat_stubs) +if(EXISTS "${_launcher_emu_compat_stubs}/thpool.h") + target_include_directories(emu_compat_stubs INTERFACE + ${_launcher_emu_compat_stubs} + ) +endif() + +# nlohmann (header-only, vendored by user under vendor/nlohmann/json.hpp if needed) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor/nlohmann/json.hpp) + target_include_directories(emu_compat_stubs INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/vendor + ) +endif() + +# ── miniaudio (header-only; .c TU does #define MINIAUDIO_IMPLEMENTATION) ───── +add_library(emu_miniaudio INTERFACE) +add_library(emu::miniaudio ALIAS emu_miniaudio) + +target_include_directories(emu_miniaudio INTERFACE + ${APPLAUNCH_THIRDPARTY_INCLUDES} +) + +# Platform audio backends — required because miniaudio's IMPLEMENTATION TU +# pulls in OS audio APIs. +if(APPLE) + target_link_libraries(emu_miniaudio INTERFACE + "-framework CoreAudio" + "-framework AudioToolbox" + "-framework AudioUnit" + "-framework CoreFoundation" + ) +elseif(UNIX AND NOT EMSCRIPTEN) + # Linux: ALSA (default), pulse optional. miniaudio compiles whatever is + # available; ALSA dev headers must be installed. + target_link_libraries(emu_miniaudio INTERFACE asound dl pthread m) +elseif(EMSCRIPTEN) + # Web: miniaudio supports Web Audio. No extra link needed beyond -pthread + # which is set globally. +endif() +# Windows (MinGW/MSVC): miniaudio uses WASAPI/DirectSound, no -l needed. + +# ── one-shot bundle for APPLaunch-style targets ────────────────────────────── +# Use this on any target that compiles ui_events.c + miniaudio + thpool. +add_library(emu_launcher_apply INTERFACE) +add_library(emu::launcher_apply ALIAS emu_launcher_apply) +target_link_libraries(emu_launcher_apply INTERFACE + emu::compat_stubs + emu::miniaudio +) From ddd9aa657ac2b341b7aa8f68f3b044db1087fccc Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Tue, 2 Jun 2026 22:38:09 +0800 Subject: [PATCH 2/7] fix(ci): align ifdef guards and link SDL HAL on Windows + Web Three follow-ups for the matrix CI: 1. cmake/patches: tighten ui_app_launch.cpp Linux-only registrations to also skip when HAL_PLATFORM_SDL is set. The launcher source uses #ifdef __linux__ to gate Linux-only page registrations, but the page header files themselves use #if !defined(HAL_PLATFORM_SDL). When emu builds on a Linux runner, both __linux__ and HAL_PLATFORM_SDL are defined: the registrations activate but the classes are excluded, producing 'UICameraPage' was not declared errors. Patch the cpp to use #if defined(__linux__) && !defined(HAL_PLATFORM_SDL) so the two guards align. Tracking PR TBD against Launcher. 2. CMakeLists: include APPLAUNCH_HAL_SDL_C/CPP sources in both the Windows static-link cardputer-emu target and the Emscripten web target. Both are statically linked (no dlopen / dynamic_lookup escape hatch), so they need every hal_* symbol resolved at link time. Linux + macOS shared dylib path was unaffected. --- CMakeLists.txt | 5 ++++- cmake/patches/apply_vendor_workarounds.cmake | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afe2875..6d44f69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,7 +198,8 @@ endif() if(WIN32 AND NOT EMU_SKIP_APPLAUNCH) # Windows: statically link APPLaunch (avoids DLL duplicate LVGL globals) add_executable(cardputer-emu src/main.cpp src/device_skin.cpp - ${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD}) + ${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD} + ${APPLAUNCH_HAL_SDL_C} ${APPLAUNCH_HAL_SDL_CPP}) target_include_directories(cardputer-emu PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/compat # win32_stubs.h, sys/queue.h ${CMAKE_CURRENT_SOURCE_DIR}/vendor @@ -336,6 +337,8 @@ if(EMSCRIPTEN) ${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD} + ${APPLAUNCH_HAL_SDL_C} + ${APPLAUNCH_HAL_SDL_CPP} ) target_include_directories(cardputer-emu-web PRIVATE diff --git a/cmake/patches/apply_vendor_workarounds.cmake b/cmake/patches/apply_vendor_workarounds.cmake index 7646c3f..bdf8a77 100644 --- a/cmake/patches/apply_vendor_workarounds.cmake +++ b/cmake/patches/apply_vendor_workarounds.cmake @@ -68,6 +68,25 @@ _emu_patch_vendor_file( ) set(_any_patch_active TRUE) +# ─── Workaround #2: ui_app_launch.cpp registers Linux-only pages whose ─────── +# ─── headers are SDL-guarded ───────────────────────────────────────────────── +# In the launcher source, ui_app_launch.cpp wraps Linux-only page registrations +# with `#ifdef __linux__`, but the page headers themselves are wrapped with +# `#if !defined(HAL_PLATFORM_SDL)`. The two guards don't align: when emu builds +# on a Linux host (real linux + SDL), `__linux__` is defined so the registration +# code activates, but `HAL_PLATFORM_SDL` is also defined so the page classes +# are excluded — and we hit `'UICameraPage' was not declared`. +# +# Tighten the guards in ui_app_launch.cpp to match the headers' SDL guard. +# Upstream fix: pick ONE guard convention (HAL_PLATFORM_SDL only) consistently. +# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD +_emu_patch_vendor_file( + "projects/APPLaunch/main/ui/components/ui_app_launch.cpp" + "#ifdef __linux__" + "#if defined(__linux__) && !defined(HAL_PLATFORM_SDL) // emu-workaround" + "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD" +) + # ─── Future entries follow the same pattern ────────────────────────────────── # _emu_patch_vendor_file( # "projects/APPLaunch/main/path/to/file.cpp" From c77a90ef00a5609956ae7a41cb624d1f0f5ab551 Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Tue, 2 Jun 2026 22:45:32 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix(ci):=20web/Windows=20static=20link=20?= =?UTF-8?q?=E2=80=94=20stub=20LV=5FEVENT=5FBATTERY=20+=20snprintf=20workar?= =?UTF-8?q?ound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups discovered after the previous CI round: 1. src/emu_launcher_stubs.c — provides LV_EVENT_BATTERY, LV_EVENT_WIFI_INFO, g_launch_thread_pool. Launcher's own main.cpp defines these globals on device, but the emulator runs its own src/main.cpp. The Unix shared-library path resolves them at dlopen time via dynamic_lookup; static-linked targets (Windows + Web) need real storage. Linked into both windows and web targets only. 2. cmake/patches: workaround #3 — hal_filesystem_sdl.cpp is missing for snprintf. Compiles on glibc/libc++ where transitively pulls it in, fails on MSYS2. Trivial header patch tracked by Launcher PR #48; will self-skip once that lands. Tracking PR (Launcher): https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 --- CMakeLists.txt | 4 ++- cmake/patches/apply_vendor_workarounds.cmake | 21 +++++++++++--- src/emu_launcher_stubs.c | 30 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/emu_launcher_stubs.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d44f69..fd3dadc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,7 +197,8 @@ endif() # --------------------------------------------------------------------------- if(WIN32 AND NOT EMU_SKIP_APPLAUNCH) # Windows: statically link APPLaunch (avoids DLL duplicate LVGL globals) - add_executable(cardputer-emu src/main.cpp src/device_skin.cpp + add_executable(cardputer-emu + src/main.cpp src/device_skin.cpp src/emu_launcher_stubs.c ${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD} ${APPLAUNCH_HAL_SDL_C} ${APPLAUNCH_HAL_SDL_CPP}) target_include_directories(cardputer-emu PRIVATE @@ -334,6 +335,7 @@ if(EMSCRIPTEN) add_executable(cardputer-emu-web src/main_web.cpp src/device_skin.cpp + src/emu_launcher_stubs.c ${APPLAUNCH_UI_C} ${APPLAUNCH_UI_CPP} ${APPLAUNCH_KBD} diff --git a/cmake/patches/apply_vendor_workarounds.cmake b/cmake/patches/apply_vendor_workarounds.cmake index bdf8a77..936a5c5 100644 --- a/cmake/patches/apply_vendor_workarounds.cmake +++ b/cmake/patches/apply_vendor_workarounds.cmake @@ -59,12 +59,12 @@ set(_any_patch_active FALSE) # inside #ifdef __linux__, so dropping the SDL guard is safe. # # Upstream fix: drop the SDL guard, route remaining raw syscalls through HAL. -# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD +# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 _emu_patch_vendor_file( "projects/APPLaunch/main/ui/components/page_app/ui_app_setup.hpp" "#if !defined\\(HAL_PLATFORM_SDL\\)" "#if 1 // emu-workaround: SDL guard disabled — apply_vendor_workarounds.cmake" - "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD" + "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" ) set(_any_patch_active TRUE) @@ -79,12 +79,25 @@ set(_any_patch_active TRUE) # # Tighten the guards in ui_app_launch.cpp to match the headers' SDL guard. # Upstream fix: pick ONE guard convention (HAL_PLATFORM_SDL only) consistently. -# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD +# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 _emu_patch_vendor_file( "projects/APPLaunch/main/ui/components/ui_app_launch.cpp" "#ifdef __linux__" "#if defined(__linux__) && !defined(HAL_PLATFORM_SDL) // emu-workaround" - "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/TBD" + "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" +) + +# ─── Workaround #3: hal_filesystem_sdl.cpp missing for snprintf ──── +# Builds fine on glibc/libc++ where / transitively pull +# in , but fails on MSYS2 MinGW with `'snprintf' was not declared`. +# Trivial header fix. +# +# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 +_emu_patch_vendor_file( + "projects/APPLaunch/main/hal/sdl/hal_filesystem_sdl.cpp" + "#include \"../hal_filesystem.h\"\n#include " + "#include \"../hal_filesystem.h\"\n#include // snprintf — emu-workaround\n#include " + "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" ) # ─── Future entries follow the same pattern ────────────────────────────────── diff --git a/src/emu_launcher_stubs.c b/src/emu_launcher_stubs.c new file mode 100644 index 0000000..ccbc7bf --- /dev/null +++ b/src/emu_launcher_stubs.c @@ -0,0 +1,30 @@ +/* + * emu_launcher_stubs.c — definitions of symbols that Launcher's main.cpp + * provides on the device but the emulator can't pull in (the emulator runs + * its own src/main.cpp, not Launcher's). + * + * Only linked into statically-built emulator targets (Windows, Web). The + * Unix shared-library path resolves these at dlopen time inside libAPPLaunch + * via dynamic_lookup / --unresolved-symbols=ignore-all, so it doesn't need + * this TU. + * + * If Launcher introduces new globals normally defined by its main.cpp, add + * them here. + */ + +#include + +/* Custom LVGL event codes registered at runtime by Launcher's main.cpp. + * The headers declare them as `extern volatile uint32_t`, so the loader + * just needs zero-initialized storage. They're populated via + * lv_event_register_id() before APPLaunch fires its first battery/wifi + * event — code paths the emulator stub'd HAL never actually drives. */ +volatile uint32_t LV_EVENT_BATTERY = 0; +volatile uint32_t LV_EVENT_WIFI_INFO = 0; + +/* Launcher uses a global thread pool for off-loading startup work. With our + * thpool stub (cmake/third_party.cmake → emu::compat_stubs), thpool_init() + * returns NULL and thpool_add_work() runs the callback synchronously, so a + * plain NULL is the correct emulator value. */ +typedef void *threadpool; +threadpool g_launch_thread_pool = (threadpool)0; From 3f35c13f095bd4c81901f259bf5eb4ce59b8de2f Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Tue, 2 Jun 2026 22:55:28 +0800 Subject: [PATCH 4/7] ci(windows): link ws2_32 for gethostname in ui_app_hack.hpp Launcher's ui_app_hack.hpp includes on Windows and calls gethostname() in build_netinfo_page(). Statically-linked Windows emu needs ws2_32 to resolve __imp_gethostname; macOS/Linux were unaffected because gethostname is in libc there. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd3dadc..1f24d9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,6 +250,8 @@ elseif(MSVC) elseif(WIN32) # MinGW: no whole-archive (app is static-linked, no DLL symbol export needed) target_link_libraries(cardputer-emu PRIVATE lvgl lvgl_thorvg) + # WinSock for gethostname() in launcher's ui_app_hack.hpp. + target_link_libraries(cardputer-emu PRIVATE ws2_32) else() # Linux: --whole-archive for dlopen'd apps target_link_libraries(cardputer-emu PRIVATE From 46300949ac5f22d53bf33bb065d758b55c993b8d Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Wed, 3 Jun 2026 00:17:51 +0800 Subject: [PATCH 5/7] Bump launcher submodule to master, retire all vendor workarounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Launcher PR #48 merged: HAL_PLATFORM_SDL guard removed from ui_app_setup.hpp, ui_app_launch.cpp gates Linux-only registrations with the right (linux && !sdl) combination, hal_filesystem_sdl now includes , and hal_wifi_disconnect has an SDL stub. - Bump vendor/M5CardputerZero-Launcher: cff863b → 92d4434 - Drop all three workaround entries in cmake/patches; the file remains as the documented hook for the next time we need one. - Add page_app.h generation to cmake/launcher_sources.cmake. Launcher's SConstruct runs generate_page_app_includes.py at build time to produce that header from the .hpp files in page_app/. The launcher repo gitignores it. We replicate the generator in pure CMake so a vanilla cmake build works without invoking SCons. --- cmake/launcher_sources.cmake | 25 +++++++++ cmake/patches/apply_vendor_workarounds.cmake | 57 ++++---------------- vendor/M5CardputerZero-Launcher | 2 +- 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/cmake/launcher_sources.cmake b/cmake/launcher_sources.cmake index e48d3eb..a3e0bef 100644 --- a/cmake/launcher_sources.cmake +++ b/cmake/launcher_sources.cmake @@ -27,6 +27,31 @@ endif() set(APPLAUNCH ${VENDOR_DIR}/projects/APPLaunch/main) set(USERDEMO ${VENDOR_DIR}/projects/UserDemo/main) +# ── generate page_app.h ────────────────────────────────────────────────────── +# Launcher's SConstruct runs generate_page_app_includes.py at build time to +# produce ui/components/page_app.h from the .hpp files in ui/components/page_app/. +# That file is .gitignore'd in the launcher repo (so writing it doesn't dirty +# `git status`). We replicate the generator in pure CMake here so a vanilla +# `cmake .. && make` works without invoking SCons. +set(_page_app_dir ${APPLAUNCH}/ui/components/page_app) +set(_page_app_h ${APPLAUNCH}/ui/components/page_app.h) +if(IS_DIRECTORY "${_page_app_dir}") + file(GLOB _page_app_headers + RELATIVE ${APPLAUNCH}/ui/components + CONFIGURE_DEPENDS + ${_page_app_dir}/*.hpp + ) + list(SORT _page_app_headers) + if(_page_app_headers) + file(WRITE "${_page_app_h}" "#pragma once\n\n") + foreach(_h ${_page_app_headers}) + file(APPEND "${_page_app_h}" "#include \"${_h}\"\n") + endforeach() + else() + message(WARNING "launcher_sources.cmake: page_app/ exists but contains no .hpp files") + endif() +endif() + # ── source globs ───────────────────────────────────────────────────────────── file(GLOB_RECURSE APPLAUNCH_UI_C ${APPLAUNCH}/ui/*.c) file(GLOB_RECURSE APPLAUNCH_UI_CPP ${APPLAUNCH}/ui/*.cpp) diff --git a/cmake/patches/apply_vendor_workarounds.cmake b/cmake/patches/apply_vendor_workarounds.cmake index 936a5c5..7cf234c 100644 --- a/cmake/patches/apply_vendor_workarounds.cmake +++ b/cmake/patches/apply_vendor_workarounds.cmake @@ -52,61 +52,24 @@ endfunction() set(_any_patch_active FALSE) -# ─── Workaround #1: ui_app_setup.hpp wrapped in `#if !defined(HAL_PLATFORM_SDL)` ─── -# The whole UISetupPage class is excluded under SDL builds, but ui_app_launch.cpp -# still references `page_v`. The class body is in fact already HAL- -# clean (uses hal_wifi_*, hal_battery_*); residual i2c ioctl spots are already -# inside #ifdef __linux__, so dropping the SDL guard is safe. +# ─── No active workarounds ─────────────────────────────────────────────────── +# All previous entries (#1 ui_app_setup SDL guard, #2 ui_app_launch __linux__ +# guards, #3 hal_filesystem_sdl missing stdio.h) were resolved upstream by +# https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 and the +# submodule has been bumped past it. # -# Upstream fix: drop the SDL guard, route remaining raw syscalls through HAL. -# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 -_emu_patch_vendor_file( - "projects/APPLaunch/main/ui/components/page_app/ui_app_setup.hpp" - "#if !defined\\(HAL_PLATFORM_SDL\\)" - "#if 1 // emu-workaround: SDL guard disabled — apply_vendor_workarounds.cmake" - "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" -) -set(_any_patch_active TRUE) +# This file remains as the documented hook for the next time we need a +# narrowly-scoped patch against the launcher submodule. Each new entry MUST +# link to the upstream tracking PR that will retire it. -# ─── Workaround #2: ui_app_launch.cpp registers Linux-only pages whose ─────── -# ─── headers are SDL-guarded ───────────────────────────────────────────────── -# In the launcher source, ui_app_launch.cpp wraps Linux-only page registrations -# with `#ifdef __linux__`, but the page headers themselves are wrapped with -# `#if !defined(HAL_PLATFORM_SDL)`. The two guards don't align: when emu builds -# on a Linux host (real linux + SDL), `__linux__` is defined so the registration -# code activates, but `HAL_PLATFORM_SDL` is also defined so the page classes -# are excluded — and we hit `'UICameraPage' was not declared`. -# -# Tighten the guards in ui_app_launch.cpp to match the headers' SDL guard. -# Upstream fix: pick ONE guard convention (HAL_PLATFORM_SDL only) consistently. -# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 -_emu_patch_vendor_file( - "projects/APPLaunch/main/ui/components/ui_app_launch.cpp" - "#ifdef __linux__" - "#if defined(__linux__) && !defined(HAL_PLATFORM_SDL) // emu-workaround" - "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" -) - -# ─── Workaround #3: hal_filesystem_sdl.cpp missing for snprintf ──── -# Builds fine on glibc/libc++ where / transitively pull -# in , but fails on MSYS2 MinGW with `'snprintf' was not declared`. -# Trivial header fix. -# -# Tracking PR: https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48 -_emu_patch_vendor_file( - "projects/APPLaunch/main/hal/sdl/hal_filesystem_sdl.cpp" - "#include \"../hal_filesystem.h\"\n#include " - "#include \"../hal_filesystem.h\"\n#include // snprintf — emu-workaround\n#include " - "https://github.com/CardputerZero/M5CardputerZero-Launcher/pull/48" -) - -# ─── Future entries follow the same pattern ────────────────────────────────── +# ─── New entries follow the same pattern ───────────────────────────────────── # _emu_patch_vendor_file( # "projects/APPLaunch/main/path/to/file.cpp" # "regex-of-broken-line" # "replacement" # "https://github.com/.../pull/NNN" # ) +# set(_any_patch_active TRUE) if(_any_patch_active) message(STATUS "[vendor-patch] one or more launcher submodule files patched in working tree.") diff --git a/vendor/M5CardputerZero-Launcher b/vendor/M5CardputerZero-Launcher index cff863b..92d4434 160000 --- a/vendor/M5CardputerZero-Launcher +++ b/vendor/M5CardputerZero-Launcher @@ -1 +1 @@ -Subproject commit cff863b29472c45306f67b9ad550c4add59accfb +Subproject commit 92d4434ef110f159b6f27769dea5b7f26ca292f1 From cb7a8197c1dad68ca4880649bd2bde32d8e1d5c9 Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Wed, 3 Jun 2026 00:23:30 +0800 Subject: [PATCH 6/7] ci(web/windows): define HAL_PLATFORM_SDL=1 on static-link targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared APPLaunch dylib gets HAL_PLATFORM_SDL=1 via target_compile_ definitions, but the web (cardputer-emu-web) and Windows static-link (cardputer-emu) targets that compile the same source files inline did not — they fell back to the un-SDL'd code paths and tried to include from ui_app_camera.hpp. Set HAL_PLATFORM_SDL=1 explicitly on both targets. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f24d9a..d003759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,7 +211,7 @@ if(WIN32 AND NOT EMU_SKIP_APPLAUNCH) "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat_win.h" -Wno-c++11-narrowing) endif() - target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1) + target_compile_definitions(cardputer-emu PRIVATE EMU_STATIC_APP=1 HAL_PLATFORM_SDL=1) # MinGW: allow multiple definitions (APPLaunch overrides LVGL's SDL keyboard) target_link_options(cardputer-emu PRIVATE -Wl,--allow-multiple-definition) elseif(WIN32) @@ -354,6 +354,7 @@ if(EMSCRIPTEN) ${APPLAUNCH_INCLUDES} ) target_link_libraries(cardputer-emu-web PRIVATE emu::launcher_apply) + target_compile_definitions(cardputer-emu-web PRIVATE HAL_PLATFORM_SDL=1) target_compile_options(cardputer-emu-web PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/src/emu_compat.h From dbd220505247e4e6e2e6f25193b38648d2289548 Mon Sep 17 00:00:00 2001 From: LiHaohua Date: Wed, 3 Jun 2026 12:04:31 +0800 Subject: [PATCH 7/7] ci(macos): drop x64 + universal jobs, ship arm64 only GitHub-hosted macos-13 (Intel) free runners have been queueing for 1+ hour, blocking PR completion. Apple Silicon shipped in 2020; users still on Intel Macs without Rosetta 2 are negligible for this project. Drop the x64 build (which fed the universal lipo) and ship a single arm64 tarball. If x64 demand surfaces, build it offline from a developer machine and attach to the release manually, or revisit the matrix once GitHub runner availability improves. --- .github/workflows/build.yml | 79 +++++++------------------------------ 1 file changed, 15 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 190e216..3d2d7a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,17 +9,16 @@ on: jobs: # ── macOS arm64 + x64 (matrix) ────────────────────────────────────────────── - macos: - name: macos-${{ matrix.arch }} - strategy: - fail-fast: false - matrix: - include: - - arch: arm64 - runner: macos-14 - - arch: x64 - runner: macos-13 - runs-on: ${{ matrix.runner }} + # ── macOS arm64 ───────────────────────────────────────────────────────────── + # We ship arm64 only on macOS. Apple Silicon shipped in 2020; users on Intel + # Macs without Rosetta 2 are vanishingly rare for a hobby project. The + # GitHub-hosted macos-13 (Intel) free runner queue is currently 1h+, which + # blocked PRs for hours; building on macos-14 (arm64) keeps CI predictable. + # If we ever need x64, build it offline from a developer machine and attach + # to the release manually, or revisit the matrix. + macos-arm64: + name: macos-arm64 + runs-on: macos-14 steps: - uses: actions/checkout@v5 with: @@ -51,58 +50,14 @@ jobs: -x "$DIST/apps/libAPPLaunch.dylib" \ -x "$DIST/apps/libUserDemo.dylib" \ -d "$DIST/libs" -p "@executable_path/libs/" || \ - echo "[warn] dylibbundler returned non-zero (continuing — dependencies may already resolve)" + echo "[warn] dylibbundler returned non-zero (continuing — deps may resolve already)" - name: Package run: | - cd dist && tar czf ../M5CardputerZero-Emulator-macOS-${{ matrix.arch }}.tar.gz M5CardputerZero-Emulator - - uses: actions/upload-artifact@v4 - with: - name: emulator-macos-${{ matrix.arch }} - path: M5CardputerZero-Emulator-macOS-${{ matrix.arch }}.tar.gz - - # ── macOS Universal (lipo merge of arm64 + x64) ──────────────────────────── - macos-universal: - name: macos-universal - needs: macos - runs-on: macos-14 - steps: - - uses: actions/checkout@v5 - - uses: actions/download-artifact@v4 - with: - pattern: emulator-macos-* - path: artifacts - - name: Build universal bundle via lipo - run: | - set -euxo pipefail - mkdir -p arm64 x64 universal/M5CardputerZero-Emulator - tar xzf artifacts/emulator-macos-arm64/M5CardputerZero-Emulator-macOS-arm64.tar.gz -C arm64 - tar xzf artifacts/emulator-macos-x64/M5CardputerZero-Emulator-macOS-x64.tar.gz -C x64 - - ARM=arm64/M5CardputerZero-Emulator - X64=x64/M5CardputerZero-Emulator - UNI=universal/M5CardputerZero-Emulator - - # Mirror arm64 layout, then lipo-merge every Mach-O. - rsync -a "$ARM/" "$UNI/" - while IFS= read -r -d '' rel; do - file_arm="$ARM/$rel" - file_x64="$X64/$rel" - file_uni="$UNI/$rel" - if [ -f "$file_x64" ] && file "$file_arm" | grep -q "Mach-O"; then - lipo -create "$file_arm" "$file_x64" -output "$file_uni" 2>/dev/null \ - || cp "$file_arm" "$file_uni" - fi - done < <(cd "$ARM" && find . -type f -print0) - - # Sanity: confirm executable is now fat - file "$UNI/cardputer-emu" - lipo -info "$UNI/cardputer-emu" - - cd universal && tar czf ../M5CardputerZero-Emulator-macOS-universal.tar.gz M5CardputerZero-Emulator + cd dist && tar czf ../M5CardputerZero-Emulator-macOS-arm64.tar.gz M5CardputerZero-Emulator - uses: actions/upload-artifact@v4 with: - name: emulator-macos-universal - path: M5CardputerZero-Emulator-macOS-universal.tar.gz + name: emulator-macos-arm64 + path: M5CardputerZero-Emulator-macOS-arm64.tar.gz # ── Windows x64 (MinGW-w64) ──────────────────────────────────────────────── windows: @@ -229,7 +184,7 @@ jobs: # ── Pre-release (after every native + web build is green) ─────────────────── release: - needs: [macos, macos-universal, windows, linux, web] + needs: [macos-arm64, windows, linux, web] runs-on: ubuntu-22.04 if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: @@ -252,16 +207,12 @@ jobs: **Downloads:** - macOS arm64: \`M5CardputerZero-Emulator-macOS-arm64.tar.gz\` - - macOS x64: \`M5CardputerZero-Emulator-macOS-x64.tar.gz\` - - macOS Universal: \`M5CardputerZero-Emulator-macOS-universal.tar.gz\` - Linux x64: \`M5CardputerZero-Emulator-Linux-x64.tar.gz\` - Windows x64 (MinGW): \`M5CardputerZero-Emulator-Windows-x64.zip\` - Web (WASM): \`M5CardputerZero-Emulator-Web.zip\` - Playground (hosted): \`M5CardputerZero-Playground.zip\`" \ --prerelease \ artifacts/emulator-macos-arm64/M5CardputerZero-Emulator-macOS-arm64.tar.gz \ - artifacts/emulator-macos-x64/M5CardputerZero-Emulator-macOS-x64.tar.gz \ - artifacts/emulator-macos-universal/M5CardputerZero-Emulator-macOS-universal.tar.gz \ artifacts/emulator-linux-x64/M5CardputerZero-Emulator-Linux-x64.tar.gz \ artifacts/emulator-windows-x64/M5CardputerZero-Emulator-Windows-x64.zip \ artifacts/emulator-web/M5CardputerZero-Emulator-Web.zip \