Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ jobs:
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: build/html/html
clean-exclude: demo/ # preserve the WASM demo published by wasm-demo.yml
108 changes: 108 additions & 0 deletions .github/workflows/wasm-demo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: WASM Demo

# Builds the self-contained WebAssembly bank GUI (examples/bank/gui_wasm) and
# publishes it to GitHub Pages under /demo/, alongside the Doxygen docs at the
# site root. Single-threaded Qt-for-WASM ⇒ no SharedArrayBuffer ⇒ no COOP/COEP
# headers ⇒ plain Pages hosting works.

on:
push:
branches:
- master
paths:
- 'examples/bank/**'
- 'include/morph/**'
- '.github/workflows/wasm-demo.yml'
pull_request:
branches:
- master
paths:
- 'examples/bank/**'
- 'include/morph/**'
- '.github/workflows/wasm-demo.yml'

concurrency:
group: wasm-demo-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write

env:
QT_VERSION: 6.8.3
EMSDK_VERSION: 3.1.56 # the emscripten Qt 6.8 was built against

jobs:
build-wasm:
name: Build & deploy WASM demo
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install build tools
run: |
sudo apt-get update -q
sudo apt-get install -y ninja-build

# aqtinstall gives a matched host + wasm Qt pair (same cmake glue), so no
# host/target version skew.
- name: Install Qt (host desktop)
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: linux
target: desktop
arch: linux_gcc_64
dir: ${{ runner.temp }}/qt

# Qt 6.7+ ships WebAssembly under host=all_os / target=wasm (not
# linux/desktop). The matching host desktop Qt above provides the tools.
- name: Install Qt (wasm, single-threaded)
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: all_os
target: wasm
arch: wasm_singlethread
dir: ${{ runner.temp }}/qt

- name: Set up emsdk
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ env.EMSDK_VERSION }}
actions-cache-folder: emsdk-cache

- name: Configure
run: |
export EM_CACHE="$PWD/.emcache"
mkdir -p "$EM_CACHE"
HOST=${{ runner.temp }}/qt/Qt/${{ env.QT_VERSION }}/gcc_64
WASM=${{ runner.temp }}/qt/Qt/${{ env.QT_VERSION }}/wasm_singlethread
# The all_os/wasm package extracts its scripts without the exec bit.
chmod +x "$WASM"/bin/* || true
"$WASM/bin/qt-cmake" -S . -B build-wasm -G Ninja \
-DQT_HOST_PATH="$HOST" \
-DMORPH_BUILD_EXAMPLES=ON -DMORPH_BUILD_BANK_EXAMPLE=ON \
-DMORPH_BUILD_BANK_GUI=ON -DMORPH_BUILD_TESTS=OFF

- name: Build
run: |
export EM_CACHE="$PWD/.emcache"
cmake --build build-wasm --target bank_gui_wasm

- name: Stage bundle
run: |
mkdir -p site
BIN=build-wasm/examples/bank/gui_wasm
cp "$BIN"/bank_gui_wasm.js "$BIN"/bank_gui_wasm.wasm "$BIN"/qtloader.js site/
# Serve the Qt loader page as the directory index.
cp "$BIN"/bank_gui_wasm.html site/index.html

- name: Deploy to GitHub Pages (/demo)
if: github.ref == 'refs/heads/master'
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: site
target-folder: demo
clean: false # leave the Doxygen docs at the site root untouched
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/build/
/build-bank/
/build-wasm/
/out/
*.db
/.cache/
/compile_commands.json
*.user
Expand Down
33 changes: 23 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)

option(MORPH_BUILD_TESTS "Build tests" ON)
option(MORPH_BUILD_EXAMPLES "Build examples" ON)
option(MORPH_BUILD_BANK_EXAMPLE "Build the SQLite/Lightweight bank example (heavy deps)" OFF)
option(MORPH_BUILD_BANK_GUI "Build the Qt 6 GUI for the bank example" OFF)
option(MORPH_BUILD_QT "Build Qt6 WebSocket backend and tests" OFF)
option(MORPH_BUILD_DOCUMENTATION "Build doxygen docs" OFF)

Expand All @@ -33,7 +35,10 @@ if(NOT glaze_FOUND)
FetchContent_MakeAvailable(glaze)
endif()

if(NOT WIN32)
# Emscripten's single-threaded build has no pthreads; requiring Threads there
# would force -pthread (SharedArrayBuffer), which we deliberately avoid so the
# WASM bundle can be served from plain GitHub Pages with no COOP/COEP headers.
if(NOT WIN32 AND NOT EMSCRIPTEN)
find_package(Threads REQUIRED)
endif()

Expand All @@ -46,7 +51,7 @@ target_include_directories(morph INTERFACE
$<INSTALL_INTERFACE:include>
)
target_link_libraries(morph INTERFACE glaze::glaze)
if(NOT WIN32)
if(NOT WIN32 AND NOT EMSCRIPTEN)
target_link_libraries(morph INTERFACE Threads::Threads)
endif()

Expand Down Expand Up @@ -77,15 +82,23 @@ target_sources(morph

# ── Demo executable ──────────────────────────────────────────────────────────
if(MORPH_BUILD_EXAMPLES)
add_executable(morph_example src/main.cpp)
target_link_libraries(morph_example PRIVATE morph)
apply_warnings(morph_example)

if(DEFINED AF_SANITIZER)
apply_sanitizers(morph_example ${AF_SANITIZER})
# The console demo uses the thread-pool executor; skip it for the
# single-threaded WASM build (which only wants the bank GUI target).
if(NOT EMSCRIPTEN)
add_executable(morph_example src/main.cpp)
target_link_libraries(morph_example PRIVATE morph)
apply_warnings(morph_example)

if(DEFINED AF_SANITIZER)
apply_sanitizers(morph_example ${AF_SANITIZER})
endif()
if(AF_COVERAGE)
apply_coverage(morph_example)
endif()
endif()
if(AF_COVERAGE)
apply_coverage(morph_example)

if(MORPH_BUILD_BANK_EXAMPLE)
add_subdirectory(examples/bank)
endif()
endif()

Expand Down
109 changes: 109 additions & 0 deletions examples/bank/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# SPDX-License-Identifier: Apache-2.0
#
# Bank example: a feature-rich demo app built on morph (async UI<->model
# bridge) with a SQLite persistence layer provided by the LASTRADA Lightweight
# ORM. Intended to be enabled from the top-level build with
# -DMORPH_BUILD_BANK_EXAMPLE=ON (it pulls a heavy dependency tree, so it is off
# by default).

cmake_minimum_required(VERSION 3.25)

# Allow standalone configuration (examples/bank as its own project) as well as
# being add_subdirectory()'d from the morph root.
if(NOT TARGET morph::morph)
message(FATAL_ERROR
"examples/bank expects the morph::morph target. Configure from the repository root "
"with -DMORPH_BUILD_BANK_EXAMPLE=ON instead of configuring examples/bank directly.")
endif()

# ── WebAssembly build ────────────────────────────────────────────────────────
# In an Emscripten configure, the native persistence layer (Lightweight ORM over
# ODBC) cannot exist in the browser, so the whole native stack — Lightweight,
# bank_lib, the CLI and the tests — is skipped. Only the self-contained WASM GUI
# (its own in-memory backend) is built. See gui_wasm/.
if(EMSCRIPTEN)
if(MORPH_BUILD_BANK_GUI)
add_subdirectory(gui_wasm)
endif()
return()
endif()

include(FetchContent)

# ── Lightweight ORM (SQLite via ODBC) ────────────────────────────────────────
# Lightweight resolves reflection-cpp / stdexec via CPM and finds yaml-cpp /
# Catch2 / libzip as system CONFIG packages.
set(LIGHTWEIGHT_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(LIGHTWEIGHT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(LIGHTWEIGHT_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(LIGHTWEIGHT_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE)

FetchContent_Declare(Lightweight
GIT_REPOSITORY https://github.com/LASTRADA-Software/Lightweight.git
GIT_TAG v0.20260625.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(Lightweight)

# ── Bank domain library ──────────────────────────────────────────────────────
add_library(bank_lib STATIC
src/core/money.cpp
src/db/schema.cpp
src/models/account_model.cpp
src/models/auth_model.cpp
src/models/transaction_model.cpp
src/models/payee_model.cpp
src/models/payment_model.cpp
src/models/card_model.cpp
src/models/loan_model.cpp
src/models/budget_model.cpp
src/models/notification_model.cpp
src/models/statement_model.cpp
src/app/app.cpp
)
target_include_directories(bank_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(bank_lib PUBLIC morph::morph Lightweight::Lightweight)
target_compile_features(bank_lib PUBLIC cxx_std_23)
# Note: deliberately NOT calling apply_warnings() here — the third-party ORM
# headers are not -Werror clean and would fail the build.

# ── CLI driver ───────────────────────────────────────────────────────────────
add_executable(bank_cli src/cli/main.cpp)
target_link_libraries(bank_cli PRIVATE bank_lib)
target_compile_features(bank_cli PRIVATE cxx_std_23)

# ── Qt 6 GUI (opt-in) ────────────────────────────────────────────────────────
if(MORPH_BUILD_BANK_GUI)
add_subdirectory(gui)
endif()

# ── Tests ────────────────────────────────────────────────────────────────────
if(MORPH_BUILD_TESTS)
find_package(Catch2 3 CONFIG QUIET)
if(NOT Catch2_FOUND)
message(WARNING "Catch2 not found; bank example tests will not be built.")
else()
add_executable(bank_tests
tests/test_account.cpp
tests/test_auth.cpp
tests/test_transaction.cpp
tests/test_payee.cpp
tests/test_payment.cpp
tests/test_card.cpp
tests/test_loan.cpp
tests/test_budget.cpp
tests/test_notification.cpp
tests/test_statement.cpp
tests/test_remote.cpp
tests/test_offline.cpp
tests/test_relations.cpp
)
target_include_directories(bank_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
target_link_libraries(bank_tests PRIVATE bank_lib Catch2::Catch2WithMain)
target_compile_features(bank_tests PRIVATE cxx_std_23)

list(APPEND CMAKE_MODULE_PATH ${Catch2_DIR})
include(Catch)
catch_discover_tests(bank_tests DISCOVERY_MODE PRE_TEST)
endif()
endif()
Loading
Loading