Skip to content
Open
60 changes: 59 additions & 1 deletion vinca/pinning.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -219,11 +219,69 @@ def _migration_name(name: str) -> str:
return name


def _existing_eol_comment_text(source: Any, index: int) -> Optional[str]:
"""Return the plain text of a CommentedSeq item's trailing EOL comment, if any."""
ca = getattr(source, "ca", None)
if ca is None:
return None
entry = ca.items.get(index)
if not entry:
return None
token = entry[0]
if token is None:
return None
return str(token.value).lstrip("#").strip()


def _flatten_v1_selectors(value: Any) -> Any:
"""Convert v1-style `- if: COND then: [...]` list entries into the legacy
`- VALUE # [COND]` comment-annotated form that rattler-build's variant
config loader actually evaluates lazily per target_platform (unlike the
v1 if/then/else mapping form, which it treats as an opaque literal value
rather than a selector -- confirmed via `Could not parse version spec
for variant key ...: invalid channel` / `multiple bracket sections not
allowed` errors when left unconverted).

Passthrough items (plain scalars, possibly already carrying their own
`# [selector]` EOL comment) must have that existing comment re-attached
at their new index -- ruamel stores comments keyed by list position on
the *source* CommentedSeq, not on the item itself, so a naive
`result.append(item)` into a freshly created CommentedSeq silently
drops it, turning a platform-scoped entry into an unconditional one.
"""
if not isinstance(value, list):
return value
import ruamel.yaml.comments as _rc

result = _rc.CommentedSeq()
for old_index, item in enumerate(value):
if isinstance(item, Mapping) and "if" in item and "then" in item:
cond = str(item["if"])
for entry in item["then"]:
idx = len(result)
result.append(entry)
result.yaml_add_eol_comment(f"[{cond}]", idx)
else_branch = item.get("else")
if else_branch is not None:
not_cond = f"not ({cond})"
for entry in else_branch:
idx = len(result)
result.append(entry)
result.yaml_add_eol_comment(f"[{not_cond}]", idx)
else:
idx = len(result)
result.append(item)
comment_text = _existing_eol_comment_text(value, old_index)
if comment_text:
result.yaml_add_eol_comment(comment_text, idx)
return result


def _overlay(target: Any, source: Any) -> None:
for key, value in source.items():
if key == "migrator_ts" or str(key).startswith("__"):
continue
target[key] = value
target[key] = _flatten_v1_selectors(value)


def _migration_timestamp(payload: bytes) -> float:
Expand Down
56 changes: 44 additions & 12 deletions vinca/templates/build_ament_cmake.sh.in
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,20 +69,52 @@ if [[ $target_platform =~ emscripten.* ]]; then
echo "set(CMAKE_STRIP FALSE) # used by default in pybind11 on .so modules">> $SRC_DIR/__vinca_shared_lib_patch.cmake
echo "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) # fixes an error where numpy header files are not found correctly">> $SRC_DIR/__vinca_shared_lib_patch.cmake

# if [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-publisher" ] || [ "${PKG_NAME}" == "ros-humble-examples-rclcpp-minimal-subscriber" ] || [ "${PKG_NAME}" == "ros-humble-rclcpp-components" ]; then
# echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -s DEMANGLE_SUPPORT=1 -sALLOW_MEMORY_GROWTH=1 -sASYNCIFY -O3 -s ASYNCIFY_STACK_SIZE=24576 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# else
echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=0 -s ALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=0 -sALLOW_MEMORY_GROWTH=1 -s DEMANGLE_SUPPORT=1 -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# fi
# Real pthreads (USE_PTHREADS=1) are required so that blocking waits
# (std::condition_variable / rmw wait sets) actually work: without a real
# OS-level thread, libc++'s condition_variable timed-wait never wakes up
# on its own, and code that spins/blocks the main thread (e.g. rclcpp's
# executor) hangs forever with no way for the browser's JS event loop
# (and thus wall-clock time) to ever advance underneath it. This must be
# consistent across every emscripten-wasm32 package: mixing a
# pthread-enabled module with a non-pthread one is a hard ABI-level
# mismatch ("memory import shared state mismatch") since a wasm module's
# shared-vs-non-shared linear memory is fixed at compile+link time.
#
# This flag has to be set at COMPILE time too (not just link time) for
# every translation unit -- it bakes in the wasm 'atomics'/'bulk-memory'
# features that the linker later requires when producing shared memory
# ("wasm-ld: error: --shared-memory is disallowed ... because it was not
# compiled with 'atomics' or 'bulk-memory' features"). add_compile_options
# here (via CMAKE_PROJECT_INCLUDE, included right after every project()
# call) applies it to every target compiled in every package.
echo "add_compile_options(\"SHELL: -s USE_PTHREADS=1\")">> $SRC_DIR/__vinca_shared_lib_patch.cmake

echo "set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
echo "set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# CMake's MODULE library type (add_library(... MODULE), what
# pybind11_add_module() uses for Python C extensions e.g. rclpy's
# _rclpy_pybind11) is a distinct target type from SHARED and reads its
# own CMAKE_SHARED_MODULE_CREATE_*_FLAGS variables -- setting only the
# SHARED ones above left every MODULE-type .so linked without
# USE_PTHREADS=1, producing a non-shared-memory module that fails to
# load ("mismatch in shared state of memory") next to the rest of a
# pthreads build, even though its own object files were compiled with
# atomics support and looked fine individually.
echo "set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
echo "set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS \"-s ASSERTIONS=1 -s SIDE_MODULE=1 -sWASM_BIGINT -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 \")">> $SRC_DIR/__vinca_shared_lib_patch.cmake
# PTHREAD_POOL_SIZE and MAXIMUM_MEMORY are only meaningful on the final
# MAIN_MODULE executable link (they configure the Worker pool and the
# shared SharedArrayBuffer's reserved size respectively -- side modules
# don't have memory of their own, they use the main module's). Emscripten
# requires MAXIMUM_MEMORY to be set explicitly whenever
# ALLOW_MEMORY_GROWTH is combined with USE_PTHREADS, since a shared
# wasm memory's maximum size can't be left unbounded.
echo "set(CMAKE_EXE_LINKER_FLAGS \"-sMAIN_MODULE=1 -sASSERTIONS=1 -fexceptions -lembind -sWASM_BIGINT -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 -sALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1024MB -L$SRC_DIR/build -L$PREFIX/lib\") # remove SIDE_MODULE from exe linker flags">> $SRC_DIR/__vinca_shared_lib_patch.cmake

export BUILD_TYPE="Debug"
export EXTRA_CMAKE_ARGS=" \
-DPYTHON_SOABI="cpython-${ROS_PYTHON_VERSION//./}-wasm32-emscripten" \
-DRMW_IMPLEMENTATION=rmw_wasm_cpp \
-DRMW_IMPLEMENTATION=${VINCA_EMSCRIPTEN_RMW_IMPLEMENTATION:-rmw_wasm_cpp} \
-DCMAKE_FIND_ROOT_PATH=$PREFIX \
-DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \
-DCMAKE_PROJECT_INCLUDE=$SRC_DIR/__vinca_shared_lib_patch.cmake \
Expand All@@ -92,8 +124,8 @@ if [[ $target_platform =~ emscripten.* ]]; then
export CMAKE_GEN="emcmake cmake"
export CMAKE_BLD="cmake"

export STATIC_ROSIDL_TYPESUPPORT_C=rosidl_typesupport_introspection_c
export STATIC_ROSIDL_TYPESUPPORT_CPP=rosidl_typesupport_introspection_cpp
export STATIC_ROSIDL_TYPESUPPORT_C=${VINCA_EMSCRIPTEN_STATIC_TYPESUPPORT_C:-rosidl_typesupport_introspection_c}
export STATIC_ROSIDL_TYPESUPPORT_CPP=${VINCA_EMSCRIPTEN_STATIC_TYPESUPPORT_CPP:-rosidl_typesupport_introspection_cpp}
else
export BUILD_TYPE="Release"
export CMAKE_GEN="cmake"
Expand Down