Skip to content

Commit f51204b

Browse files
lefticusclaude
andauthored
Add wasm support (#127)
* Add WebAssembly build infrastructure Port WASM infrastructure from travels project to enable building the intro executable for web browsers using Emscripten. Core WASM infrastructure: - cmake/Emscripten.cmake: Emscripten/WASM build configuration - Detects Emscripten builds and disables incompatible features - Configures pthread support (required by FTXUI) - Provides myproject_configure_wasm_target() function - Optional resource embedding support via INTRO_RESOURCES_DIR - web/shell.html: Custom HTML shell with xterm.js terminal - Dark-themed terminal UI with WebGL rendering - Handles stdin/stdout/stderr buffering for WASM - Supports ?version URL parameter - web/coi-serviceworker.min.js: Service worker for COOP/COEP headers - Required for SharedArrayBuffer/pthread support on GitHub Pages - .github/workflows/wasm.yml: CI/CD workflow - Builds WASM on all CI runs - Deploys to GitHub Pages for main/develop branches and tags CMake integration: - CMakeLists.txt: Include Emscripten.cmake before ProjectOptions - ProjectOptions.cmake: Add EMSCRIPTEN detection to disable sanitizers - src/ftxui_sample/CMakeLists.txt: Apply WASM configuration to intro Critical bug fixes included: - cmake/Hardening.cmake: Fix CMake list handling and global flag propagation (proper add_compile_options() usage) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Improve URL parameter to CLI argument handling in shell.html - Support any URL parameter as command-line argument - Smart prefix detection: single-char → -, multi-char → -- - Handle both boolean flags and parameters with values - Add visible usage guide on web page explaining conversion rules Examples: ?version → --version ?v → -v ?file=test.txt → --file test.txt ?verbose&config=app.cfg → --verbose --config app.cfg 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Upgrade xterm to 6.0.0 - Update xterm from 5.3.0 to 6.0.0 - Update addon-fit from 0.8.0 to 0.10.0 - Update addon-webgl from 0.16.0 to 0.18.0 - Use new @xterm scoped package names for addons 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix xterm 6.0 ES modules with proper initialization order Fixes multiple critical issues that prevented xterm 6.0 from working: 1. Package naming: Use @xterm/xterm instead of deprecated xterm package - Changed CSS URL to @xterm/xterm@6.0.0/css/xterm.css - Changed import map to use @xterm/xterm - Changed import statement to match 2. Module initialization timing: Define Module synchronously - Module must exist BEFORE Emscripten's {{{ SCRIPT }}} loads - ES modules execute asynchronously, causing "Module should not be replaced during async compilation" error - Solution: Define Module in regular <script> tag, then ES module hooks into Module.preRun/postRun arrays 3. Add FS to EXPORTED_RUNTIME_METHODS - Without FS export, FS.init() fails - Emscripten falls back to window.prompt() for stdin - Added 'FS' to exported methods in Emscripten.cmake Structure: - <script>: Define Module synchronously - <script type="importmap">: Define ES module imports - <script type="module">: Load xterm 6.0, hook into Module lifecycle - {{{ SCRIPT }}}: Emscripten code (uses existing Module) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Refactor WASM build system for multi-executable support - Convert hardcoded shell.html to CMake template (shell_template.html.in) - Add per-target customization via TITLE and DESCRIPTION parameters - Generate target-specific shell files in build/web/<target>_shell.html - Fix @ symbol escaping in npm CDN URLs using @at@ placeholder - Update myproject_configure_wasm_target() to accept optional parameters This enables multiple WASM executables in one project, each with customized HTML pages and titles. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add unified web deployment directory for WASM targets - Create web-dist target that collects all WASM artifacts into organized structure - Add index_template.html.in for landing page with app cards - Register WASM targets globally with title and description metadata - Implement myproject_create_web_dist() function to: - Generate index.html from template with links to all apps - Copy each target's artifacts to subdirectories (intro/, etc.) - Rename target.html to index.html for clean URLs - Update CI workflow to build web-dist instead of individual targets - Make web-dist part of ALL target so it builds by default Directory structure: web-dist/ index.html (landing page) coi-serviceworker.min.js intro/ index.html intro.js intro.wasm coi-serviceworker.min.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix hardening options for MSVC * Clean up and improve WASM build configuration Phase 1: Critical Fixes - Remove redundant -fwasm-exceptions flag (already in global CMAKE_CXX_FLAGS) - Fix HTML injection vulnerability by escaping special characters in titles/descriptions - Absolutify resource paths to prevent Emscripten path resolution issues - Ensure service worker copied to both individual targets and web-dist Phase 2: Configuration Improvements - Add configurable WASM runtime parameters (memory, thread pool, asyncify stack) - MYPROJECT_WASM_INITIAL_MEMORY (default: 32MB) - MYPROJECT_WASM_PTHREAD_POOL_SIZE (default: 4) - MYPROJECT_WASM_ASYNCIFY_STACK_SIZE (default: 64KB) - Replace INTRO_RESOURCES_DIR with per-target RESOURCES_DIR parameter - Make each target directory fully standalone with its own service worker Benefits: - Users can tune WASM performance without editing CMake files - HTML injection vulnerability fixed - Resource paths work correctly with relative paths - Both individual targets and web-dist deployments are self-contained - More flexible per-target resource embedding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Simplify WASM build infrastructure code Reduces code duplication and improves maintainability while preserving all functionality. Changes include: JavaScript simplifications (web/shell_template.html.in): - Remove 39 lines of duplicate URL parameter documentation - Consolidate error handlers with shared showError() helper - Extract buffer writer factory to eliminate stdout/stderr duplication - Simplify URL parameter parsing using forEach API - Total reduction: ~70 lines CMake simplifications (cmake/Emscripten.cmake): - Add reusable escape_html() helper function - Loop sanitizer configuration instead of 5 separate set() calls - Use string(APPEND) for compiler flags - Use escape_html() helper to eliminate duplicate escaping logic - Remove redundant else clause for WASM_BUILD flag - Remove redundant directory creation (configure_file creates parents) - Change template missing warning to fatal error (fail fast) - Total reduction: ~12 lines Overall impact: ~82 fewer lines, improved readability, easier maintenance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Use add_compile_options and add_link_options for Emscripten flags Replace direct CMAKE_CXX_FLAGS/CMAKE_C_FLAGS manipulation with add_compile_options() and add_link_options(), which is more idiomatic modern CMake and handles flag propagation correctly. Both -pthread and -fwasm-exceptions need to be specified at compile and link time for Emscripten builds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Apply DRY principles to Emscripten configuration Eliminates code duplication and improves maintainability: - Define common web asset paths as constants at top of file (MYPROJECT_WEB_DIR, MYPROJECT_COI_WORKER, etc.) instead of duplicating "${CMAKE_SOURCE_DIR}/web/..." throughout - Loop static analysis tool options (CLANG_TIDY, CPPCHECK, WARNINGS_AS_ERRORS) instead of three separate set() calls - Use path constants consistently in both myproject_configure_wasm_target and myproject_create_web_dist functions - Remove outdated comment reference to old line number - Add clarifying comment about WASM artifact files being copied Net result: 3 fewer lines, single source of truth for file paths, more consistent code structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Update MYPROJECT to myproject for proper replacement * Document GitHub Pages deployment hierarchy Note that main deploys to root, develop to /develop/, and tags to /tagname/ subdirectories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add develop branch link to WebAssembly demo section Include links to both main and develop deployments so users can see both stable and development versions of the demo. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3a3b08d commit f51204b

File tree

12 files changed

+764
-20
lines changed

12 files changed

+764
-20
lines changed

.github/template/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
## About %%myproject%%
88
%%description%%
99

10+
## WebAssembly Demo
11+
12+
Try the live WebAssembly demo:
13+
- Main: [https://%%myorg%%.github.io/%%myproject%%/](https://%%myorg%%.github.io/%%myproject%%/)
14+
- Develop: [https://%%myorg%%.github.io/%%myproject%%/develop/](https://%%myorg%%.github.io/%%myproject%%/develop/)
15+
16+
The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`.
17+
1018

1119
## More Details
1220

.github/workflows/wasm.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Build Intro WASM and Deploy to GitHub Pages
2+
3+
on:
4+
pull_request:
5+
release:
6+
types: [published]
7+
push:
8+
branches: [main, develop]
9+
tags: ['**']
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
build-and-deploy:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Setup Emscripten
21+
uses: mymindstorm/setup-emsdk@v14
22+
with:
23+
version: 'latest'
24+
25+
- name: Install Ninja
26+
run: sudo apt-get install -y ninja-build
27+
28+
- name: Configure CMake
29+
run: emcmake cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
30+
31+
- name: Build all WASM targets
32+
run: emmake cmake --build build --target web-dist
33+
34+
- name: Prepare deployment
35+
run: |
36+
# web-dist target already created build/web-dist/
37+
# Just copy it to dist/ for GitHub Pages action
38+
cp -r build/web-dist dist
39+
40+
- name: Determine deploy path
41+
id: deploy-path
42+
if: github.event_name != 'pull_request' && github.event_name != 'release'
43+
run: |
44+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
45+
echo "path=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
46+
elif [[ "$GITHUB_REF" == refs/heads/main ]]; then
47+
echo "path=." >> $GITHUB_OUTPUT
48+
elif [[ "$GITHUB_REF" == refs/heads/develop ]]; then
49+
echo "path=develop" >> $GITHUB_OUTPUT
50+
fi
51+
52+
- name: Deploy to GitHub Pages
53+
if: github.event_name != 'pull_request' && github.event_name != 'release'
54+
uses: peaceiris/actions-gh-pages@v4
55+
with:
56+
github_token: ${{ secrets.GITHUB_TOKEN }}
57+
publish_dir: ./dist
58+
destination_dir: ${{ steps.deploy-path.outputs.path }}
59+
keep_files: true

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ project(
2323
LANGUAGES CXX C)
2424

2525
include(cmake/PreventInSourceBuilds.cmake)
26+
include(cmake/Emscripten.cmake)
2627
include(ProjectOptions.cmake)
2728

2829

@@ -60,6 +61,11 @@ add_subdirectory(configured_files)
6061
# Adding the src:
6162
add_subdirectory(src)
6263

64+
# Create unified web deployment directory (for WASM builds)
65+
if(EMSCRIPTEN)
66+
myproject_create_web_dist()
67+
endif()
68+
6369
# Don't even look at tests if we're not top level
6470
if(NOT PROJECT_IS_TOP_LEVEL)
6571
return()

ProjectOptions.cmake

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ include(CheckCXXSourceCompiles)
88

99

1010
macro(myproject_supports_sanitizers)
11-
if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32)
11+
# Emscripten doesn't support sanitizers
12+
if(EMSCRIPTEN)
13+
set(SUPPORTS_UBSAN OFF)
14+
set(SUPPORTS_ASAN OFF)
15+
elseif((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32)
1216

1317
message(STATUS "Sanity checking UndefinedBehaviorSanitizer, it should be supported on this platform")
1418
set(TEST_PROGRAM "int main() { return 0; }")
@@ -166,19 +170,22 @@ macro(myproject_local_options)
166170
""
167171
"")
168172

169-
if(myproject_ENABLE_USER_LINKER)
170-
include(cmake/Linker.cmake)
171-
myproject_configure_linker(myproject_options)
172-
endif()
173+
# Linker and sanitizers not supported in Emscripten
174+
if(NOT EMSCRIPTEN)
175+
if(myproject_ENABLE_USER_LINKER)
176+
include(cmake/Linker.cmake)
177+
myproject_configure_linker(myproject_options)
178+
endif()
173179

174-
include(cmake/Sanitizers.cmake)
175-
myproject_enable_sanitizers(
176-
myproject_options
177-
${myproject_ENABLE_SANITIZER_ADDRESS}
178-
${myproject_ENABLE_SANITIZER_LEAK}
179-
${myproject_ENABLE_SANITIZER_UNDEFINED}
180-
${myproject_ENABLE_SANITIZER_THREAD}
181-
${myproject_ENABLE_SANITIZER_MEMORY})
180+
include(cmake/Sanitizers.cmake)
181+
myproject_enable_sanitizers(
182+
myproject_options
183+
${myproject_ENABLE_SANITIZER_ADDRESS}
184+
${myproject_ENABLE_SANITIZER_LEAK}
185+
${myproject_ENABLE_SANITIZER_UNDEFINED}
186+
${myproject_ENABLE_SANITIZER_THREAD}
187+
${myproject_ENABLE_SANITIZER_MEMORY})
188+
endif()
182189

183190
set_target_properties(myproject_options PROPERTIES UNITY_BUILD ${myproject_ENABLE_UNITY_BUILD})
184191

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ It includes
2020
* a basic CLI example
2121
* examples for fuzz, unit, and constexpr testing
2222
* large GitHub action testing matrix
23+
* WebAssembly build support with automatic GitHub Pages deployment
24+
25+
**Live Demo:** If you enable GitHub Pages in your project created from this template, you'll have a working example like this:
26+
- Main: [https://cpp-best-practices.github.io/cmake_template/](https://cpp-best-practices.github.io/cmake_template/)
27+
- Develop: [https://cpp-best-practices.github.io/cmake_template/develop/](https://cpp-best-practices.github.io/cmake_template/develop/)
28+
29+
The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`.
2330

2431
It requires
2532

cmake/Emscripten.cmake

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# cmake/Emscripten.cmake
2+
# Emscripten/WebAssembly build configuration
3+
4+
# Common paths for web assets
5+
set(myproject_WEB_DIR "${CMAKE_SOURCE_DIR}/web")
6+
set(myproject_COI_WORKER "${myproject_WEB_DIR}/coi-serviceworker.min.js")
7+
set(myproject_SHELL_TEMPLATE "${myproject_WEB_DIR}/shell_template.html.in")
8+
set(myproject_INDEX_TEMPLATE "${myproject_WEB_DIR}/index_template.html.in")
9+
10+
# Helper function to escape HTML special characters
11+
function(escape_html output_var input)
12+
set(result "${input}")
13+
string(REPLACE "&" "&amp;" result "${result}")
14+
string(REPLACE "<" "&lt;" result "${result}")
15+
string(REPLACE ">" "&gt;" result "${result}")
16+
string(REPLACE "\"" "&quot;" result "${result}")
17+
set(${output_var} "${result}" PARENT_SCOPE)
18+
endfunction()
19+
20+
# Detect if we're building with Emscripten
21+
if(EMSCRIPTEN)
22+
message(STATUS "Emscripten build detected - configuring for WebAssembly")
23+
24+
# Set WASM build flag
25+
set(myproject_WASM_BUILD ON CACHE BOOL "Building for WebAssembly" FORCE)
26+
27+
# Sanitizers don't work with Emscripten
28+
foreach(sanitizer ADDRESS LEAK UNDEFINED THREAD MEMORY)
29+
set(myproject_ENABLE_SANITIZER_${sanitizer} OFF CACHE BOOL "Not supported with Emscripten")
30+
endforeach()
31+
32+
# Disable static analysis and strict warnings for Emscripten builds
33+
foreach(option CLANG_TIDY CPPCHECK WARNINGS_AS_ERRORS)
34+
set(myproject_ENABLE_${option} OFF CACHE BOOL "Disabled for Emscripten")
35+
endforeach()
36+
37+
# Disable testing - no way to execute WASM test targets
38+
set(BUILD_TESTING OFF CACHE BOOL "No test runner for WASM")
39+
40+
# WASM runtime configuration - tunable performance parameters
41+
set(myproject_WASM_INITIAL_MEMORY "33554432" CACHE STRING
42+
"Initial WASM memory in bytes (default: 32MB)")
43+
set(myproject_WASM_PTHREAD_POOL_SIZE "4" CACHE STRING
44+
"Pthread pool size for WASM builds (default: 4)")
45+
set(myproject_WASM_ASYNCIFY_STACK_SIZE "65536" CACHE STRING
46+
"Asyncify stack size in bytes (default: 64KB)")
47+
48+
# For Emscripten WASM builds, FTXUI requires pthreads and native exception handling
49+
# Set these flags early so they propagate to all dependencies
50+
add_compile_options(-pthread -fwasm-exceptions)
51+
add_link_options(-pthread -fwasm-exceptions)
52+
endif()
53+
54+
# Function to apply WASM settings to a target
55+
function(myproject_configure_wasm_target target)
56+
if(EMSCRIPTEN)
57+
# Parse optional named arguments
58+
set(options "")
59+
set(oneValueArgs TITLE DESCRIPTION RESOURCES_DIR)
60+
set(multiValueArgs "")
61+
cmake_parse_arguments(WASM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
62+
63+
# Set defaults if not provided
64+
if(NOT WASM_TITLE)
65+
set(WASM_TITLE "${target}")
66+
endif()
67+
68+
if(NOT WASM_DESCRIPTION)
69+
set(WASM_DESCRIPTION "WebAssembly application")
70+
endif()
71+
72+
# Register this target in the global WASM targets list
73+
set_property(GLOBAL APPEND PROPERTY myproject_WASM_TARGETS "${target}")
74+
set_property(GLOBAL PROPERTY myproject_WASM_TARGET_${target}_TITLE "${WASM_TITLE}")
75+
set_property(GLOBAL PROPERTY myproject_WASM_TARGET_${target}_DESCRIPTION "${WASM_DESCRIPTION}")
76+
77+
target_compile_definitions(${target} PRIVATE myproject_WASM_BUILD=1)
78+
79+
# Emscripten link flags
80+
target_link_options(${target} PRIVATE
81+
# Enable pthreads - REQUIRED by FTXUI's WASM implementation
82+
"-sUSE_PTHREADS=1"
83+
"-sPROXY_TO_PTHREAD=1"
84+
"-sPTHREAD_POOL_SIZE=${myproject_WASM_PTHREAD_POOL_SIZE}"
85+
# Enable asyncify for emscripten_sleep and async operations
86+
"-sASYNCIFY=1"
87+
"-sASYNCIFY_STACK_SIZE=${myproject_WASM_ASYNCIFY_STACK_SIZE}"
88+
# Memory configuration
89+
"-sALLOW_MEMORY_GROWTH=1"
90+
"-sINITIAL_MEMORY=${myproject_WASM_INITIAL_MEMORY}"
91+
# Environment - need both web and worker for pthread support
92+
"-sENVIRONMENT=web,worker"
93+
# Export runtime methods for JavaScript interop
94+
"-sEXPORTED_RUNTIME_METHODS=['FS','ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8']"
95+
# Export malloc/free for MAIN_THREAD_EM_ASM usage
96+
"-sEXPORTED_FUNCTIONS=['_main','_malloc','_free']"
97+
# Debug: enable assertions for better error messages
98+
"-sASSERTIONS=1"
99+
)
100+
101+
# Embed resources into WASM binary (optional, per-target)
102+
if(WASM_RESOURCES_DIR AND EXISTS "${WASM_RESOURCES_DIR}")
103+
# Convert to absolute path to avoid issues with Emscripten path resolution
104+
get_filename_component(ABS_RESOURCES_DIR "${WASM_RESOURCES_DIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}")
105+
106+
target_link_options(${target} PRIVATE
107+
"--embed-file=${ABS_RESOURCES_DIR}@/resources"
108+
)
109+
message(STATUS "Embedding resources for ${target} from ${ABS_RESOURCES_DIR}")
110+
endif()
111+
112+
# Configure the shell HTML template for this target
113+
set(TARGET_NAME "${target}")
114+
set(TARGET_TITLE "${WASM_TITLE}")
115+
set(TARGET_DESCRIPTION "${WASM_DESCRIPTION}")
116+
set(AT "@") # For escaping @ in npm package URLs
117+
set(CONFIGURED_SHELL "${CMAKE_BINARY_DIR}/web/${target}_shell.html")
118+
119+
# Generate target-specific shell file (configure_file creates parent directories automatically)
120+
if(EXISTS "${myproject_SHELL_TEMPLATE}")
121+
configure_file(
122+
"${myproject_SHELL_TEMPLATE}"
123+
"${CONFIGURED_SHELL}"
124+
@ONLY
125+
)
126+
127+
# Use the generated shell file
128+
target_link_options(${target} PRIVATE
129+
"--shell-file=${CONFIGURED_SHELL}"
130+
)
131+
132+
# Add both template and configured file as link dependencies
133+
set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS
134+
"${myproject_SHELL_TEMPLATE}"
135+
"${CONFIGURED_SHELL}"
136+
)
137+
138+
message(STATUS "Configured WASM shell for ${target}: ${CONFIGURED_SHELL}")
139+
else()
140+
message(FATAL_ERROR "Shell template not found: ${myproject_SHELL_TEMPLATE}")
141+
endif()
142+
143+
# Copy service worker to target build directory for standalone target builds
144+
if(EXISTS "${myproject_COI_WORKER}")
145+
add_custom_command(TARGET ${target} POST_BUILD
146+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
147+
"${myproject_COI_WORKER}"
148+
"$<TARGET_FILE_DIR:${target}>/coi-serviceworker.min.js"
149+
COMMENT "Copying coi-serviceworker.min.js to ${target} build directory"
150+
)
151+
endif()
152+
153+
# Set output suffix to .html
154+
set_target_properties(${target} PROPERTIES SUFFIX ".html")
155+
156+
message(STATUS "Configured ${target} for WebAssembly")
157+
endif()
158+
endfunction()
159+
160+
# Create a unified web deployment directory with all WASM targets
161+
function(myproject_create_web_dist)
162+
if(NOT EMSCRIPTEN)
163+
return()
164+
endif()
165+
166+
# Define output directory
167+
set(WEB_DIST_DIR "${CMAKE_BINARY_DIR}/web-dist")
168+
169+
# Get list of all WASM targets
170+
get_property(WASM_TARGETS GLOBAL PROPERTY myproject_WASM_TARGETS)
171+
172+
if(NOT WASM_TARGETS)
173+
message(WARNING "No WASM targets registered. Skipping web-dist generation.")
174+
return()
175+
endif()
176+
177+
# Generate HTML for app cards
178+
set(WASM_APPS_HTML "")
179+
foreach(target ${WASM_TARGETS})
180+
get_property(TITLE GLOBAL PROPERTY myproject_WASM_TARGET_${target}_TITLE)
181+
get_property(DESCRIPTION GLOBAL PROPERTY myproject_WASM_TARGET_${target}_DESCRIPTION)
182+
183+
# Escape HTML special characters to prevent injection
184+
escape_html(TITLE_ESCAPED "${TITLE}")
185+
escape_html(DESC_ESCAPED "${DESCRIPTION}")
186+
187+
string(APPEND WASM_APPS_HTML
188+
" <a href=\"${target}/\" class=\"app-card\">
189+
<div class=\"app-title\">${TITLE_ESCAPED}</div>
190+
<div class=\"app-description\">${DESC_ESCAPED}</div>
191+
</a>
192+
")
193+
endforeach()
194+
195+
# Generate index.html from template
196+
set(INDEX_OUTPUT "${WEB_DIST_DIR}/index.html")
197+
198+
if(EXISTS "${myproject_INDEX_TEMPLATE}")
199+
configure_file("${myproject_INDEX_TEMPLATE}" "${INDEX_OUTPUT}" @ONLY)
200+
else()
201+
message(WARNING "Index template not found: ${myproject_INDEX_TEMPLATE}")
202+
endif()
203+
204+
# Build list of copy commands
205+
set(COPY_COMMANDS "")
206+
207+
# For each WASM target, copy artifacts to subdirectory
208+
# Each target gets its own service worker copy for standalone deployment
209+
foreach(target ${WASM_TARGETS})
210+
get_target_property(TARGET_BINARY_DIR ${target} BINARY_DIR)
211+
set(TARGET_DIST_DIR "${WEB_DIST_DIR}/${target}")
212+
213+
# Copy WASM artifacts: .html (as index.html), .js, .wasm, and service worker
214+
list(APPEND COPY_COMMANDS
215+
COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_DIST_DIR}"
216+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
217+
"${TARGET_BINARY_DIR}/${target}.html"
218+
"${TARGET_DIST_DIR}/index.html"
219+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
220+
"${TARGET_BINARY_DIR}/${target}.js"
221+
"${TARGET_DIST_DIR}/${target}.js"
222+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
223+
"${TARGET_BINARY_DIR}/${target}.wasm"
224+
"${TARGET_DIST_DIR}/${target}.wasm"
225+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
226+
"${myproject_COI_WORKER}"
227+
"${TARGET_DIST_DIR}/coi-serviceworker.min.js"
228+
)
229+
endforeach()
230+
231+
# Create custom target with all commands (part of ALL so it builds by default)
232+
add_custom_target(web-dist ALL
233+
COMMAND ${CMAKE_COMMAND} -E make_directory "${WEB_DIST_DIR}"
234+
${COPY_COMMANDS}
235+
COMMENT "Creating unified web deployment directory"
236+
)
237+
238+
# Ensure web-dist runs after all WASM targets are built
239+
add_dependencies(web-dist ${WASM_TARGETS})
240+
241+
message(STATUS "Configured web-dist target with ${WASM_TARGETS}")
242+
endfunction()

0 commit comments

Comments
 (0)