From f692af7213bfb6da02fb6493949c142b1f5d1fed Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Mon, 7 Sep 2026 13:01:19 +1000 Subject: [PATCH 1/7] fix: remove unsupported -s DEMANGLE_SUPPORT=1 emcc flag for emscripten builds Emscripten 4.0.9 (the SDK version emscripten-forge/recipes currently publishes as emscripten_emscripten-wasm32) has removed the DEMANGLE_SUPPORT link setting entirely: passing it now hard-errors with `emcc: error: invalid command line setting -sDEMANGLE_SUPPORT=1: No longer supported`, rather than being ignored or warned about. This broke every emscripten-wasm32 ament_cmake package build past the first one or two (confirmed hitting it on ros-humble's rcutils, the first real C library in its selected package set) -- the flag was unconditionally injected into CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS, CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS, and CMAKE_EXE_LINKER_FLAGS for every wasm32 shared-library build via this template, both in the live code path and in the dead, already-commented-out per-package branch above it (fixed for consistency in case it's ever re-enabled). Co-Authored-By: Claude Sonnet 5 --- vinca/templates/build_ament_cmake.sh.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vinca/templates/build_ament_cmake.sh.in b/vinca/templates/build_ament_cmake.sh.in index cc25a25..647e69f 100644 --- a/vinca/templates/build_ament_cmake.sh.in +++ b/vinca/templates/build_ament_cmake.sh.in @@ -70,13 +70,13 @@ if [[ $target_platform =~ emscripten.* ]]; then echo "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) # fixes an error where numpy header files are not found correctly">> $SRC_DIR/__vinca_shared_lib_patch.cmake # if [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-publisher" ] || [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-subscriber" ] || [ "${PKG_NAME}" == "ros-humble-rclcpp-components" ]; then - # echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake + # echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + # echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + # echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake # else - echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake + echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake # fi export BUILD_TYPE="Debug" From 26be199502207a3def36f28645d383726d0620aa Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Mon, 7 Sep 2026 14:14:05 +1000 Subject: [PATCH 2/7] fix: evaluate v1 if/then/else selectors in pinning_overrides values vinca_pinning.yaml's pinning_overrides are copied verbatim into the rendered conda_build_config.yaml by _overlay() -- but rattler-build's variant config loader does not evaluate v1-style `if: COND then: [...]` mappings as selectors the way recipe.yaml files do; it treats each one as an opaque literal value. Depending on how many if/then entries a key has, this surfaces as either "Could not parse version spec for variant key X: invalid channel" (single entry) or "multiple bracket sections not allowed" / a silent cartesian-product explosion across every branch (multiple entries), since rattler-build sees N distinct raw-dict "values" instead of one selector to resolve. The legacy `- VALUE # [selector]` comment-annotated list form, used throughout the rest of this file (c_compiler_version, cxx_compiler_version, etc.), *is* understood and evaluated lazily per --target-platform by rattler-build itself. Fixed by having _overlay() convert any v1-style if/then/else list entries in an override's value into that legacy form before writing it out, so overrides can be authored with the more readable if/then/else syntax (matching emscripten-forge/recipes' own variant.yaml style) while still producing a conda_build_config.yaml rattler-build actually parses as conditional. Also had to make sure existing `# [selector]` EOL comments on passthrough (non-if/then) list items survive the rebuild: ruamel keeps comments keyed by list position on the *source* CommentedSeq, not on the item value itself, so appending an item into a freshly created CommentedSeq silently drops its comment unless it's explicitly copied across -- confirmed by a first pass of this fix accidentally stripping the selectors off c_compiler/c_compiler_version's existing entries, which briefly turned every platform's compiler into a candidate for every other platform's build. Co-Authored-By: Claude Sonnet 5 --- vinca/pinning.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/vinca/pinning.py b/vinca/pinning.py index b773e6b..df80d1b 100644 --- a/vinca/pinning.py +++ b/vinca/pinning.py @@ -219,11 +219,69 @@ def _migration_name(name: str) -> str: return name +def _existing_eol_comment_text(source: Any, index: int) -> Optional[str]: + """Return the plain text of a CommentedSeq item's trailing EOL comment, if any.""" + ca = getattr(source, "ca", None) + if ca is None: + return None + entry = ca.items.get(index) + if not entry: + return None + token = entry[0] + if token is None: + return None + return str(token.value).lstrip("#").strip() + + +def _flatten_v1_selectors(value: Any) -> Any: + """Convert v1-style `- if: COND then: [...]` list entries into the legacy + `- VALUE # [COND]` comment-annotated form that rattler-build's variant + config loader actually evaluates lazily per target_platform (unlike the + v1 if/then/else mapping form, which it treats as an opaque literal value + rather than a selector -- confirmed via `Could not parse version spec + for variant key ...: invalid channel` / `multiple bracket sections not + allowed` errors when left unconverted). + + Passthrough items (plain scalars, possibly already carrying their own + `# [selector]` EOL comment) must have that existing comment re-attached + at their new index -- ruamel stores comments keyed by list position on + the *source* CommentedSeq, not on the item itself, so a naive + `result.append(item)` into a freshly created CommentedSeq silently + drops it, turning a platform-scoped entry into an unconditional one. + """ + if not isinstance(value, list): + return value + import ruamel.yaml.comments as _rc + + result = _rc.CommentedSeq() + for old_index, item in enumerate(value): + if isinstance(item, Mapping) and "if" in item and "then" in item: + cond = str(item["if"]) + for entry in item["then"]: + idx = len(result) + result.append(entry) + result.yaml_add_eol_comment(f"[{cond}]", idx) + else_branch = item.get("else") + if else_branch is not None: + not_cond = f"not ({cond})" + for entry in else_branch: + idx = len(result) + result.append(entry) + result.yaml_add_eol_comment(f"[{not_cond}]", idx) + else: + idx = len(result) + result.append(item) + comment_text = _existing_eol_comment_text(value, old_index) + if comment_text: + result.yaml_add_eol_comment(comment_text, idx) + return result + + def _overlay(target: Any, source: Any) -> None: for key, value in source.items(): if key == "migrator_ts" or str(key).startswith("__"): continue - target[key] = value + target[key] = _flatten_v1_selectors(value) def _migration_timestamp(payload: bytes) -> float: From 7133b302498edda9bc87a939329353fac95177c5 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Tue, 8 Sep 2026 17:23:16 +1000 Subject: [PATCH 3/7] feat: enable real pthreads for emscripten-wasm32 builds Flips USE_PTHREADS=0 to 1 (compile and link) for every emscripten-wasm32 ament_cmake build, plus adds compile-time -s USE_PTHREADS=1 globally via CMAKE_PROJECT_INCLUDE so every translation unit gets the atomics/bulk-memory wasm features the linker requires for shared memory. Without real threads, libc++'s condition_variable timed-wait (used by rclcpp's executor / rmw wait-set) never wakes up on its own -- there is no OS thread to run a real blocking wait, so a synchronous C++ wait blocks the single JS thread forever with no way for wall-clock time to advance underneath it. This was blocking any ROS2 wasm demo from running more than one executor iteration. Co-Authored-By: Claude Sonnet 5 --- vinca/templates/build_ament_cmake.sh.in | 39 +++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/vinca/templates/build_ament_cmake.sh.in b/vinca/templates/build_ament_cmake.sh.in index 647e69f..057cadf 100644 --- a/vinca/templates/build_ament_cmake.sh.in +++ b/vinca/templates/build_ament_cmake.sh.in @@ -69,15 +69,36 @@ if [[ $target_platform =~ emscripten.* ]]; then echo "set(CMAKE_STRIP FALSE) # used by default in pybind11 on .so modules">> $SRC_DIR/__vinca_shared_lib_patch.cmake echo "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) # fixes an error where numpy header files are not found correctly">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # if [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-publisher" ] || [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-subscriber" ] || [ "${PKG_NAME}" == "ros-humble-rclcpp-components" ]; then - # echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # else - echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake - echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake - # fi + # Real pthreads (USE_PTHREADS=1) are required so that blocking waits + # (std::condition_variable / rmw wait sets) actually work: without a real + # OS-level thread, libc++'s condition_variable timed-wait never wakes up + # on its own, and code that spins/blocks the main thread (e.g. rclcpp's + # executor) hangs forever with no way for the browser's JS event loop + # (and thus wall-clock time) to ever advance underneath it. This must be + # consistent across every emscripten-wasm32 package: mixing a + # pthread-enabled module with a non-pthread one is a hard ABI-level + # mismatch ("memory import shared state mismatch") since a wasm module's + # shared-vs-non-shared linear memory is fixed at compile+link time. + # + # This flag has to be set at COMPILE time too (not just link time) for + # every translation unit -- it bakes in the wasm 'atomics'/'bulk-memory' + # features that the linker later requires when producing shared memory + # ("wasm-ld: error: --shared-memory is disallowed ... because it was not + # compiled with 'atomics' or 'bulk-memory' features"). add_compile_options + # here (via CMAKE_PROJECT_INCLUDE, included right after every project() + # call) applies it to every target compiled in every package. + echo "add_compile_options(\"SHELL: -s USE_PTHREADS=1\")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + + echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + # PTHREAD_POOL_SIZE and MAXIMUM_MEMORY are only meaningful on the final + # MAIN_MODULE executable link (they configure the Worker pool and the + # shared SharedArrayBuffer's reserved size respectively -- side modules + # don't have memory of their own, they use the main module's). Emscripten + # requires MAXIMUM_MEMORY to be set explicitly whenever + # ALLOW_MEMORY_GROWTH is combined with USE_PTHREADS, since a shared + # wasm memory's maximum size can't be left unbounded. + echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 -sALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1024MB -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake export BUILD_TYPE="Debug" export EXTRA_CMAKE_ARGS=" \ From dd38b4a7d24ca3d2af4951ad5bda31669b4bc066 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Tue, 8 Sep 2026 23:09:27 +1000 Subject: [PATCH 4/7] fix: don't require a build_platform copy of rosidl_default_generators for emscripten rattler-build resolves "build:" dependencies against build_platform, not target_platform, so listing the target-arch-named rosidl_default_generators package under build: for emscripten-wasm32 cross builds only works if a native (non-emscripten) copy of that exact package has also been built and published -- which is not generally the case. The host-prefix copy is already sufficient for CMake's find_package() during configure (confirmed: packages using only rosidl_core_generators in host, with no matching build: entry, already build fine), so this extra requirement was both unsatisfiable and unnecessary. Co-Authored-By: Claude Sonnet 5 --- vinca/main.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/vinca/main.py b/vinca/main.py index 4aa962c..abb7715 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -593,15 +593,6 @@ def sortkey(k): } ) - rosidl_generators_name = f"{package_prefix}-rosidl-default-generators" - if rosidl_generators_name in output["requirements"]["host"]: - output["requirements"]["build"].append( - { - "if": "target_platform == 'emscripten-wasm32'", - "then": [rosidl_generators_name], - } - ) - output["requirements"]["run"] = sorted(output["requirements"]["run"], key=sortkey) output["requirements"]["host"] = sorted(output["requirements"]["host"], key=sortkey) From b50406304d8009b44d4e56e50abab444a3912942 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Wed, 9 Sep 2026 05:49:20 +1000 Subject: [PATCH 5/7] feat: make the default emscripten RMW_IMPLEMENTATION configurable Hardcoding rmw_wasm_cpp as the default RMW for emscripten-wasm32 builds only works for repos that actually build that specific RMW. Read it from VINCA_EMSCRIPTEN_RMW_IMPLEMENTATION instead (falling back to rmw_wasm_cpp unchanged), so other emscripten-wasm32 experiments -- e.g. one building rmw_zenoh_pico instead -- can select their own default without forking this template again. Co-Authored-By: Claude Sonnet 5 --- vinca/templates/build_ament_cmake.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vinca/templates/build_ament_cmake.sh.in b/vinca/templates/build_ament_cmake.sh.in index 057cadf..6b5ddda 100644 --- a/vinca/templates/build_ament_cmake.sh.in +++ b/vinca/templates/build_ament_cmake.sh.in @@ -103,7 +103,7 @@ if [[ $target_platform =~ emscripten.* ]]; then export BUILD_TYPE="Debug" export EXTRA_CMAKE_ARGS=" \ -DPYTHON_SOABI="cpython-${ROS_PYTHON_VERSION//./}-wasm32-emscripten" \ - -DRMW_IMPLEMENTATION=rmw_wasm_cpp \ + -DRMW_IMPLEMENTATION=${VINCA_EMSCRIPTEN_RMW_IMPLEMENTATION:-rmw_wasm_cpp} \ -DCMAKE_FIND_ROOT_PATH=$PREFIX \ -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \ -DCMAKE_PROJECT_INCLUDE=$SRC_DIR/__vinca_shared_lib_patch.cmake \ From 235bd2fcf88a7e61a084a39cc216e1271b753486 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Wed, 9 Sep 2026 09:40:28 +1000 Subject: [PATCH 6/7] feat: make the emscripten static rosidl typesupport backend configurable STATIC_ROSIDL_TYPESUPPORT_C/CPP were hardcoded to rosidl_typesupport_introspection_c/cpp, which is what rmw_wasm_cpp expects. A micro-ROS-lineage RMW (e.g. rmw_zenoh_pico) instead requires messages' rosidl_typesupport_c dispatch table to register rosidl_typesupport_microxrcedds_c/cpp specifically -- confirmed via a live browser demo that publisher creation fails with "Type support not from this implementation" otherwise, since the dispatch table is filtered down to a single backend at build time and never even considers alternatives at runtime. Purely additive: defaults to the existing introspection backend when the new env vars are unset, so non-zenoh consumers of this template (e.g. ros-humble-emscripten's rmw_wasm_cpp pin) are unaffected. Co-Authored-By: Claude Sonnet 5 --- vinca/templates/build_ament_cmake.sh.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vinca/templates/build_ament_cmake.sh.in b/vinca/templates/build_ament_cmake.sh.in index 6b5ddda..539e7da 100644 --- a/vinca/templates/build_ament_cmake.sh.in +++ b/vinca/templates/build_ament_cmake.sh.in @@ -113,8 +113,8 @@ if [[ $target_platform =~ emscripten.* ]]; then export CMAKE_GEN="emcmake cmake" export CMAKE_BLD="cmake" - export STATIC_ROSIDL_TYPESUPPORT_C=rosidl_typesupport_introspection_c - export STATIC_ROSIDL_TYPESUPPORT_CPP=rosidl_typesupport_introspection_cpp + export STATIC_ROSIDL_TYPESUPPORT_C=${VINCA_EMSCRIPTEN_STATIC_TYPESUPPORT_C:-rosidl_typesupport_introspection_c} + export STATIC_ROSIDL_TYPESUPPORT_CPP=${VINCA_EMSCRIPTEN_STATIC_TYPESUPPORT_CPP:-rosidl_typesupport_introspection_cpp} else export BUILD_TYPE="Release" export CMAKE_GEN="cmake" From 325d0afb5e4a862c47a65addf113eff62d090486 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Wed, 9 Sep 2026 13:22:38 +1000 Subject: [PATCH 7/7] fix: apply emscripten pthreads flags to CMake MODULE libraries too pybind11_add_module() (used by rclpy's _rclpy_pybind11 extension, and any other pybind11-based Python C extension) creates a CMake MODULE-type library, not SHARED -- a distinct target type with its own CMAKE_SHARED_MODULE_CREATE_*_FLAGS variables. Setting only the SHARED ones left every MODULE .so linked without USE_PTHREADS=1: its object files were compiled with atomics support (add_compile_options applies globally) and looked fine individually, but the final linked module was non-shared-memory, causing a load-time "mismatch in shared state of memory" failure alongside the rest of a real-pthreads build. Co-Authored-By: Claude Sonnet 5 --- vinca/templates/build_ament_cmake.sh.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vinca/templates/build_ament_cmake.sh.in b/vinca/templates/build_ament_cmake.sh.in index 539e7da..06fb73d 100644 --- a/vinca/templates/build_ament_cmake.sh.in +++ b/vinca/templates/build_ament_cmake.sh.in @@ -91,6 +91,17 @@ if [[ $target_platform =~ emscripten.* ]]; then echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + # CMake's MODULE library type (add_library(... MODULE), what + # pybind11_add_module() uses for Python C extensions e.g. rclpy's + # _rclpy_pybind11) is a distinct target type from SHARED and reads its + # own CMAKE_SHARED_MODULE_CREATE_*_FLAGS variables -- setting only the + # SHARED ones above left every MODULE-type .so linked without + # USE_PTHREADS=1, producing a non-shared-memory module that fails to + # load ("mismatch in shared state of memory") next to the rest of a + # pthreads build, even though its own object files were compiled with + # atomics support and looked fine individually. + echo "set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake + echo "set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake # PTHREAD_POOL_SIZE and MAXIMUM_MEMORY are only meaningful on the final # MAIN_MODULE executable link (they configure the Worker pool and the # shared SharedArrayBuffer's reserved size respectively -- side modules