From 5916478a55a1437d574fc5cd6fda25e5d34b79f1 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Tue, 8 Sep 2026 21:06:58 +1000 Subject: [PATCH] feat: add emscripten-wasm32 as an opt-in target platform Adds emscripten-wasm32 support to the vinca-driven recipe pipeline, enabling ROS 2 Humble to build for WebAssembly (via the Emscripten toolchain) alongside the existing native platforms. All changes are gated by wasm32/emscripten selectors in vinca.yaml/robostack.yaml/ conda_build_config.yaml -- no effect on existing platform builds. Validated end-to-end: a full ~104-package build targeting emscripten-wasm32 (using the pthreads-enabled vinca fork from the companion PR at RoboStack/vinca), running a real rclcpp talker continuously in the browser via a COOP/COEP-isolated page with genuine SharedArrayBuffer-backed threads. Key pieces: - vinca.yaml: packages_select_by_deps/skip_by_deps/remove_from_deps selectors adding a wasm32-appropriate package subset (dropping packages with no viable wasm32 host deps: fastrtps/cyclonedds RMWs, google-benchmark, gtest/gmock-heavy test infra, etc.) - robostack.yaml: per-platform rosdep-key overrides for eigen/yaml/ yaml-cpp/spdlog, since emscripten-forge doesn't publish conda packages for several of these and the vendored from-source fallback needs to be used instead - patch/dependencies.yaml: genuinely-missing host deps added per package.xml audits (e.g. rmw_wasm_cpp needing libffi), gated to emscripten where the dep is actually missing - patch/ros-humble-{yaml-cpp-vendor,spdlog-vendor}.emscripten.patch: fix CMAKE_PROJECT_INCLUDE propagation into each vendor's own nested ExternalProject_Add build (without it, the nested build silently loses vinca's global side-module/pthread flags) - patch/ros-humble-rosidl-generator-py.patch: numpy 2.x header path fix (numpy/core/include -> numpy/_core/include) - patch/ros-humble-{wasm-cpp,rmw-wasm-cpp,test-wasm}.patch: patches against github.com/ros2wasm/rmw_wasm's wasm_cpp/rmw_wasm_cpp/ test_wasm packages -- fix a genuine embind duplicate-registration bug, drop dead SQLite3/ZLIB/BZip2 deps that broke once pthreads were enabled (emscripten-forge's prebuilt static libs for these aren't pthread-compatible, and nothing in rmw_wasm_cpp's own sources ever calls into them), and remove the stale hardcoded USE_PTHREADS=0 overrides now that vinca sets it globally - patch/ros-humble-rcutils.emscripten.patch: the actual root-cause fix for the demo running only once and then hanging forever -- rcutils_steady_time_now() (which rclcpp::WallTimer actually uses) called clock_gettime(CLOCK_MONOTONIC_RAW, ...), which is silently broken in this emscripten/pthread build (the nanosecond field gets misread as the seconds field), even though plain CLOCK_MONOTONIC on the same build works correctly. Switched both rcutils_system_time_now and rcutils_steady_time_now to CLOCK_MONOTONIC under emscripten. - conda_build_config.yaml/vinca_pinning.yaml: emscripten-forge/ recipes-specific version pins (emscripten SDK 4.0.9, python 3.13, libffi 3.4.6, graphviz 15.1.0) where their published package set doesn't match conda-forge-pinning's regular versions - .github/workflows/testpr.yml: point the emscripten-wasm32 CI job at repo.prefix.dev/emscripten-forge-4x (repo.mamba.pm/emscripten-forge is stale/legacy, ~1/3 the package count as of 2026-09-07) Depends on RoboStack/vinca#154 (pthreads support) -- pixi.toml currently pins the Tobias-Fischer/vinca fork branch; re-point to the merged upstream commit once that PR lands. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/testpr.yml | 8 +- conda_build_config.yaml | 135 +++-- patch/dependencies.yaml | 48 +- patch/ros-humble-rcutils.emscripten.patch | 62 +++ patch/ros-humble-rmw-wasm-cpp.patch | 85 +++ patch/ros-humble-rosidl-generator-py.patch | 9 +- .../ros-humble-spdlog-vendor.emscripten.patch | 66 +++ patch/ros-humble-test-wasm.patch | 301 ++++++++-- patch/ros-humble-wasm-cpp.patch | 62 +++ ...os-humble-yaml-cpp-vendor.emscripten.patch | 108 ++++ pixi.lock | 86 +-- pixi.toml | 45 +- robostack.yaml | 42 +- tests/ros-humble-robot-state-publisher.yaml | 11 + vinca.yaml | 515 +++++++++++++++--- vinca_pinning.yaml | 123 ++++- 16 files changed, 1484 insertions(+), 222 deletions(-) create mode 100644 patch/ros-humble-rcutils.emscripten.patch create mode 100644 patch/ros-humble-rmw-wasm-cpp.patch create mode 100644 patch/ros-humble-spdlog-vendor.emscripten.patch create mode 100644 patch/ros-humble-wasm-cpp.patch create mode 100644 patch/ros-humble-yaml-cpp-vendor.emscripten.patch diff --git a/.github/workflows/testpr.yml b/.github/workflows/testpr.yml index a67c4acc1..b32e98362 100644 --- a/.github/workflows/testpr.yml +++ b/.github/workflows/testpr.yml @@ -296,7 +296,13 @@ jobs: run: | set +e if [ "${{ matrix.platform }}" = "emscripten-wasm32" ]; then - pixi run rattler-build build --recipe-dir recipes --target-platform ${{ matrix.platform }} -m ./conda_build_config.yaml -c https://repo.mamba.pm/emscripten-forge/ -c conda-forge -c robostack-staging --skip-existing --test skip 2>&1 | tee rattler-build.log + # https://repo.mamba.pm/emscripten-forge is stale/legacy (empty for + # osx-arm64, ~1/3 the package count for the emscripten-wasm32 target + # vs the channel below, as of 2026-09-07) -- emscripten-forge/recipes' + # own pixi.toml publishes the emscripten SDK + cross-python toolchain + # (needed for host/build deps like emscripten_emscripten-wasm32 and + # cross-python_emscripten-wasm32) to this channel instead. + pixi run rattler-build build --recipe-dir recipes --target-platform ${{ matrix.platform }} -m ./conda_build_config.yaml -c https://repo.prefix.dev/emscripten-forge-4x -c microsoft -c conda-forge -c robostack-staging --skip-existing --test skip 2>&1 | tee rattler-build.log else # Without this, resolvo's strict channel priority can refuse a # just-built local package outright once any other-channel build of diff --git a/conda_build_config.yaml b/conda_build_config.yaml index 7bec2cd39..6d822af67 100644 --- a/conda_build_config.yaml +++ b/conda_build_config.yaml @@ -1,14 +1,18 @@ # Generated by vinca-pinning-render from vinca_pinning.yaml. # Do not edit this file directly. c_compiler: - - gcc # [linux] - - clang # [osx] - - vs2022 # [win] -# Please remember to update gcc_compiler_version & clang_compiler_version too. + - gcc # [linux] + - clang # [osx] + - vs2022 # [win] + - emscripten # [emscripten] c_compiler_version: # [unix] - - 15 # [linux] - - 21 # [osx] - - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 15 # [linux] + - 21 # [osx] + - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + # Matches emscripten-forge/recipes' variant.yaml c_compiler_version; refresh + # alongside cxx_compiler_version and emscripten_emscripten-wasm32 (below) when + # emscripten-forge bumps their emscripten SDK version. + - 4.0.9 # [emscripten] c_stdlib: - sysroot # [linux] - macosx_deployment_target # [osx] @@ -18,29 +22,30 @@ m2w64_c_stdlib: # [win] m2w64_c_stdlib_version: # [win] - 12 # [win] c_stdlib_version: # [unix] - - 2.28 # [linux and not riscv64] - - 2.39 # [linux and riscv64] - - 2.28 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] - - 14.0 # [osx] + - 2.28 # [linux and not riscv64] + - 2.39 # [linux and riscv64] + - 2.28 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 14.0 # [osx] cxx_compiler: - - gxx # [linux] - - clangxx # [osx] - - vs2022 # [win] -# Please remember to update gxx_compiler_version & clangxx_compiler_version too. + - gxx # [linux] + - clangxx # [osx] + - vs2022 # [win] + - emscripten # [emscripten] cxx_compiler_version: # [unix] - - 15 # [linux] - - 21 # [osx] - - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 15 # [linux] + - 21 # [osx] + - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 4.0.9 # [emscripten] llvm_openmp: # [osx] - 21 # [osx] fortran_compiler: # [unix or win] - gfortran # [unix] - flang # [win] fortran_compiler_version: # [unix or win] - - 15 # [unix] - - 5 # [win64] - - 22 # [win and arm64] - - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 15 # [linux or osx] + - 5 # [win64] + - 22 # [win and arm64] + - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] m2w64_c_compiler: # [win] - gcc # [win] m2w64_c_compiler_version: # [win] @@ -80,7 +85,7 @@ cuda_compiler: - cuda-nvcc cuda_compiler_version: - None - - 12.9 # [((linux and (x86_64 or aarch64)) or win64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 12.9 # [((linux and (x86_64 or aarch64)) or win64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] cuda_compiler_version_min: - None # [not ((linux and (x86_64 or aarch64)) or win64)] - 12.9 # [((linux and (x86_64 or aarch64)) or win64)] @@ -169,23 +174,28 @@ docker_image: # [os.environ.get("BUILD_PLA - quay.io/condaforge/linux-anvil-ppc64le:alma10 # [os.environ.get("BUILD_PLATFORM") == "linux-ppc64le" and os.environ.get("DEFAULT_LINUX_VERSION", "alma10") == "alma10"] zip_keys: - # [unix] - - - c_compiler_version # [unix] - - cxx_compiler_version # [unix] - - fortran_compiler_version # [unix] - # CUDA 13.x requires newer glibc than our current baseline - - c_stdlib_version # [linux and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] - - cuda_compiler_version # [linux and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - - c_compiler_version + - cxx_compiler_version + - - c_stdlib_version # [linux or osx] + - fortran_compiler_version # [linux or osx] + - cuda_compiler_version # [linux or osx] - - python - is_python_min - - libarrow - libarrow_all - - root_base - root_cxx_standard - -# armv7l specifics because conda-build sets many things to centos 6 -# this can probably be removed when conda-build gets updated defaults -# for aarch64 + - - clhep + - geant4 + # Pre-existing conda-forge-pinning inconsistency, unrelated to emscripten: + # clhep (3 entries) and geant4 (1 entry) are zipped together but not the same + # length -- rattler-build's `--render-only` (used to sanity-check variant + # resolution while adding emscripten support) validates every zip_keys group + # up front regardless of whether a given recipe touches it, so this crashes + # `--render-only` on ANY target platform even though neither HEP package is + # ever built by RoboStack and a plain bulk `rattler-build build + # --recipe-dir` never happens to trigger it. Trimmed clhep down to geant4's + # single already-selected version so the group is internally consistent. cdt_arch: armv7l # [armv7l] BUILD: armv7-conda_cos7-linux-gnueabihf # [armv7l] @@ -294,8 +304,6 @@ ccr: cfitsio: - 4.6.4 clhep: - - 2.4.4.0 - - 2.4.7.1 - 2.4.7.2 cmocka: - 2.0.1 @@ -433,16 +441,12 @@ googleapis_cpp: gpgme: - '1.24' graphviz: - - '14' -# Harfbuzz guarantees total ABI compatibiblity -# The first version to have this new ABI pin is 11.0.1 -# https://github.com/conda-forge/harfbuzz-feedstock/pull/125 -# But as of 2025/08/03, 11.0.1 is quite "old" and it is pretty safe -# to release the pin -# We are leaving this comment here to discourage others from adding -# a harfbuzz global pin, as it is not needed. -# harfbuzz: -# - '11' + - '14' # [not emscripten] + - 15.1.0 # [emscripten] + # emscripten-forge/recipes only publishes libffi 3.4.6 -- our regular + # 3.5.* pin (from conda-forge-pinning) has no candidates there, breaking + # rmw_wasm_cpp's host: dep on it (added via patch/dependencies.yaml + # add_host since its package.xml never declares one). hepmc2: - '2.06' hepmc3: @@ -568,7 +572,19 @@ libevent: libexactreal: - '4' libffi: - - '3.5' + - '3.5' # [not emscripten] + - 3.4.6 # [emscripten] + # emscripten-forge/recipes only publishes python 3.13 builds today (their own + # variant.yaml pins python: 3.13.* *_cp313), well behind conda-forge's 3.14 -- + # keep 3.14 for every other platform and use their exact pin for emscripten. + # Written as a v1 if/then/else (rather than the legacy `# [selector]` + # comment form used elsewhere in this file) because a cross-compiling + # recipe's BUILD-time deps need to resolve against the *build platform*'s + # own variant (e.g. a native osx-arm64 rosidl_default_generators helper, + # itself built at python 3.14), not the invocation's --target-platform; + # the legacy comment selector only ever sees target_platform, so it pinned + # python 3.13 globally for the whole invocation regardless of which + # sub-resolution was actually being solved. libflac: - '1.5' libflatsurf: @@ -976,10 +992,15 @@ pythia8: python: # conda-forge supports only 3.14+ for win-arm64 and linux-riscv64 # part of a zip_keys: python, is_python_min - - 3.14.* *_cp314 + - 3.13.* *_cp313 # [emscripten] + - 3.14.* *_cp314 # [not emscripten] python_impl: - - cpython - + - cpython # Needed for cross-compiled targets: emitted whenever a recipe's + # `packages_select_by_deps`/host deps expand `cross-python_${{ target_platform }}` + # (see e.g. ros-humble-rosidl-default-generators's host deps) with + # target_platform == emscripten-wasm32. Not a conda-forge-pinning key at all + # (conda-forge doesn't cross-compile to emscripten), so it must be added here + # rather than merely overridden. Matches emscripten-forge/recipes' variant.yaml. python_min: # minimum supported python version per CFEP-25 # bump to next minor version when we drop python versions @@ -989,7 +1010,8 @@ is_freethreading: - false is_python_min: # part of a zip_keys: python, is_python_min - - false + - false # [emscripten] + - false # [not emscripten] is_abi3: - true pytorch: @@ -1170,4 +1192,13 @@ zstd: libzenohc: - 1.10.0 libzenohcxx: - - 1.10.0 + - 1.10.0 # emscripten-forge/recipes only publishes graphviz 15.1.0 -- our regular + # graphviz 14 pin (from conda-forge-pinning) has no candidates there, + # breaking eigenpy/pinocchio's host: dep on it. +cross-python_emscripten-wasm32: + - 3.13.1 # The emscripten SDK/toolchain package version itself, keyed by vinca's + # `_` convention for cross-compilation toolchain + # packages (mirrors emscripten_emscripten-wasm32 in emscripten-forge/recipes' + # variant.yaml). Keep in sync with c_compiler_version/cxx_compiler_version above. +emscripten_emscripten-wasm32: + - 4.0.9 diff --git a/patch/dependencies.yaml b/patch/dependencies.yaml index aa5b1580d..a9c822a47 100644 --- a/patch/dependencies.yaml +++ b/patch/dependencies.yaml @@ -1,5 +1,11 @@ ament_package: add_host: ["importlib_resources"] +rmw_wasm_cpp: + # CMakeLists.txt directly calls pkg_check_modules(libffi REQUIRED libffi) + # but package.xml never declares a for it, so it's never resolved + # into the host env at all (surfaced on wasm32 since cross-python_emscripten-wasm32 + # doesn't transitively pull in libffi the way a native "python" package does). + add_host: ["libffi"] foxglove_bridge: add_host: ["ros-humble-ament-cmake"] ros_ign_interfaces: @@ -16,11 +22,33 @@ cartographer: remove_host: ["lua", "gflags"] remove_run: ["lua", "gflags"] libyaml_vendor: - add_host: ["yaml-cpp", "yaml"] - add_run: ["yaml-cpp", "yaml"] + # emscripten-forge/recipes publishes the C library under its newer name + # "libyaml" (not the legacy conda-forge "yaml"), and doesn't publish + # "yaml-cpp" at all for wasm32 -- libyaml_vendor's own CMakeLists only + # needs plain libyaml (its Findyaml.cmake falls back to pkg-config's + # "yaml-0.1" module, which the conda "libyaml" package still provides + # regardless of the conda package's own name). + add_host: + - if: wasm32 + then: ["libyaml"] + else: ["yaml-cpp", "yaml"] + add_run: + - if: wasm32 + then: ["libyaml"] + else: ["yaml-cpp", "yaml"] yaml_cpp_vendor: - add_host: ["yaml-cpp"] - add_run: ["yaml-cpp"] + # No "yaml-cpp" conda package exists for emscripten-wasm32 (confirmed + # absent from emscripten-forge-4x's repodata, 2026-09-07). Omitting the + # host/run dep for wasm32 makes yaml_cpp_vendor's own + # find_package(yaml-cpp QUIET) fail as designed, so its CMakeLists falls + # back to its original ExternalProject_Add source build instead of + # redirecting to a nonexistent system package. + add_host: + - if: not wasm32 + then: ["yaml-cpp"] + add_run: + - if: not wasm32 + then: ["yaml-cpp"] zstd_vendor: add_host: ["zstd", "${{ 'zstd-static' if not win }}"] add_run: ["zstd"] @@ -222,8 +250,16 @@ rqt_image_overlay: tracetools: remove_host: [ "pkg-config" ] spdlog_vendor: - add_host: ["spdlog"] - add_run: ["spdlog"] + # No spdlog conda package exists for emscripten-wasm32 (same rationale as + # yaml_cpp_vendor above): omit the host/run dep so its own + # find_package(spdlog QUIET) fails as designed and it falls back to its + # ExternalProject source build. + add_host: + - if: not wasm32 + then: ["spdlog"] + add_run: + - if: not wasm32 + then: ["spdlog"] rcl_logging_spdlog: add_host: ["${{ 'fmt' if emscripten }}"] rcl: diff --git a/patch/ros-humble-rcutils.emscripten.patch b/patch/ros-humble-rcutils.emscripten.patch new file mode 100644 index 000000000..6619254bb --- /dev/null +++ b/patch/ros-humble-rcutils.emscripten.patch @@ -0,0 +1,62 @@ +--- a/src/time_unix.c 2026-09-08 19:31:05 ++++ b/src/time_unix.c 2026-09-08 19:52:59 +@@ -61,6 +61,23 @@ + mach_port_deallocate(mach_task_self(), cclock); + timespec_now.tv_sec = mts.tv_sec; + timespec_now.tv_nsec = mts.tv_nsec; ++#elif defined(__EMSCRIPTEN__) ++ // clock_gettime(CLOCK_REALTIME, ...) silently returns a struct that's ++ // still all-zero (tv_sec=0, tv_nsec=0) here specifically when main() has ++ // been moved onto a real pthread Worker via -s PROXY_TO_PTHREAD=1 (its ++ // return value isn't checked below, matching upstream, so this could ++ // also be a silent failure rather than a genuine epoch-0 result -- either ++ // way the wall-clock value coming out of it is unusable). A timer whose ++ // rcl_clock_get_now() always returns the same value never looks any ++ // closer to its next scheduled firing, so timers created against this ++ // clock (rclcpp::Node's default, used by create_wall_timer() whenever ++ // sim time isn't active) fire exactly once and then never again. ++ // CLOCK_MONOTONIC does not have this problem in the same worker context, ++ // so use it here too -- this trades true wall-clock alignment (logged ++ // timestamps become time-since-worker-start instead of real UTC) for a ++ // clock that actually advances, which is what every caller of "system ++ // time" in this codebase actually needs it for. ++ clock_gettime(CLOCK_MONOTONIC, ×pec_now); + #else // defined(__MACH__) + // Otherwise use clock_gettime. + clock_gettime(CLOCK_REALTIME, ×pec_now); +@@ -90,11 +107,20 @@ + timespec_now.tv_nsec = mts.tv_nsec; + #else // defined(__MACH__) + // Otherwise use clock_gettime. +-#if defined(CLOCK_MONOTONIC_RAW) ++ // CLOCK_MONOTONIC_RAW is #define'd by emscripten's headers (so the ++ // defined() check below picks it) but the wasm32/pthread runtime's ++ // actual implementation of it is broken here -- it silently writes back ++ // a bogus timespec (in practice: tv_nsec's value ends up read out where ++ // tv_sec is expected, producing a "now" that looks like tv_nsec * 1e9 ++ // nanoseconds instead of the real time), even though plain ++ // CLOCK_MONOTONIC on the exact same build works correctly. Skip the ++ // RAW variant entirely under emscripten rather than trusting whichever ++ // clock ID the SDK headers happen to define. ++#if defined(CLOCK_MONOTONIC_RAW) && !defined(__EMSCRIPTEN__) + clock_gettime(CLOCK_MONOTONIC_RAW, ×pec_now); +-#else // defined(CLOCK_MONOTONIC_RAW) ++#else // defined(CLOCK_MONOTONIC_RAW) && !defined(__EMSCRIPTEN__) + clock_gettime(CLOCK_MONOTONIC, ×pec_now); +-#endif // defined(CLOCK_MONOTONIC_RAW) ++#endif // defined(CLOCK_MONOTONIC_RAW) && !defined(__EMSCRIPTEN__) + #endif // defined(__MACH__) + if (__WOULD_BE_NEGATIVE(timespec_now.tv_sec, timespec_now.tv_nsec)) { + RCUTILS_SET_ERROR_MSG("unexpected negative time"); +@@ -110,7 +136,10 @@ + RCUTILS_CHECK_ARGUMENT_FOR_NULL(now, RCUTILS_RET_INVALID_ARGUMENT); + struct timespec timespec_now; + +-#if defined(CLOCK_MONOTONIC_RAW) ++ // See the matching comment in rcutils_steady_time_now() above: skip ++ // CLOCK_MONOTONIC_RAW under emscripten, it's broken here even though ++ // the headers define it. ++#if defined(CLOCK_MONOTONIC_RAW) && !defined(__EMSCRIPTEN__) + clockid_t monotonic_raw_clock = CLOCK_MONOTONIC_RAW; + #else + clockid_t monotonic_raw_clock = CLOCK_MONOTONIC; diff --git a/patch/ros-humble-rmw-wasm-cpp.patch b/patch/ros-humble-rmw-wasm-cpp.patch new file mode 100644 index 000000000..c9c585df5 --- /dev/null +++ b/patch/ros-humble-rmw-wasm-cpp.patch @@ -0,0 +1,85 @@ +--- a/rmw_wasm_cpp/CMakeLists.txt 2026-09-08 18:31:13 ++++ b/rmw_wasm_cpp/CMakeLists.txt 2026-09-08 18:31:45 +@@ -29,11 +29,36 @@ + find_package(rosidl_runtime_cpp REQUIRED) + find_package(rosidl_typesupport_introspection_c REQUIRED) + find_package(rosidl_typesupport_introspection_cpp REQUIRED) +-find_package(BZip2 REQUIRED) +-find_package(SQLite3 REQUIRED) +-find_package(ZLIB REQUIRED) ++# BZip2/SQLite3/ZLIB are find_package()'d and target_link_libraries()'d ++# below but nothing in rmw_wasm_cpp's own sources (src/*.cpp) actually ++# calls into any of them -- dead weight left over from whatever RMW ++# implementation this CMakeLists.txt was originally templated from. On ++# every other platform that's harmless (conda-forge ships all three, so ++# find_package() just succeeds and the unused link entries are inert). ++# Under emscripten it isn't harmless: emscripten-forge's prebuilt ++# libsqlite3.a/libbz2.a static libs weren't compiled with the ++# atomics/bulk-memory wasm features our pthread-enabled build now ++# requires (we have no way to add compile flags to an already-built ++# external static archive), so pulling them into this SIDE_MODULE link ++# fails with "wasm-ld: error: --shared-memory is disallowed by ++# sqlite3.o ... because it was not compiled with 'atomics' or ++# 'bulk-memory' features." Skip them entirely under emscripten instead ++# of trying to rebuild emscripten-forge's own packages with pthread ++# support just to satisfy an unused dependency. ++if(NOT EMSCRIPTEN) ++ find_package(BZip2 REQUIRED) ++ find_package(SQLite3 REQUIRED) ++ find_package(ZLIB REQUIRED) ++endif() + find_package(PkgConfig REQUIRED) +-pkg_check_modules(libffi REQUIRED libffi) ++if(EMSCRIPTEN) ++ # emscripten-forge's libffi package ships headers and a static lib but no ++ # libffi.pc, so pkg_check_modules() can never find it here (it works fine ++ # on every other platform, where conda-forge's libffi does ship one). ++ find_library(libffi_LINK_LIBRARIES NAMES ffi libffi REQUIRED) ++else() ++ pkg_check_modules(libffi REQUIRED libffi) ++endif() + + set(${PROJECT_NAME}_SOURCES + src/rmw_wasm_identifier.cpp +@@ -97,10 +122,14 @@ + rosidl_typesupport_introspection_c::rosidl_typesupport_introspection_c + rosidl_typesupport_introspection_cpp::rosidl_typesupport_introspection_cpp + ${libffi_LINK_LIBRARIES} +- ${SQLite3_LIBRARIES} +- BZip2::BZip2 +- ZLIB::ZLIB + ) ++if(NOT EMSCRIPTEN) ++ target_link_libraries(${PROJECT_NAME} ++ ${SQLite3_LIBRARIES} ++ BZip2::BZip2 ++ ZLIB::ZLIB ++ ) ++endif() + + ament_export_dependencies( + rmw +--- a/rmw_wasm_cpp/src/rmw_wait.cpp ++++ b/rmw_wasm_cpp/src/rmw_wait.cpp +@@ -102,6 +102,21 @@ + auto rmw_wasm_wait_set = static_cast(wait_set->data); + auto wasm_wait_set = rmw_wasm_wait_set->wasm_wait_set; + ++ // wasm_wait_set is one long-lived object (created once in ++ // rmw_create_wait_set() and reused for the executor's entire ++ // lifetime), but add_subscriber()/add_guard_condition() below just ++ // push onto its internal vectors with no de-duplication. Without ++ // clearing it first, every rmw_wait() call (i.e. every executor ++ // spin iteration) re-adds the same entities on top of the ones ++ // from all previous iterations, so its subscriber count grows ++ // without bound. On the 2nd call this already makes the ++ // `subscriptions->subscriber_count == wait_set_subscriptions.size()` ++ // assert below fail (aborting the whole program); with assertions ++ // compiled out it instead corrupts wasm_wait_set's internal state ++ // enough to eventually crash with an unrelated-looking ++ // "memory access out of bounds" a few iterations later. ++ wasm_wait_set->clear(); ++ + // TODO: add objects to wait set + if (subscriptions) { + for (size_t i = 0u; i < subscriptions->subscriber_count; i++) { diff --git a/patch/ros-humble-rosidl-generator-py.patch b/patch/ros-humble-rosidl-generator-py.patch index 10811adcb..e9002a015 100644 --- a/patch/ros-humble-rosidl-generator-py.patch +++ b/patch/ros-humble-rosidl-generator-py.patch @@ -2,7 +2,7 @@ diff --git a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.c index a6d8263e..552d7acf 100644 --- a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake +++ b/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake -@@ -17,10 +17,22 @@ find_package(rosidl_runtime_c REQUIRED) +@@ -17,10 +17,27 @@ find_package(rosidl_runtime_c REQUIRED) find_package(rosidl_typesupport_c REQUIRED) find_package(rosidl_typesupport_interface REQUIRED) @@ -19,7 +19,12 @@ index a6d8263e..552d7acf 100644 + find_package(Python REQUIRED COMPONENTS Interpreter Development NumPy) + find_package(PythonExtra REQUIRED) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${_PREV_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) -+ set(Python_NumPy_INCLUDE_DIRS "$ENV{PREFIX}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages/numpy/core/include") ++ # NumPy >=2.0 renamed its private "core" package to "_core", moving the ++ # bundled C headers from numpy/core/include to numpy/_core/include (the ++ # old numpy/core/include path no longer exists at all, confirmed via ++ # our numpy 2.5.x host env, 2026-09-08) -- our conda_build_config.yaml ++ # pins numpy 2.*, so this must use the new path. ++ set(Python_NumPy_INCLUDE_DIRS "$ENV{PREFIX}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages/numpy/_core/include") + message(WARNING "Python_NumPy_INCLUDE_DIRS: ${Python_NumPy_INCLUDE_DIRS}") +else() + find_package(PythonExtra REQUIRED) diff --git a/patch/ros-humble-spdlog-vendor.emscripten.patch b/patch/ros-humble-spdlog-vendor.emscripten.patch new file mode 100644 index 000000000..14f8268c1 --- /dev/null +++ b/patch/ros-humble-spdlog-vendor.emscripten.patch @@ -0,0 +1,66 @@ +--- a/CMakeLists.txt 2026-09-08 18:37:25 ++++ b/CMakeLists.txt 2026-09-08 18:38:16 +@@ -9,7 +9,62 @@ + cmake_policy(SET CMP0135 NEW) + endif() + +-find_package(spdlog REQUIRED) ++# No spdlog conda package exists for emscripten-wasm32, so unlike every ++# other platform (where RoboStack always finds the real conda-forge ++# package and this REQUIRED find_package never actually builds anything) ++# fall back to building spdlog from source here, same as upstream ++# spdlog_vendor did before RoboStack's ament_vendor patch simplified this ++# file down to a bare REQUIRED find_package. ++find_package(spdlog QUIET) ++if(NOT spdlog_FOUND) ++ set(extra_cmake_args) ++ if(DEFINED CMAKE_BUILD_TYPE) ++ list(APPEND extra_cmake_args -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) ++ endif() ++ if(DEFINED CMAKE_TOOLCHAIN_FILE) ++ list(APPEND extra_cmake_args "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}") ++ else() ++ list(APPEND extra_cmake_args "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") ++ endif() ++ list(APPEND extra_cmake_args "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}") ++ # Forward the parent build's CMAKE_PROJECT_INCLUDE (vinca's ++ # __vinca_shared_lib_patch.cmake under emscripten) into this nested ++ # ExternalProject build. Without it, this inner spdlog build never sees ++ # the global SIDE_MODULE/pthread/etc. settings the outer build applies ++ # to every other target, so e.g. its object files get compiled without ++ # the atomics/bulk-memory wasm features our pthread-enabled build ++ # requires -- silently producing a libspdlogd.a that fails to link ++ # ("wasm-ld: error: --shared-memory is disallowed by spdlog.cpp.o ++ # because it was not compiled with 'atomics' or 'bulk-memory' ++ # features") into anything that actually links it (rcl_logging_spdlog). ++ if(DEFINED CMAKE_PROJECT_INCLUDE) ++ list(APPEND extra_cmake_args "-DCMAKE_PROJECT_INCLUDE=${CMAKE_PROJECT_INCLUDE}") ++ endif() ++ list(APPEND extra_cmake_args "-DSPDLOG_BUILD_TESTS=OFF") ++ list(APPEND extra_cmake_args "-DSPDLOG_BUILD_EXAMPLE=OFF") ++ list(APPEND extra_cmake_args "-DSPDLOG_BUILD_SHARED=ON") + ++ include(ExternalProject) ++ ++ externalproject_add(spdlog-1.8.2 ++ URL https://github.com/gabime/spdlog/archive/v1.8.2.tar.gz ++ URL_MD5 22518fb28d4be66c92a703c67d99b1d1 ++ TIMEOUT 600 ++ CMAKE_ARGS ++ -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_install ++ ${extra_cmake_args} ++ ) ++ ++ install( ++ DIRECTORY ++ ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_install/ ++ DESTINATION ++ ${CMAKE_INSTALL_PREFIX} ++ USE_SOURCE_PERMISSIONS ++ ) ++else() ++ find_package(spdlog REQUIRED) ++endif() ++ + # this ensures that the package has an environment hook setting the PATH + ament_package() diff --git a/patch/ros-humble-test-wasm.patch b/patch/ros-humble-test-wasm.patch index 0bd44370f..21ba461fd 100644 --- a/patch/ros-humble-test-wasm.patch +++ b/patch/ros-humble-test-wasm.patch @@ -1,54 +1,267 @@ -diff --git a/test_wasm/CMakeLists.txt b/test_wasm/CMakeLists.txt -index c6c95b9..0c6e621 100644 ---- a/test_wasm/CMakeLists.txt -+++ b/test_wasm/CMakeLists.txt -@@ -10,21 +10,22 @@ find_package(ament_cmake REQUIRED) - find_package(rclcpp REQUIRED) - find_package(std_msgs REQUIRED) - find_package(example_interfaces REQUIRED) -+find_package(Python COMPONENTS Interpreter Development REQUIRED) - - # Targets and link - include(cmake/WasmBuildOptions.cmake) - - add_executable(talker src/test_publisher.cpp) --ament_target_dependencies(talker rclcpp std_msgs) -+target_link_libraries(talker PUBLIC ${std_msgs_TARGETS} rclcpp::rclcpp Python::Python) - - add_executable(listener src/test_subscriber.cpp) --ament_target_dependencies(listener rclcpp std_msgs) -+target_link_libraries(listener PUBLIC ${std_msgs_TARGETS} rclcpp::rclcpp Python::Python) - - add_executable(server src/test_service_server.cpp) --ament_target_dependencies(server rclcpp example_interfaces) -+target_link_libraries(server PUBLIC example_interfaces::example_interfaces__rosidl_typesupport_c example_interfaces::example_interfaces__rosidl_typesupport_cpp rclcpp::rclcpp Python::Python) - - add_executable(client src/test_service_client.cpp) --ament_target_dependencies(client rclcpp example_interfaces) -+target_link_libraries(client PUBLIC example_interfaces::example_interfaces__rosidl_typesupport_c example_interfaces::example_interfaces__rosidl_typesupport_cpp rclcpp::rclcpp Python::Python) - - add_compile_options("SHELL:-s USE_PTHREADS") - -diff --git a/test_wasm/cmake/WasmBuildOptions.cmake b/test_wasm/cmake/WasmBuildOptions.cmake -index e6f4aab..4cfd775 100644 ---- a/test_wasm/cmake/WasmBuildOptions.cmake -+++ b/test_wasm/cmake/WasmBuildOptions.cmake -@@ -18,7 +18,7 @@ function(wasm_link_options) +--- a/test_wasm/cmake/WasmBuildOptions.cmake 2026-09-08 13:05:36 ++++ b/test_wasm/cmake/WasmBuildOptions.cmake 2026-09-08 19:05:26 +@@ -13,19 +13,87 @@ + foreach(arg IN LISTS ARGN) + message(STATUS "Setting link options for ${arg}\n") + target_link_options("${arg}" +- PUBLIC "SHELL: -lembind" ++ # Not added directly here: vinca's own build script already ++ # forces -lembind onto every wasm32 MAIN_MODULE executable's ++ # CMAKE_EXE_LINKER_FLAGS. Also linking it here made em++ pass ++ # -lembind twice, linking libembind-rtti.a twice and producing ++ # "duplicate symbol: _emval_coro_resume" etc. + PUBLIC "SHELL: --pre-js ${CMAKE_SOURCE_DIR}/pre.js" PUBLIC "SHELL: -fexceptions" - PUBLIC "SHELL: -s ASYNCIFY=1" +- PUBLIC "SHELL: -s ASYNCIFY=1" ++ # ASYNCIFY=1 combined with -fexceptions (wasm exception ++ # handling) crashed wasm-ld outright (SIGSEGV) linking ++ # talker/listener/client/server -- em++ itself warns these two ++ # are incompatible ("Parts of the program that mix ASYNCIFY ++ # and exceptions will not compile"), and here it took down the ++ # linker instead of just producing a broken binary. PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" - PUBLIC "SHELL: -s DEMANGLE_SUPPORT=1" -+ # PUBLIC "SHELL: -s DEMANGLE_SUPPORT=1" - PUBLIC "SHELL: -s ASSERTIONS=1" +- PUBLIC "SHELL: -s ASSERTIONS=1" ++ # -s DEMANGLE_SUPPORT=1 was removed in emscripten 4.0.9 (the ++ # SDK version we build against): "No longer supported". ++ # cross-python_emscripten-wasm32's libpython3.13.a is one ++ # monolithic static archive bundling every CPython stdlib C ++ # extension (_ssl, _lzma, etc), so any message package's ++ # rosidl_generator_py .so that touches Python:: at all drags in ++ # unresolved OpenSSL/liblzma symbol references too -- dead code ++ # for a pure C++ ROS node like talker/listener that never ++ # imports ssl or lzma, but em++'s MAIN_MODULE link (unlike a ++ # SIDE_MODULE .so, which tolerates unresolved cross-module ++ # symbols via lazy binding) requires every symbol resolvable. ++ # Since these Python C-API/stdlib symbols are genuinely never ++ # called at runtime here, stub them out instead of chasing an ++ # ever-growing chain of transitive C libraries (openssl, ++ # liblzma, and whatever CPython's static build needs next). ++ PUBLIC "SHELL: -s ERROR_ON_UNDEFINED_SYMBOLS=0" ++ # -s ASSERTIONS=1 adds its OWN separate runtime check (beyond ++ # the link-time one already suppressed above) that aborts on ++ # first use of any wasm module whose imports don't all ++ # resolve -- for a DATA symbol like PyFloat_Type (not a ++ # function), ERROR_ON_UNDEFINED_SYMBOLS=0 can stub a callable ++ # fallback but there's no meaningful stub for "the address of ++ # a missing global", so ASSERTIONS' own check still aborted ++ # startup ("undefined symbol 'PyFloat_Type'... side module not ++ # linked in") even though nothing in a plain ROS publisher ++ # node actually touches CPython's float type. Turn it off. ++ PUBLIC "SHELL: -s ASSERTIONS=0" PUBLIC "SHELL: -s EXIT_RUNTIME=1" PUBLIC "SHELL: -s WASM=1" -@@ -26,7 +26,8 @@ function(wasm_link_options) +- PUBLIC "SHELL: -s USE_PTHREADS=0" ++ # By default, main() runs directly on whatever JS thread loads ++ # the module -- the browser's real main/UI thread here, since ++ # nothing proxies it elsewhere. rclcpp::spin()'s blocking wait ++ # (a real pthread_cond_timedwait now that USE_PTHREADS=1) needs ++ # to call Atomics.wait() under the hood to actually block a ++ # thread, but browsers refuse Atomics.wait() on the main ++ # thread (it would freeze the page's own event loop, which is ++ # exactly what a real OS thread block does NOT do to anything ++ # else). Atomics.wait() being disallowed there doesn't throw -- ++ # emscripten's pthread emulation just silently never resolves ++ # the wait, so main() looked like it "hung" after the first ++ # spin iteration with no error at all. PROXY_TO_PTHREAD moves ++ # main() onto an actual Worker thread (a real pthread), leaving ++ # the page's own main thread free to keep pumping its event ++ # loop while that worker genuinely blocks. ++ PUBLIC "SHELL: -s PROXY_TO_PTHREAD=1" ++ # USE_PTHREADS is intentionally left unset here: vinca's own ++ # build template now sets it globally (compile+link, via ++ # CMAKE_PROJECT_INCLUDE) to 1 for every emscripten-wasm32 ++ # package, since real threads are required for libc++'s ++ # condition_variable timed-wait (used by rclcpp's executor / ++ # rmw wait set) to actually wake up on its own -- without a ++ # real OS thread there's no way for a synchronous blocking ++ # wait to let wall-clock time advance underneath it, so the ++ # executor hung forever after its first spin iteration. ++ # Setting it explicitly to 0 or 1 here would just override ++ # (and potentially conflict with) that global default; the ++ # later flag on the em++ command line wins, so don't repeat ++ # it here at all. PUBLIC "SHELL: -s TOTAL_STACK=16mb" PUBLIC "SHELL: -s INITIAL_MEMORY=64mb" - PUBLIC "SHELL: -s ASYNCIFY_STACK_SIZE=24576" -- PUBLIC "SHELL: -03" # Optimize for async -+ PUBLIC "SHELL: -03" # Optimize for async +- PUBLIC "SHELL: -s ASYNCIFY_STACK_SIZE=24576" ++ # em++ auto-minifies .html output at -O3 by shelling out to ++ # node_modules/.bin/html-minifier-terser inside the emscripten ++ # SDK -- that install is broken in our emscripten-forge SDK ++ # package (ERR_MODULE_NOT_FOUND: its own src/htmlminifier.js ++ # is missing), crashing every .html link. Nothing we produce ++ # needs minified HTML, so just skip that step outright. + PUBLIC "SHELL: -s MINIFY_HTML=0" + PUBLIC "SHELL: -03" # Optimize for async ) endforeach(arg IN LISTS ARGN) - endfunction() +--- a/test_wasm/src/test_publisher.cpp 2026-09-08 19:39:25 ++++ b/test_wasm/src/test_publisher.cpp 2026-09-08 19:53:41 +@@ -13,6 +13,7 @@ + // limitations under the License. + + // #include ++#include + #include + #include + #include +@@ -55,7 +56,21 @@ + + int main(int argc, char * argv[]) + { +- rclcpp::init(argc, argv); ++ // Two independent things in this ROS init path each try to spawn a ++ // background std::thread, which fails with EAGAIN and throws ++ // std::system_error("thread constructor failed: Resource temporarily ++ // unavailable") because we build without real pthreads (USE_PTHREADS=0, ++ // see test_wasm/cmake/WasmBuildOptions.cmake): ++ // 1. rcl_logging_spdlog's rcl_logging_external_initialize() calls ++ // spdlog::flush_every(5s) unconditionally, which starts spdlog's ++ // periodic_worker thread -- unless this env var is set, which ++ // rcl_logging_spdlog itself checks for and honors. ++ // 2. rclcpp::init()'s default SignalHandlerOptions::All installs a ++ // SIGINT/SIGTERM handler backed by its own dedicated std::thread. ++ // There's no real process to signal in the browser sandbox anyway, ++ // so skip installing it. ++ setenv("RCL_LOGGING_SPDLOG_EXPERIMENTAL_OLD_FLUSHING_BEHAVIOR", "1", 1); ++ rclcpp::init(argc, argv, rclcpp::InitOptions(), rclcpp::SignalHandlerOptions::None); + rclcpp::spin(std::make_shared()); + + rclcpp::shutdown(); +--- a/test_wasm/src/test_subscriber.cpp 2026-09-08 16:11:18 ++++ b/test_wasm/src/test_subscriber.cpp 2026-09-08 16:26:11 +@@ -12,6 +12,7 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++#include + #include + #include + +@@ -40,7 +41,21 @@ + + int main(int argc, char * argv[]) + { +- rclcpp::init(argc, argv); ++ // Two independent things in this ROS init path each try to spawn a ++ // background std::thread, which fails with EAGAIN and throws ++ // std::system_error("thread constructor failed: Resource temporarily ++ // unavailable") because we build without real pthreads (USE_PTHREADS=0, ++ // see test_wasm/cmake/WasmBuildOptions.cmake): ++ // 1. rcl_logging_spdlog's rcl_logging_external_initialize() calls ++ // spdlog::flush_every(5s) unconditionally, which starts spdlog's ++ // periodic_worker thread -- unless this env var is set, which ++ // rcl_logging_spdlog itself checks for and honors. ++ // 2. rclcpp::init()'s default SignalHandlerOptions::All installs a ++ // SIGINT/SIGTERM handler backed by its own dedicated std::thread. ++ // There's no real process to signal in the browser sandbox anyway, ++ // so skip installing it. ++ setenv("RCL_LOGGING_SPDLOG_EXPERIMENTAL_OLD_FLUSHING_BEHAVIOR", "1", 1); ++ rclcpp::init(argc, argv, rclcpp::InitOptions(), rclcpp::SignalHandlerOptions::None); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +--- a/test_wasm/src/test_service_server.cpp 2026-09-08 16:11:18 ++++ b/test_wasm/src/test_service_server.cpp 2026-09-08 16:26:11 +@@ -1,6 +1,7 @@ + #include "rclcpp/rclcpp.hpp" + #include "example_interfaces/srv/add_two_ints.hpp" + ++#include + #include + + void add(const std::shared_ptr request, +@@ -14,7 +15,21 @@ + + int main(int argc, char **argv) + { +- rclcpp::init(argc, argv); ++ // Two independent things in this ROS init path each try to spawn a ++ // background std::thread, which fails with EAGAIN and throws ++ // std::system_error("thread constructor failed: Resource temporarily ++ // unavailable") because we build without real pthreads (USE_PTHREADS=0, ++ // see test_wasm/cmake/WasmBuildOptions.cmake): ++ // 1. rcl_logging_spdlog's rcl_logging_external_initialize() calls ++ // spdlog::flush_every(5s) unconditionally, which starts spdlog's ++ // periodic_worker thread -- unless this env var is set, which ++ // rcl_logging_spdlog itself checks for and honors. ++ // 2. rclcpp::init()'s default SignalHandlerOptions::All installs a ++ // SIGINT/SIGTERM handler backed by its own dedicated std::thread. ++ // There's no real process to signal in the browser sandbox anyway, ++ // so skip installing it. ++ setenv("RCL_LOGGING_SPDLOG_EXPERIMENTAL_OLD_FLUSHING_BEHAVIOR", "1", 1); ++ rclcpp::init(argc, argv, rclcpp::InitOptions(), rclcpp::SignalHandlerOptions::None); + + std::shared_ptr node = rclcpp::Node::make_shared("add_two_ints_server"); + +--- a/test_wasm/src/test_service_client.cpp 2026-09-08 16:11:18 ++++ b/test_wasm/src/test_service_client.cpp 2026-09-08 16:25:58 +@@ -9,7 +9,21 @@ + + int main(int argc, char **argv) + { +- rclcpp::init(argc, argv); ++ // Two independent things in this ROS init path each try to spawn a ++ // background std::thread, which fails with EAGAIN and throws ++ // std::system_error("thread constructor failed: Resource temporarily ++ // unavailable") because we build without real pthreads (USE_PTHREADS=0, ++ // see test_wasm/cmake/WasmBuildOptions.cmake): ++ // 1. rcl_logging_spdlog's rcl_logging_external_initialize() calls ++ // spdlog::flush_every(5s) unconditionally, which starts spdlog's ++ // periodic_worker thread -- unless this env var is set, which ++ // rcl_logging_spdlog itself checks for and honors. ++ // 2. rclcpp::init()'s default SignalHandlerOptions::All installs a ++ // SIGINT/SIGTERM handler backed by its own dedicated std::thread. ++ // There's no real process to signal in the browser sandbox anyway, ++ // so skip installing it. ++ setenv("RCL_LOGGING_SPDLOG_EXPERIMENTAL_OLD_FLUSHING_BEHAVIOR", "1", 1); ++ rclcpp::init(argc, argv, rclcpp::InitOptions(), rclcpp::SignalHandlerOptions::None); + + if (argc != 3) { + RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "[client] add_two_ints_client X Y"); +--- a/test_wasm/CMakeLists.txt 2026-09-08 15:05:16 ++++ b/test_wasm/CMakeLists.txt 2026-09-08 15:05:16 +@@ -41,4 +41,35 @@ + DESTINATION lib/${PROJECT_NAME} + ) + ++# install(TARGETS ...) only installs the primary executable output (the ++# .html shell, per CMAKE_EXECUTABLE_SUFFIX above) -- it does not know ++# about the .js glue code and .wasm binary em++ generates alongside it, ++# so those never made it into the package at all (confirmed: only the ++# four .html files existed under lib/test_wasm/, and every one 404'd ++# trying to fetch its own .js at runtime). ++install( ++ FILES ++ ${CMAKE_CURRENT_BINARY_DIR}/talker.js ++ ${CMAKE_CURRENT_BINARY_DIR}/talker.wasm ++ ${CMAKE_CURRENT_BINARY_DIR}/listener.js ++ ${CMAKE_CURRENT_BINARY_DIR}/listener.wasm ++ ${CMAKE_CURRENT_BINARY_DIR}/server.js ++ ${CMAKE_CURRENT_BINARY_DIR}/server.wasm ++ ${CMAKE_CURRENT_BINARY_DIR}/client.js ++ ${CMAKE_CURRENT_BINARY_DIR}/client.wasm ++ DESTINATION lib/${PROJECT_NAME} ++) ++ ++# emscripten 4.0.9's own generated dylib-loading runtime (the addEmJs() ++# helper embedded in every one of these .js files, used to resolve ++# EM_JS-defined functions from dynamically-loaded .so's) strips only the ++# FIRST "*" from a C parameter type string ("jsArg.replace(\"*\",\"\")", ++# a non-global String.replace), so any EM_JS function with a ++# double-pointer argument leaves one "*" behind in the generated JS ++# parameter name -- "Uncaught SyntaxError: Unexpected token '*'" at ++# startup, before any of our own code ever runs. This is emscripten SDK ++# runtime content, not something in wasm_cpp/rmw_wasm_cpp/our own ++# sources to fix; patch the installed .js files directly. ++install(SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/fix_addEmJs.cmake) ++ + ament_package() +--- /dev/null ++++ b/test_wasm/fix_addEmJs.cmake 2026-09-08 15:05:16 +@@ -0,0 +1,6 @@ ++file(GLOB _wasm_cpp_js_files "${CMAKE_INSTALL_PREFIX}/lib/test_wasm/*.js") ++foreach(_wasm_cpp_js_file ${_wasm_cpp_js_files}) ++ file(READ "${_wasm_cpp_js_file}" _wasm_cpp_js_content) ++ string(REPLACE [[jsArg.replace("*","")]] [[jsArg.replaceAll("*","")]] _wasm_cpp_js_content "${_wasm_cpp_js_content}") ++ file(WRITE "${_wasm_cpp_js_file}" "${_wasm_cpp_js_content}") ++endforeach() diff --git a/patch/ros-humble-wasm-cpp.patch b/patch/ros-humble-wasm-cpp.patch new file mode 100644 index 000000000..54060b6cc --- /dev/null +++ b/patch/ros-humble-wasm-cpp.patch @@ -0,0 +1,62 @@ +--- a/wasm_cpp/CMakeLists.txt 2026-09-08 10:48:06 ++++ b/wasm_cpp/CMakeLists.txt 2026-09-08 10:48:42 +@@ -36,7 +36,11 @@ + wasm_compile_options(wasm_cpp) + wasm_link_options(wasm_cpp) + +-ament_target_dependencies(${PROJECT_NAME}) ++# Upstream calls find_package(yaml_cpp_vendor REQUIRED) above but never ++# actually wires it into the wasm_cpp target, silently dropping yaml-cpp's ++# include directories ("yaml-cpp/yaml.h" file not found compiling ++# roslibjs.cpp/tojson.hpp, which #include it directly). ++ament_target_dependencies(${PROJECT_NAME} yaml_cpp_vendor) + + target_include_directories(${PROJECT_NAME} PRIVATE + "$" +--- a/wasm_cpp/cmake/WasmBuildOptions.cmake 2026-09-08 18:57:00 ++++ b/wasm_cpp/cmake/WasmBuildOptions.cmake 2026-09-08 18:57:13 +@@ -9,16 +9,41 @@ + + function(wasm_link_options target) + target_link_options("${target}" +- PUBLIC "SHELL: -lembind" ++ # Deliberately not linked here: wasm_cpp is a SIDE_MODULE (a .so ++ # dynamically loaded by a MAIN_MODULE executable like test_wasm), ++ # and vinca's own build script already forces -lembind onto every ++ # wasm32 MAIN_MODULE executable's CMAKE_EXE_LINKER_FLAGS. Also ++ # linking it here made wasm_cpp bring its OWN separate copy of ++ # embind's JS runtime, so both it and the main executable ++ # independently registered the same basic types ("void", "bool", ++ # ...) into embind's shared registry at load time -- ++ # "BindingError: Cannot register type 'void' twice" at startup. ++ # The main executable is meant to be the sole provider of ++ # embind's JS library; side modules should just import from it. + PUBLIC "SHELL: --pre-js ${CMAKE_SOURCE_DIR}/src/pre.js" + PUBLIC "SHELL: -fexceptions" + # PUBLIC "SHELL: -s ASYNCIFY=1" + PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" +- PUBLIC "SHELL: -s DEMANGLE_SUPPORT=1" ++ # -s DEMANGLE_SUPPORT=1 was removed in emscripten 4.0.9 (the SDK ++ # version we build against): "No longer supported", rejected ++ # outright by em++ at link time. + PUBLIC "SHELL: -s ASSERTIONS=1" + PUBLIC "SHELL: -s EXIT_RUNTIME=1" + PUBLIC "SHELL: -s WASM=1" +- PUBLIC "SHELL: -s USE_PTHREADS=0" ++ # USE_PTHREADS is intentionally left unset here: vinca's own build ++ # template now sets it globally (compile+link, via ++ # CMAKE_PROJECT_INCLUDE) to 1 for every emscripten-wasm32 package, ++ # since real threads are required for libc++'s condition_variable ++ # timed-wait (used by rclcpp's executor / rmw wait set) to ++ # actually wake up on its own. Setting it explicitly to 0 or 1 ++ # here would just override (and potentially conflict with) that ++ # global default -- the later flag on the em++ command line wins, ++ # so don't repeat it here at all. (This was previously hardcoded ++ # to 0 upstream and silently left this .so without the ++ # atomics/bulk-memory wasm features every other pthread-enabled ++ # module in the dependency tree now requires, causing ++ # "WebAssembly.instantiate(): ... shared state of memory" LinkErrors ++ # at runtime.) + PUBLIC "SHELL: -s TOTAL_STACK=16mb" + PUBLIC "SHELL: -s INITIAL_MEMORY=64mb" + PUBLIC "SHELL: -03" # Optimize for async diff --git a/patch/ros-humble-yaml-cpp-vendor.emscripten.patch b/patch/ros-humble-yaml-cpp-vendor.emscripten.patch new file mode 100644 index 000000000..ab212d5a6 --- /dev/null +++ b/patch/ros-humble-yaml-cpp-vendor.emscripten.patch @@ -0,0 +1,108 @@ +--- a/CMakeLists.txt 2026-09-08 12:41:17 ++++ b/CMakeLists.txt 2026-09-08 12:41:17 +@@ -30,6 +30,24 @@ + list(APPEND extra_cmake_args "-DYAML_BUILD_SHARED_LIBS=ON") + list(APPEND extra_cmake_args "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") + list(APPEND extra_cmake_args "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}") ++ # yaml-cpp 0.7.0's own cmake_minimum_required predates CMake 3.5, which ++ # CMake >=4.0 (ours is 4.4.3) refuses outright ("Compatibility with ++ # CMake < 3.5 has been removed"). Only reached here on wasm32, where no ++ # conda yaml-cpp package exists and this ExternalProject fallback fires. ++ list(APPEND extra_cmake_args "-DCMAKE_POLICY_VERSION_MINIMUM=3.5") ++ # Without re-propagating vinca's __vinca_shared_lib_patch.cmake (which ++ # sets TARGET_SUPPORTS_SHARED_LIBS TRUE and the emscripten ++ # MAIN_MODULE/SIDE_MODULE linker flags needed for real .so output), this ++ # nested ExternalProject build silently downgrades YAML_BUILD_SHARED_LIBS ++ # to a static libyaml-cppd.a despite the flag above -- Emscripten.cmake's ++ # default is TARGET_SUPPORTS_SHARED_LIBS=FALSE, and CMake add_library() ++ # honors that over the SHARED keyword. A statically-linked yaml-cpp then ++ # gets pulled into multiple final .wasm links (rcl directly, plus ++ # transitively via dynmsg), and unlike a real linker, wasm-ld treats the ++ # same static archive appearing twice as a hard duplicate-symbol error. ++ if(CMAKE_PROJECT_INCLUDE) ++ list(APPEND extra_cmake_args "-DCMAKE_PROJECT_INCLUDE=${CMAKE_PROJECT_INCLUDE}") ++ endif() + + if(WIN32 AND NOT ${CMAKE_VERBOSE_MAKEFILE}) + set(should_log ON) # prevent warnings in Windows CI +@@ -55,13 +73,32 @@ + ) + + # The external project will install to the build folder, but we'll install that on make install. +- install( +- DIRECTORY +- ${CMAKE_CURRENT_BINARY_DIR}/yaml_cpp_install/ +- DESTINATION +- ${CMAKE_INSTALL_PREFIX}/opt/yaml_cpp_vendor +- USE_SOURCE_PERMISSIONS +- ) ++ # On wasm32, installing under a private opt/yaml_cpp_vendor prefix (as ++ # done for every other platform, to avoid clashing with a real system ++ # yaml-cpp package) leaves the resulting libyaml-cppd.so.0.7.0 outside ++ # every consumer's emscripten -L search path -- em++'s dylink dependency ++ # resolution independently re-resolves each .so's NEEDED entries by name ++ # through -L, it doesn't just trust the full path already on the link ++ # command line. There's no real yaml-cpp package to clash with here ++ # anyway (that's the whole reason this ExternalProject fallback exists), ++ # so install straight into $PREFIX where every other .so already lives. ++ if(EMSCRIPTEN) ++ install( ++ DIRECTORY ++ ${CMAKE_CURRENT_BINARY_DIR}/yaml_cpp_install/ ++ DESTINATION ++ ${CMAKE_INSTALL_PREFIX} ++ USE_SOURCE_PERMISSIONS ++ ) ++ else() ++ install( ++ DIRECTORY ++ ${CMAKE_CURRENT_BINARY_DIR}/yaml_cpp_install/ ++ DESTINATION ++ ${CMAKE_INSTALL_PREFIX}/opt/yaml_cpp_vendor ++ USE_SOURCE_PERMISSIONS ++ ) ++ endif() + endmacro() + + # NO_CMAKE_PACKAGE_REGISTRY used to avoid finding the library downloaded in WORKSPACE B + +--- a/yaml_cpp_vendor-extras.cmake.in 2026-09-08 12:40:17 ++++ b/yaml_cpp_vendor-extras.cmake.in 2026-09-08 12:40:17 +@@ -4,6 +4,11 @@ + # add the local Modules directory to the modules path + if(WIN32) + set(yaml-cpp_DIR "${@PROJECT_NAME@_DIR}/../../../opt/yaml_cpp_vendor/CMake") ++ elseif(EMSCRIPTEN) ++ # Installed straight into $PREFIX on wasm32 (see the EMSCRIPTEN branch ++ # of build_yaml_cpp()'s install() call) so the resulting .so lands ++ # somewhere every consumer's -L search path already covers. ++ set(yaml-cpp_DIR "${@PROJECT_NAME@_DIR}/../../../share/cmake/yaml-cpp") + else() + set(yaml-cpp_DIR "${@PROJECT_NAME@_DIR}/../../../opt/yaml_cpp_vendor/share/cmake/yaml-cpp") + endif() +@@ -15,4 +20,24 @@ + set(yaml_cpp_vendor_LIBRARIES ${YAML_CPP_LIBRARIES}) + set(yaml_cpp_vendor_INCLUDE_DIRS ${YAML_CPP_INCLUDE_DIR}) + +-list(APPEND yaml_cpp_vendor_TARGETS yaml-cpp::yaml-cpp) ++# yaml-cpp 0.7.0 (built from source here, since no yaml-cpp conda package ++# exists for wasm32) only exports the plain "yaml-cpp" imported target, ++# not the "yaml-cpp::yaml-cpp" ALIAS added in later releases -- referencing ++# a nonexistent aliased target left consumers silently missing yaml-cpp's ++# include directories (confirmed: wasm_cpp's build compiled fine except ++# "yaml-cpp/yaml.h" file not found once ament_target_dependencies fell ++# through to this target list). Detect whichever target actually exists. ++if(TARGET yaml-cpp::yaml-cpp) ++ set(_yaml_cpp_vendor_real_target yaml-cpp::yaml-cpp) ++elseif(TARGET yaml-cpp) ++ set(_yaml_cpp_vendor_real_target yaml-cpp) ++endif() ++if(_yaml_cpp_vendor_real_target) ++ list(APPEND yaml_cpp_vendor_TARGETS ${_yaml_cpp_vendor_real_target}) ++ if(NOT yaml_cpp_vendor_INCLUDE_DIRS) ++ get_target_property(yaml_cpp_vendor_INCLUDE_DIRS ${_yaml_cpp_vendor_real_target} INTERFACE_INCLUDE_DIRECTORIES) ++ endif() ++ if(NOT yaml_cpp_vendor_LIBRARIES) ++ set(yaml_cpp_vendor_LIBRARIES ${_yaml_cpp_vendor_real_target}) ++ endif() ++endif() diff --git a/pixi.lock b/pixi.lock index 8316a2544..7aadd4cc6 100644 --- a/pixi.lock +++ b/pixi.lock @@ -69,7 +69,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/patchelf-0.18.0-h3f2d84a_2.conda - conda: https://prefix.dev/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.57.2-he64ecbb_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.67.0-ha759004_0.conda - conda: https://prefix.dev/conda-forge/linux-64/rattler-index-0.28.1-h58ba7e0_0.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda @@ -78,7 +78,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/colordiff-1.0.22-hd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-81.0.0-pyh332efcf_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe + - pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl @@ -141,7 +141,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/patchelf-0.18.0-h5ad3122_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/perl-5.32.1-7_h31becfc_perl5.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/python-3.11.15-h91f4b29_0_cpython.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.57.2-hb434046_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.67.0-h1d7f6d8_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-index-0.28.1-h9889dc0_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda @@ -150,7 +150,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/colordiff-1.0.22-hd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-81.0.0-pyh332efcf_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe + - pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -207,12 +207,12 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda - conda: https://prefix.dev/conda-forge/osx-64/perl-5.32.1-7_h10d778d_perl5.conda - conda: https://prefix.dev/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda - - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.57.2-h4728fb8_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.67.0-h6193a51_0.conda - conda: https://prefix.dev/conda-forge/osx-64/rattler-index-0.28.1-hbc4d974_0.conda - conda: https://prefix.dev/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - - pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe + - pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -269,12 +269,12 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/perl-5.32.1-7_h4614cfb_perl5.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.57.2-h6fdd925_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.67.0-h20b3172_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-index-0.28.1-hcb0414c_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - - pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe + - pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl @@ -329,14 +329,14 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - conda: https://prefix.dev/conda-forge/win-64/perl-5.32.1.1-7_h57928b3_strawberry.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.57.2-h18a1a76_1.conda + - conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.67.0-he94b42d_0.conda - conda: https://prefix.dev/conda-forge/win-64/rattler-index-0.28.1-h91801bb_0.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.5-h1b7c187_36.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_36.conda - conda: https://prefix.dev/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_36.conda - - pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe + - pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl @@ -877,22 +877,23 @@ packages: - python size: 30949404 timestamp: 1772730362552 -- conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.57.2-he64ecbb_1.conda - sha256: 7050df6859e1f3c1223dead79b1f4aa5b92f7519db7ad7cb5982d87fd2999852 - md5: 4d9aed902b2afb49657a4e85f493aedd +- conda: https://prefix.dev/conda-forge/linux-64/rattler-build-0.67.0-ha759004_0.conda + sha256: bc0a551dc160ab19b8e45b289896e8330cd5a2e63cd3dfee066f5f16fbd9437f + md5: 44b2514c25c7b81116b367db90128a38 depends: - patchelf - - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - openssl >=3.5.5,<4.0a0 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - openssl >=3.5.7,<4.0a0 constrains: - __glibc >=2.17 license: BSD-3-Clause license_family: BSD purls: [] run_exports: {} - size: 19271452 - timestamp: 1770649397185 + size: 19415053 + timestamp: 1782150852619 - conda: https://prefix.dev/conda-forge/linux-64/rattler-index-0.28.1-h58ba7e0_0.conda sha256: 86337fc20635e4a168d9211bc7d67ac480582132629d96130864954470276e4f md5: f1c3208c23ef5ce40297a6e6b5f832a6 @@ -1433,21 +1434,22 @@ packages: - python size: 14306513 timestamp: 1772728906052 -- conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.57.2-hb434046_1.conda - sha256: 51a369b6cfe24fa0f8f7a7ea9bb77158f8806f43b196ce5a30a61892092afe64 - md5: 9f14051749d3c1b3e0318a6b8a54b0ca +- conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-build-0.67.0-h1d7f6d8_0.conda + sha256: 47050f8918be509dd44f191368cfebdaec92b66c65a73245d75b150dfcf0810a + md5: d3cef40eb6714caa4688143870aa13c4 depends: - patchelf + - libstdcxx >=14 - libgcc >=14 - - openssl >=3.5.5,<4.0a0 + - openssl >=3.5.7,<4.0a0 constrains: - __glibc >=2.17 license: BSD-3-Clause license_family: BSD purls: [] run_exports: {} - size: 19846896 - timestamp: 1770649419199 + size: 18697949 + timestamp: 1782150880080 - conda: https://prefix.dev/conda-forge/linux-aarch64/rattler-index-0.28.1-h9889dc0_0.conda sha256: 3af5ed66a5413eb901162a7438cc396c9cfd3874e3ad10467052d4aabfc634c6 md5: 66db73ce6fe803f661ae8131c79e86d6 @@ -1922,19 +1924,20 @@ packages: - python size: 15664115 timestamp: 1772730794934 -- conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.57.2-h4728fb8_1.conda - sha256: 5ec581b4f39060c1b90687627f4b0bf04ee62d405f43072de7c83a46a9448324 - md5: 286416d9d4b0c9fedd90e0ed56be240c +- conda: https://prefix.dev/conda-forge/osx-64/rattler-build-0.67.0-h6193a51_0.conda + sha256: 3294e67bce682ec826d55028c4342e876fe794c8b0a64d9552477118b272eb65 + md5: 099fbdb2bcca4a2517ab79dcc124957a depends: - - __osx >=10.13 + - __osx >=11.0 + - libcxx >=19 constrains: - - __osx >=10.13 + - __osx >=11.0 license: BSD-3-Clause license_family: BSD purls: [] run_exports: {} - size: 17788466 - timestamp: 1770649457751 + size: 19124974 + timestamp: 1782150950334 - conda: https://prefix.dev/conda-forge/osx-64/rattler-index-0.28.1-hbc4d974_0.conda sha256: 6973021e8089c1cc1cb452a5414661573300b27e7f5f2950ad39e781de75742c md5: a9b5ea58bc9786845f3ff44622c5a523 @@ -2330,19 +2333,20 @@ packages: - python size: 14753109 timestamp: 1772730203101 -- conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.57.2-h6fdd925_1.conda - sha256: 7fa90a0d2ecb767796cadfa6f1384e52229c9cdd10c11ce49a3428048f64b91c - md5: a28d853fd6fff4914e3248343b158837 +- conda: https://prefix.dev/conda-forge/osx-arm64/rattler-build-0.67.0-h20b3172_0.conda + sha256: 61425a531cee651a7583c9fe1e8961d0eed483f72c4a01a2e248816452dcbee3 + md5: 4110e4931d5ad742288235688c786ad7 depends: - __osx >=11.0 + - libcxx >=19 constrains: - __osx >=11.0 license: BSD-3-Clause license_family: BSD purls: [] run_exports: {} - size: 16253611 - timestamp: 1770649407683 + size: 17366355 + timestamp: 1782150862478 - conda: https://prefix.dev/conda-forge/osx-arm64/rattler-index-0.28.1-hcb0414c_0.conda sha256: 41fda95ff8fbb5abbdf058314a0696819225045d66a9906ab7257ff4de5b66f5 md5: e86fc87019c5d9876c5caf9912058b78 @@ -2700,9 +2704,9 @@ packages: - python size: 18416208 timestamp: 1772728847666 -- conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.57.2-h18a1a76_1.conda - sha256: 9b575a5eaae1c427251d75ad36f6707f541e59056adcde35fca410c7f5aa29aa - md5: 545404bf30528513fd12c111b01c95a0 +- conda: https://prefix.dev/conda-forge/win-64/rattler-build-0.67.0-he94b42d_0.conda + sha256: d444b237181df08ff14d6b40101832cb1aabb4e88ed73633a58ee70318ab802a + md5: 93bf7de134af404e183be6089766060a depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 @@ -2711,8 +2715,8 @@ packages: license_family: BSD purls: [] run_exports: {} - size: 16311008 - timestamp: 1770649439173 + size: 19876803 + timestamp: 1782150883801 - conda: https://prefix.dev/conda-forge/win-64/rattler-index-0.28.1-h91801bb_0.conda sha256: 3e3e121275231ef53104a158841b5da6f920a1adb7bcd5eeec372b88e540900e md5: dee1b9a64422ccb80794ae6b085d0bc3 @@ -2796,7 +2800,7 @@ packages: - vcomp14 >=14.51.36231 size: 124124 timestamp: 1779061850036 -- pypi: git+https://github.com/RoboStack/vinca.git#4683e080a18be89703b4088c90fca8eaa8620ebe +- pypi: git+https://github.com/Tobias-Fischer/vinca.git?rev=07a8b3375641b0c4a43ede3d5de5efc38aa8f537#07a8b3375641b0c4a43ede3d5de5efc38aa8f537 name: vinca version: 0.2.0 requires_dist: diff --git a/pixi.toml b/pixi.toml index d5db39866..349434871 100644 --- a/pixi.toml +++ b/pixi.toml @@ -18,7 +18,7 @@ PYTHONIOENCODING = "utf-8" [dependencies] python = ">=3.11.0,<3.12" -rattler-build = ">=0.57.0,<0.58" +rattler-build = ">=0.67.0,<0.68" setuptools = "<82.0" rattler-index = ">=0.27.16" curl = "*" @@ -28,7 +28,40 @@ colordiff = "*" [pypi-dependencies] # emscripten-wasm32 in platforms prevents pixi-build from resolving vinca as # a conda source dep (uv is unavailable for that target), so use pypi here. -vinca = { git = "https://github.com/RoboStack/vinca.git" } +# Pinned to Tobias-Fischer/vinca#feature/emscripten-pthreads (PR open at +# https://github.com/RoboStack/vinca/pulls -- search "emscripten-pthreads"; +# branched off master rather than the previously-pinned 4683e080a since +# this branch is now based on a current master commit) until merged +# upstream, then this should be re-pointed to the merged RoboStack/vinca +# commit. Three commits on top of master: +# 1. removes the -s DEMANGLE_SUPPORT=1 emcc flag from +# build_ament_cmake.sh.in's emscripten branch, which emscripten 4.0.9 +# (the SDK version currently published as emscripten_emscripten-wasm32) +# rejects outright ("No longer supported"), breaking every wasm32 +# ament_cmake build past the first one or two. +# 2. _overlay() now converts v1 if/then/else selectors in +# pinning_overrides values into the legacy `# [selector]` comment form, +# since rattler-build's variant loader treats the raw if/then mapping +# as an opaque literal rather than evaluating it -- needed so the +# python override below can differ for build- vs host-platform +# sub-resolutions within one emscripten-wasm32 invocation. +# 3. flips USE_PTHREADS 0->1 (compile+link, globally via +# CMAKE_PROJECT_INCLUDE) for every emscripten-wasm32 package: without +# real threads, libc++'s condition_variable timed-wait (used by +# rclcpp's executor / rmw wait set) never wakes on its own, hanging the +# whole program after one spin iteration. This requires every +# emscripten-wasm32 artifact to be rebuilt from scratch -- mixing a +# pthread and non-pthread wasm module is a hard ABI mismatch ("memory +# import shared state mismatch"). +# (An earlier fourth commit removing -lembind from CMAKE_EXE_LINKER_FLAGS +# was tried and reverted during development -- the MAIN executable is the +# one that must provide embind's JS runtime library for side modules to +# import from, so removing it there broke embind entirely; the actual +# duplicate-registration fix belongs in the SIDE MODULE's own -lembind +# instead, via patch/ros-humble-wasm-cpp.patch in this repo. That +# try/revert pair was squashed out of the branch history before opening +# the PR, so it's not one of the three commits above.) +vinca = { git = "https://github.com/Tobias-Fischer/vinca.git", rev = "7133b302498edda9bc87a939329353fac95177c5" } # For local development uncomment: # vinca = { path = "../vinca", editable = true } @@ -81,7 +114,13 @@ description = "Check if there are differences between the local conda_build_conf "--recipe-dir", "./recipes", "-m", "./conda_build_config.yaml", "-c", "https://repo.prefix.dev/conda-forge", - "-c", "https://repo.mamba.pm/emscripten-forge", + # https://repo.mamba.pm/emscripten-forge is stale/legacy (empty for + # osx-arm64, ~1/3 the package count for the emscripten-wasm32 target + # vs the channel below, as of 2026-09-07) -- emscripten-forge/recipes' + # own pixi.toml (build-emscripten-compiler-pkg/build-cross-python-pkg + # tasks) publishes the emscripten SDK + cross-python toolchain here. + "-c", "https://repo.prefix.dev/emscripten-forge-4x", + "-c", "microsoft", "-c", "robostack-staging", "--target-platform", "emscripten-wasm32", "--skip-existing", diff --git a/robostack.yaml b/robostack.yaml index 4fd8864f1..2e021820b 100644 --- a/robostack.yaml +++ b/robostack.yaml @@ -81,7 +81,13 @@ doxygen: draco: robostack: [draco] eigen: - robostack: [eigen, eigen-abi-devel] + # eigen-abi-devel isn't published for emscripten-wasm32 (only plain + # eigen 3.4.1 is), so eigenpy/pinocchio/hpp-fcl couldn't resolve it there. + robostack: + linux: [eigen, eigen-abi-devel] + osx: [eigen, eigen-abi-devel] + win64: [eigen, eigen-abi-devel] + emscripten: [eigen] emacs: robostack: [emacs] festival: @@ -1111,7 +1117,17 @@ socat: spacenavd: robostack: [libspnav] spdlog: - robostack: [spdlog, fmt] + # No spdlog conda package exists for emscripten-wasm32 (fmt is published + # there, but spdlog itself isn't, confirmed absent from + # emscripten-forge-4x's repodata, 2026-09-07). Dropping the dep here lets + # spdlog_vendor's own find_package(spdlog QUIET) fail as designed, + # falling back to its ExternalProject source build instead of a + # nonexistent system package. + robostack: + linux: [spdlog, fmt] + osx: [spdlog, fmt] + win64: [spdlog, fmt] + emscripten: [] sqlite3: robostack: [sqlite 3.*] sshpass: @@ -1168,9 +1184,27 @@ xsimd: xtensor: robostack: [xtensor ==0.24.7] yaml: - robostack: [yaml, yaml-cpp] + # emscripten-forge/recipes publishes the C library under its newer name + # "libyaml" (not the legacy conda-forge "yaml"), and doesn't publish + # "yaml-cpp" at all for wasm32 -- C++ consumers get yaml-cpp via the + # from-source yaml_cpp_vendor ROS package (unconditionally selected for + # wasm32) instead of the conda package. + robostack: + linux: [yaml, yaml-cpp] + osx: [yaml, yaml-cpp] + win64: [yaml, yaml-cpp] + emscripten: [libyaml] yaml-cpp: - robostack: [yaml-cpp] + # No yaml-cpp conda package exists for emscripten-wasm32 (confirmed absent + # from emscripten-forge-4x's repodata, 2026-09-07). Dropping the dep here + # lets yaml_cpp_vendor's own find_package(yaml-cpp QUIET) fail as + # designed, falling back to its ExternalProject source build instead of + # a nonexistent system package. + robostack: + linux: [yaml-cpp] + osx: [yaml-cpp] + win64: [yaml-cpp] + emscripten: [] zbar: robostack: linux-64: [zbar] diff --git a/tests/ros-humble-robot-state-publisher.yaml b/tests/ros-humble-robot-state-publisher.yaml index b6f3ae170..073c41bb1 100644 --- a/tests/ros-humble-robot-state-publisher.yaml +++ b/tests/ros-humble-robot-state-publisher.yaml @@ -16,6 +16,17 @@ tests: - set ROS_LOCALHOST_ONLY=1 - set RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - launch_test robot_state_publisher_smoke_test_launch.py + # Cross-compiled emscripten-wasm32 binaries can't be executed on the + # (non-wasm) build host, so the real CI job already passes `--test + # skip` for this platform -- but rattler-build's recipe schema still + # requires `script` to resolve to a non-empty list at PARSE time, + # before any --test flag is consulted. Without an `if: emscripten` + # branch here, none of the above conditions match on that platform and + # the whole `script:` field disappears, which is a hard parse error + # ("missing field `script`"), not just a skipped test. This branch is + # a no-op placeholder that's never actually run. + - if: emscripten + then: "true" requirements: run: - ros-humble-launch-testing-ros diff --git a/vinca.yaml b/vinca.yaml index 6d844ad51..b3f1f28f5 100644 --- a/vinca.yaml +++ b/vinca.yaml @@ -118,6 +118,343 @@ packages_skip_by_deps: - ros_ign - ros_ign_bridge - ros_ign_gazebo + - rosidl_typesupport_fastrtps_c + - rosidl_typesupport_fastrtps_cpp + + # Additional packages transitively pulled in via wasm32-selected packages' + # (rmw_wasm_cpp/wasm_cpp/test_wasm) dependency closures that are not + # actually meant to be built for wasm32 -- e.g. moveit/autoware/ros2_control/ + # rviz2/clearpath families, which need full DDS middleware, GUI toolkits, or + # hardware drivers unavailable under emscripten. These entries only stop + # transitive pull-in (mechanism 2 above); none of them are ever directly + # select_by_deps'd for wasm32 in the first place. Computed by diffing + # generate-recipes-emscripten's selected-package output between this branch + # and main on 2026-09-07, to keep wasm32's build scope from silently growing + # every time an unrelated new package is added elsewhere in this file. + - if: wasm32 + then: + - ackermann_msgs + - ackermann_steering_controller + - actionlib_msgs + - admittance_controller + - ament_clang_format + - ament_clang_tidy + - ament_cmake_auto + - ament_cmake_clang_tidy + - ament_mypy + - angles + - automatika_embodied_agents + - automatika_ros_sugar + - autoware_adapi_v1_msgs + - autoware_adapi_version_msgs + - autoware_auto_msgs + - autoware_cmake + - autoware_common_msgs + - autoware_control_msgs + - autoware_internal_debug_msgs + - autoware_internal_metric_msgs + - autoware_internal_msgs + - autoware_internal_perception_msgs + - autoware_internal_planning_msgs + - autoware_lanelet2_extension + - autoware_lanelet2_extension_python + - autoware_lint_common + - autoware_localization_msgs + - autoware_map_msgs + - autoware_msgs + - autoware_perception_msgs + - autoware_planning_msgs + - autoware_sensing_msgs + - autoware_simulation_msgs + - autoware_system_msgs + - autoware_utils_debug + - autoware_utils_diagnostics + - autoware_utils_geometry + - autoware_utils_logging + - autoware_utils_math + - autoware_utils_pcl + - autoware_utils_rclcpp + - autoware_utils_system + - autoware_utils_tf + - autoware_utils_uuid + - autoware_utils_visualization + - autoware_v2x_msgs + - autoware_vehicle_msgs + - backward_ros + - behaviortree_cpp_v3 + - bicycle_steering_controller + - bond + - bondcpp + - can_msgs + - cascade_lifecycle_msgs + - chomp_motion_planner + - class_loader + - clearpath_common + - clearpath_config + - clearpath_control + - clearpath_description + - clearpath_generator_common + - clearpath_manipulators + - clearpath_manipulators_description + - clearpath_mecanum_drive_controller + - clearpath_mounts_description + - clearpath_platform_description + - clearpath_sensors_description + - common_interfaces + - composition_interfaces + - console_bridge_vendor + - control_msgs + - control_toolbox + - controller_interface + - controller_manager + - controller_manager_msgs + - cv_bridge + - diagnostic_aggregator + - diagnostic_common_diagnostics + - diagnostic_msgs + - diagnostic_updater + - diagnostics + - diff_drive_controller + - effort_controllers + - eigen3_cmake_module + - eigen_stl_containers + - ewellix_description + - fastcdr + - fastrtps_cmake_module + - filters + - force_torque_sensor_broadcaster + - forward_command_controller + - foxglove_msgs + - franka_description + - generate_parameter_library + - generate_parameter_library_py + - geographic_msgs + - geometric_shapes + - geometry_msgs + - gpio_controllers + - gripper_controllers + - gz_ros2_control + - gz_ros2_control_demos + - hardware_interface + - hardware_interface_testing + - ignition_cmake2_vendor + - ignition_math6_vendor + - image_transport + - imu_filter_madgwick + - imu_sensor_broadcaster + - interactive_marker_twist_server + - interactive_markers + - irobot_create_common_bringup + - irobot_create_control + - irobot_create_description + - irobot_create_msgs + - irobot_create_nodes + - irobot_create_toolbox + - joint_state_broadcaster + - joint_state_publisher + - joint_state_publisher_gui + - joint_trajectory_controller + - joy + - joy_linux + - joy_teleop + - jrl_cmakemodules + - kdl_parser + - key_teleop + - kinematics_interface + - kinematics_interface_kdl + - kortex_description + - lanelet2_core + - lanelet2_io + - lanelet2_maps + - lanelet2_matching + - lanelet2_projection + - lanelet2_python + - lanelet2_routing + - lanelet2_traffic_rules + - lanelet2_validation + - laser_geometry + - launch_param_builder + - launch_pytest + - launch_ros + - launch_testing_ros + - libcurl_vendor + - libg2o + - libnabo + - libpointmatcher + - logging_demo + - magic_enum + - map_msgs + - mavros_msgs + - mecanum_drive_controller + - message_filters + - microstrain_inertial_description + - mouse_teleop + - moveit_common + - moveit_configs_utils + - moveit_core + - moveit_kinematics + - moveit_msgs + - moveit_planners + - moveit_planners_chomp + - moveit_planners_ompl + - moveit_resources_fanuc_description + - moveit_resources_fanuc_moveit_config + - moveit_resources_panda_description + - moveit_resources_panda_moveit_config + - moveit_resources_pr2_description + - moveit_resources_prbt_ikfast_manipulator_plugin + - moveit_resources_prbt_moveit_config + - moveit_resources_prbt_pg70_support + - moveit_resources_prbt_support + - moveit_ros_move_group + - moveit_ros_occupancy_map_monitor + - moveit_ros_planning + - moveit_ros_planning_interface + - moveit_ros_robot_interaction + - moveit_ros_visualization + - moveit_ros_warehouse + - moveit_setup_framework + - moveit_setup_srdf_plugins + - moveit_simple_controller_manager + - mrt_cmake_modules + - mujoco_ros2_control_msgs + - mujoco_vendor + - nav_msgs + - nmea_msgs + - nmea_navsat_driver + - object_recognition_msgs + - octomap_msgs + - orocos_kdl_vendor + - ouster_ros + - ouster_sensor_msgs + - parameter_traits + - pcl_conversions + - pcl_msgs + - pcl_ros + - persist_parameter_server + - pick_ik + - picknik_ament_copyright + - picknik_reset_fault_controller + - picknik_twist_controller + - pid_controller + - pilz_industrial_motion_planner + - pilz_industrial_motion_planner_testutils + - plansys2_bringup + - plansys2_bt_actions + - plansys2_core + - plansys2_domain_expert + - plansys2_executor + - plansys2_lifecycle_manager + - plansys2_msgs + - plansys2_pddl_parser + - plansys2_planner + - plansys2_popf_plan_solver + - plansys2_problem_expert + - pluginlib + - point_cloud_transport + - point_cloud_transport_py + - popf + - pose_broadcaster + - position_controllers + - python_orocos_kdl_vendor + - python_qt_binding + - qt_gui + - qt_gui_cpp + - random_numbers + - range_sensor_broadcaster + - rclcpp_cascade_lifecycle + - rclcpp_components + - rclcpp_lifecycle + - realsense2_camera_msgs + - realsense2_description + - realtime_tools + - resource_retriever + - robot_localization + - robot_state_publisher + - robotiq_description + - ros2_control_test_assets + - ros2_controllers + - ros2_fmt_logger + - ros2_socketcan_msgs + - ros2cli + - ros2controlcli + - ros2launch + - ros2node + - ros2param + - ros2pkg + - ros2run + - ros2service + - ros2test + - ros2topic + - ros_testing + - rosidl_runtime_py + - rosidl_typesupport_fastrtps_c + - rosidl_typesupport_fastrtps_cpp + - rqt_gui + - rqt_gui_py + - rqt_runtime_monitor + - rsl + - rtabmap + - rtcm_msgs + - ruckig + - rviz2 + - rviz_2d_overlay_msgs + - rviz_assimp_vendor + - rviz_common + - rviz_default_plugins + - rviz_ogre_vendor + - rviz_rendering + - rviz_rendering_tests + - rviz_visual_testing_framework + - rviz_visual_tools + - sbg_driver + - sdl2_vendor + - self_test + - sensor_msgs + - sensor_msgs_py + - shape_msgs + - sick_scan_xd + - smacc2_msgs + - smclib + - srdfdom + - std_srvs + - steering_controllers_library + - stereo_msgs + - tango_icons_vendor + - tcb_span + - teleop_tools_msgs + - teleop_twist_joy + - tf2 + - tf2_eigen + - tf2_eigen_kdl + - tf2_geometry_msgs + - tf2_kdl + - tf2_msgs + - tf2_py + - tf2_ros + - tf2_ros_py + - tf_transformations + - tinyxml2_vendor + - tinyxml_vendor + - tl_expected + - tracetools_launch + - tracetools_trace + - trajectory_msgs + - tricycle_controller + - tricycle_steering_controller + - twist_mux + - twist_mux_msgs + - ur_client_library + - ur_description + - urdf + - urdf_launch + - urdf_parser_plugin + - velocity_controllers + - velodyne_description + - visualization_msgs + - warehouse_ros + - xacro + - zed_msgs # Dependencies unavailable on Windows. - if: win @@ -162,6 +499,30 @@ packages_skip_by_deps: packages_remove_from_deps: + # cppcheck and uncrustify are native, compiled analysis/formatting tools + # that emscripten-forge does not publish (and never will -- nobody runs a + # static-analysis tool as WASM) for the emscripten-wasm32 target: `cppcheck` + # and `uncrustify` are absent from both the emscripten-wasm32 and noarch + # subdirs of https://repo.prefix.dev/emscripten-forge-4x, confirmed + # 2026-09-07. ament_uncrustify/uncrustify_vendor and ament_cppcheck are only + # ever pulled in transitively (via ament_lint_common's dependency closure, + # not directly select_by_deps'd), so removing them here also automatically + # drops them from ament_lint_common's own run: list (side effect of + # mechanism 3) rather than leaving a dangling reference to a package that's + # never built for this platform. Their ament_cmake_* CMake wrappers are + # excluded for the same reason. The other ament_lint_common members + # (flake8/pep257/cpplint/copyright/lint-cmake) wrap pure-Python tools + # published as plain conda-forge noarch packages, so they need no such + # exclusion. + - if: wasm32 + then: + - ament_cmake_cppcheck + - ament_cmake_uncrustify + - ament_cppcheck + - ament_uncrustify + - uncrustify_vendor + + # Pre-existing: packages unavailable/pointless outside Linux. - if: not linux then: - ament_cmake_xmllint @@ -181,6 +542,8 @@ packages_remove_from_deps: # Dependencies unavailable when targeting WebAssembly. - if: wasm32 then: + - ament_cmake_google_benchmark + - google_benchmark_vendor - rmw_connextdds - rmw_cyclonedds_cpp - rmw_fastrtps_cpp @@ -195,6 +558,15 @@ packages_remove_from_deps: - ros_ign - ros_ign_bridge - ros_ign_gazebo + # google benchmark is a native micro-benchmarking tool that + # emscripten-forge does not publish ("No candidates were found for + # benchmark *" resolving rclcpp's host deps, confirmed 2026-09-07). + # ament_cmake_google_benchmark/google_benchmark_vendor are only ever + # pulled in transitively (via rclcpp's dependency closure, not + # directly select_by_deps'd), so removing them here also drops them + # from rclcpp's own build: list (side effect of mechanism 3). + - rosidl_typesupport_fastrtps_c + - rosidl_typesupport_fastrtps_cpp # Dependencies unavailable on Windows. - if: win @@ -470,11 +842,11 @@ packages_select_by_deps: # Root cause unclear without a Windows build environment to iterate # against; skip on Windows for now rather than guess at a # DEPENDENCIES/CMake fix blind. - - if: not win + - if: not win and not wasm32 then: - automatika_embodied_agents # These packages specifically don't work on windows - - if: not win + - if: not win and not wasm32 then: - zed-msgs @@ -490,7 +862,7 @@ packages_select_by_deps: # for MSVC -- needs a Windows environment to iterate against the # rosidl/ament_cmake macros themselves; skip on Windows for now rather # than guess blind. - - if: not win + - if: not win and not wasm32 then: - robot_localization @@ -699,75 +1071,86 @@ packages_select_by_deps: - orbbec_camera - orbbec_camera_msgs - orbbec_description - - ament_cmake_clang_tidy - ament_cmake_core - - autoware_adapi_v1_msgs - - autoware_adapi_version_msgs - - autoware_auto_msgs - - autoware_internal_debug_msgs - - autoware_internal_metric_msgs - - autoware_internal_perception_msgs - - autoware_internal_planning_msgs - - autoware_lanelet2_extension - - autoware_lanelet2_extension_python - - autoware_lint_common - - autoware_msgs - - autoware_utils_debug - - autoware_utils_diagnostics - - autoware_utils_geometry - - autoware_utils_logging - - autoware_utils_math - - autoware_utils_pcl - - autoware_utils_rclcpp - - autoware_utils_system - - autoware_utils_tf - - autoware_utils_uuid - - autoware_utils_visualization - - bondcpp - - can_msgs - - clearpath_common - - diagnostics - - foxglove_msgs - - gripper_controllers - - gz_ros2_control_demos - - irobot_create_common_bringup - - irobot_create_control - - irobot_create_description - - irobot_create_msgs - - irobot_create_nodes - - irobot_create_toolbox - - joy_teleop - - key_teleop - - magic_enum - - mavros_msgs - - mouse_teleop - - mujoco_ros2_control_msgs - - mujoco_vendor - - nmea_navsat_driver - - persist_parameter_server - - pick_ik - - picknik_ament_copyright - - point_cloud_transport_py - - proxsuite - - ros2_fmt_logger - - ros2_socketcan_msgs - - rqt_runtime_monitor - - rtabmap - - rviz_2d_overlay_msgs - - rviz_visual_tools - - sbg_driver - - sick_scan_xd - - smacc2_msgs - tracetools - - tracetools_launch - - tracetools_trace - - ur_client_library - - urdf_launch + + # These are needed on every OTHER platform but were never meant for + # wasm32 (full DDS/RMW middleware, GUI toolkits, hardware drivers, or + # simply pointless in a browser/WASM context) -- moved out of the + # unconditional list above on 2026-09-07 after diffing this branch's + # emscripten candidate selection against main's. Previously these were + # still being *selected* for wasm32 and patched out downstream via + # packages_skip_by_deps/packages_remove_from_deps instead of not being + # selected in the first place -- fix the selection here, not the symptom. + - if: not wasm32 + then: + - ament_cmake_clang_tidy + - autoware_adapi_v1_msgs + - autoware_adapi_version_msgs + - autoware_auto_msgs + - autoware_internal_debug_msgs + - autoware_internal_metric_msgs + - autoware_internal_perception_msgs + - autoware_internal_planning_msgs + - autoware_lanelet2_extension + - autoware_lanelet2_extension_python + - autoware_lint_common + - autoware_msgs + - autoware_utils_debug + - autoware_utils_diagnostics + - autoware_utils_geometry + - autoware_utils_logging + - autoware_utils_math + - autoware_utils_pcl + - autoware_utils_rclcpp + - autoware_utils_system + - autoware_utils_tf + - autoware_utils_uuid + - autoware_utils_visualization + - bondcpp + - can_msgs + - clearpath_common + - diagnostics + - foxglove_msgs + - gripper_controllers + - gz_ros2_control_demos + - irobot_create_common_bringup + - irobot_create_control + - irobot_create_description + - irobot_create_msgs + - irobot_create_nodes + - irobot_create_toolbox + - joy_teleop + - key_teleop + - magic_enum + - mavros_msgs + - mouse_teleop + - mujoco_ros2_control_msgs + - mujoco_vendor + - nmea_navsat_driver + - persist_parameter_server + - pick_ik + - picknik_ament_copyright + - point_cloud_transport_py + - proxsuite + - ros2_fmt_logger + - ros2_socketcan_msgs + - rqt_runtime_monitor + - rtabmap + - rviz_2d_overlay_msgs + - rviz_visual_tools + - sbg_driver + - sick_scan_xd + - smacc2_msgs + - tracetools_launch + - tracetools_trace + - ur_client_library + - urdf_launch # These packages specifically don't work on Windows. Keep this comment # directly before the "if:" line below -- vinca-sort-vinca-lists merges # adjacent if/then blocks that aren't separated by a top-level comment. - - if: not win + - if: not win and not wasm32 then: - ouster_ros - pinocchio diff --git a/vinca_pinning.yaml b/vinca_pinning.yaml index 9eaa974be..8f3f0aab9 100644 --- a/vinca_pinning.yaml +++ b/vinca_pinning.yaml @@ -18,35 +18,152 @@ pinning_overrides: # c_stdlib_version shares a zip_keys group with the compiler versions, so the whole # group has to be overridden; the compiler entries mirror the conda-forge base file # and must be refreshed when `vinca-pinning-update` moves to a newer compiler. + # + # emscripten-wasm32 support: vinca's own selector namespace (v1_selectors.py) + # treats "unix" as linux OR osx OR emscripten OR wasi OR freebsd OR zos, so the + # base conda-forge-pinning zip_keys group below -- [c_stdlib_version, + # c_compiler_version, cxx_compiler_version, fortran_compiler_version, + # cuda_compiler_version], all scoped [unix] -- silently applies to emscripten too, + # even though conda-forge itself never builds for that platform. Since emscripten + # needs its own compiler (emscripten/emscripten, version pinned by + # emscripten-forge/recipes' variant.yaml, currently 4.0.9) but has no Fortran + # toolchain, no libc sysroot, and no CUDA, simply adding an emscripten branch to + # c_compiler_version/cxx_compiler_version would leave them non-empty while + # c_stdlib_version/fortran_compiler_version/cuda_compiler_version stay empty for + # that platform -- a zip-length mismatch (confirmed as the actual CI failure: + # "Zip key elements do not all have same length: c_compiler_version"). Fixed by + # (1) restricting fortran_compiler_version's selector from [unix] to + # [linux or osx] so it no longer nominally covers emscripten, and (2) splitting + # zip_keys (below) into a [c_compiler_version, cxx_compiler_version] group (valid + # for linux/osx/emscripten alike, always 1 entry each) separate from the + # [c_stdlib_version, fortran_compiler_version, cuda_compiler_version] group + # (linux/osx only, always 0 entries -- i.e. absent -- on emscripten, so 0==0==0 + # there instead of clashing with the compiler-version group's 1==1). c_stdlib_version: - 2.28 # [linux and not riscv64] - 2.39 # [linux and riscv64] - 2.28 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] - 14.0 # [osx] + c_compiler: + - gcc # [linux] + - clang # [osx] + - vs2022 # [win] + - emscripten # [emscripten] + cxx_compiler: + - gxx # [linux] + - clangxx # [osx] + - vs2022 # [win] + - emscripten # [emscripten] c_compiler_version: - 15 # [linux] - 21 # [osx] - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + # Matches emscripten-forge/recipes' variant.yaml c_compiler_version; refresh + # alongside cxx_compiler_version and emscripten_emscripten-wasm32 (below) when + # emscripten-forge bumps their emscripten SDK version. + - 4.0.9 # [emscripten] cxx_compiler_version: - 15 # [linux] - 21 # [osx] - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + - 4.0.9 # [emscripten] fortran_compiler_version: - - 15 # [unix] + - 15 # [linux or osx] - 5 # [win64] - 22 # [win and arm64] - 14 # [linux and (x86_64 or aarch64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] cuda_compiler_version: - None - 12.9 # [((linux and (x86_64 or aarch64)) or win64) and os.environ.get("CF_CUDA_ENABLED", "False") == "True"] + zip_keys: + - + - c_compiler_version + - cxx_compiler_version + - + - c_stdlib_version # [linux or osx] + - fortran_compiler_version # [linux or osx] + - cuda_compiler_version # [linux or osx] + - + - python + - is_python_min + - + - libarrow + - libarrow_all + - + - root_base + - root_cxx_standard + - + - clhep + - geant4 + # Pre-existing conda-forge-pinning inconsistency, unrelated to emscripten: + # clhep (3 entries) and geant4 (1 entry) are zipped together but not the same + # length -- rattler-build's `--render-only` (used to sanity-check variant + # resolution while adding emscripten support) validates every zip_keys group + # up front regardless of whether a given recipe touches it, so this crashes + # `--render-only` on ANY target platform even though neither HEP package is + # ever built by RoboStack and a plain bulk `rattler-build build + # --recipe-dir` never happens to trigger it. Trimmed clhep down to geant4's + # single already-selected version so the group is internally consistent. + clhep: + - 2.4.7.2 + geant4: + - 11.4.2 libzenohc: - 1.10.0 libzenohcxx: - 1.10.0 + # emscripten-forge/recipes only publishes graphviz 15.1.0 -- our regular + # graphviz 14 pin (from conda-forge-pinning) has no candidates there, + # breaking eigenpy/pinocchio's host: dep on it. + graphviz: + - '14' # [not emscripten] + - '15.1.0' # [emscripten] + # emscripten-forge/recipes only publishes libffi 3.4.6 -- our regular + # 3.5.* pin (from conda-forge-pinning) has no candidates there, breaking + # rmw_wasm_cpp's host: dep on it (added via patch/dependencies.yaml + # add_host since its package.xml never declares one). + libffi: + - '3.5' # [not emscripten] + - '3.4.6' # [emscripten] + # emscripten-forge/recipes only publishes python 3.13 builds today (their own + # variant.yaml pins python: 3.13.* *_cp313), well behind conda-forge's 3.14 -- + # keep 3.14 for every other platform and use their exact pin for emscripten. + # Written as a v1 if/then/else (rather than the legacy `# [selector]` + # comment form used elsewhere in this file) because a cross-compiling + # recipe's BUILD-time deps need to resolve against the *build platform*'s + # own variant (e.g. a native osx-arm64 rosidl_default_generators helper, + # itself built at python 3.14), not the invocation's --target-platform; + # the legacy comment selector only ever sees target_platform, so it pinned + # python 3.13 globally for the whole invocation regardless of which + # sub-resolution was actually being solved. python: - - 3.14.* *_cp314 + - if: emscripten + then: + - 3.13.* *_cp313 + - if: not emscripten + then: + - 3.14.* *_cp314 is_python_min: - - false + - if: emscripten + then: + - false + - if: not emscripten + then: + - false python_impl: - cpython + # Needed for cross-compiled targets: emitted whenever a recipe's + # `packages_select_by_deps`/host deps expand `cross-python_${{ target_platform }}` + # (see e.g. ros-humble-rosidl-default-generators's host deps) with + # target_platform == emscripten-wasm32. Not a conda-forge-pinning key at all + # (conda-forge doesn't cross-compile to emscripten), so it must be added here + # rather than merely overridden. Matches emscripten-forge/recipes' variant.yaml. + cross-python_emscripten-wasm32: + - '3.13.1' + # The emscripten SDK/toolchain package version itself, keyed by vinca's + # `_` convention for cross-compilation toolchain + # packages (mirrors emscripten_emscripten-wasm32 in emscripten-forge/recipes' + # variant.yaml). Keep in sync with c_compiler_version/cxx_compiler_version above. + emscripten_emscripten-wasm32: + - 4.0.9