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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ compile_commands.json
/smoke-test-*
/*.tgz
/build-artifacts
.parcel-cache/
dist/
*.wasm
225 changes: 222 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,200 @@ if(${ZMQ_ENABLE_SANITIZER_UNDEFINED})
set(ENABLE_SANITIZER_UNDEFINED "ENABLE_SANITIZER_UNDEFINED")
endif()

option(ZMQ_WASM "Build for WebAssembly" OFF)
set_option_from_env(ZMQ_WASM)

if(ZMQ_WASM OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
set(ZMQ_WASM TRUE)

# Compile for WebAssembly. Keep vcpkg's host triplet native so build tools
# run on the host while target libraries are built for Emscripten.
set(VCPKG_TARGET_TRIPLET wasm32-emscripten)

if ("$ENV{EMSCRIPTEN_ROOT}" STREQUAL "")
find_program(EMCC emcc REQUIRED)
get_filename_component(EMSCRIPTEN_ROOT "${EMCC}" DIRECTORY)
else()
set(EMSCRIPTEN_ROOT "$ENV{EMSCRIPTEN_ROOT}")
endif()

set(EMSCRIPTEN_PLATFORM_FILE "${EMSCRIPTEN_ROOT}/cmake/Modules/Platform/Emscripten.cmake")
if(NOT EXISTS "${EMSCRIPTEN_PLATFORM_FILE}")
message(FATAL_ERROR
"Could not find the Emscripten CMake platform module at ${EMSCRIPTEN_PLATFORM_FILE}")
endif()

# vcpkg's autotools ports honor CC/CXX from the environment before their
# generated target-triplet settings. Ensure target libraries, especially
# libsodium, are compiled by Emscripten rather than an inherited host clang.
set(ENV{CC} "${EMSCRIPTEN_ROOT}/emcc")
set(ENV{CXX} "${EMSCRIPTEN_ROOT}/em++")
set(ENV{AR} "${EMSCRIPTEN_ROOT}/emar")
set(ENV{RANLIB} "${EMSCRIPTEN_ROOT}/emranlib")
set(ENV{NM} "${EMSCRIPTEN_ROOT}/emnm")
set(ENV{STRIP} "${EMSCRIPTEN_ROOT}/emstrip")
set(ENV{VCPKG_BINARY_SOURCES} "clear")

# vcpkg includes this chainload exactly once when its toolchain is loaded.
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${EMSCRIPTEN_PLATFORM_FILE}"
CACHE FILEPATH "Emscripten toolchain chainloaded by vcpkg" FORCE)

# Use the repository's Emscripten-specific ZeroMQ port overlay. The overlay
# patches libzmq's signaler to use a pipe because Emscripten does not provide
# an anonymous socketpair in its Node socket backend.
set(VCPKG_OVERLAY_PORTS "${CMAKE_SOURCE_DIR}/cmake/vcpkg-overlays"
CACHE STRING "vcpkg overlay ports for the WebAssembly target" FORCE)

add_compile_definitions("-D__wasm__" "-D__EMSCRIPTEN__")

# export an indirect function table for napi-wasm
add_link_options("--export-table")

# Add WebAssembly-specific exports for napi-wasm
add_link_options("SHELL:-s EXPORTED_FUNCTIONS=['_malloc','_free','_napi_wasm_malloc','_napi_register_wasm_v1']")
add_link_options("SHELL:-s EXPORTED_RUNTIME_METHODS=['ccall','cwrap']")
add_link_options("SHELL:-s ALLOW_MEMORY_GROWTH=1")
add_link_options("SHELL:-s ENVIRONMENT='node'")
# Keep the signaler's pipe inside the module. The standalone Emscripten
# output otherwise leaves pipe2 as a host import, while Node's WASI adapter
# only provides the standard WASI descriptors.
add_link_options("SHELL:-s WASMFS=1")
add_link_options("SHELL:-Wl,--undefined=__syscall_pipe2")
# Emscripten's --allow-undefined-file does not allow Node-API imports in
# this em++ invocation. Permit unresolved symbols at link time, then expose
# the exact module:symbol contract for the post-link audit.
add_link_options("SHELL:-s ERROR_ON_UNDEFINED_SYMBOLS=0")
add_link_options("SHELL:-s WARN_ON_UNDEFINED_SYMBOLS=1")
set(ZMQ_WASM_IMPORT_CONTRACT
"napi:napi_add_finalizer"
"napi:napi_adjust_external_memory"
"napi:napi_async_destroy"
"napi:napi_async_init"
"napi:napi_call_function"
"napi:napi_close_callback_scope"
"napi:napi_close_escapable_handle_scope"
"napi:napi_close_handle_scope"
"napi:napi_coerce_to_object"
"napi:napi_coerce_to_string"
"napi:napi_create_array_with_length"
"napi:napi_create_async_work"
"napi:napi_create_buffer"
"napi:napi_create_buffer_copy"
"napi:napi_create_double"
"napi:napi_create_error"
"napi:napi_create_external_buffer"
"napi:napi_create_function"
"napi:napi_create_object"
"napi:napi_create_promise"
"napi:napi_create_reference"
"napi:napi_create_string_latin1"
"napi:napi_create_string_utf8"
"napi:napi_create_type_error"
"napi:napi_define_class"
"napi:napi_define_properties"
"napi:napi_delete_async_work"
"napi:napi_delete_property"
"napi:napi_delete_reference"
"napi:napi_escape_handle"
"napi:napi_fatal_error"
"napi:napi_get_and_clear_last_exception"
"napi:napi_get_array_length"
"napi:napi_get_arraybuffer_info"
"napi:napi_get_boolean"
"napi:napi_get_cb_info"
"napi:napi_get_element"
"napi:napi_get_global"
"napi:napi_get_last_error_info"
"napi:napi_get_named_property"
"napi:napi_get_new_target"
"napi:napi_get_null"
"napi:napi_get_property"
"napi:napi_get_reference_value"
"napi:napi_get_typedarray_info"
"napi:napi_get_undefined"
"napi:napi_get_value_bool"
"napi:napi_get_value_double"
"napi:napi_get_value_int32"
"napi:napi_get_value_int64"
"napi:napi_get_value_string_utf8"
"napi:napi_get_value_uint32"
"napi:napi_has_named_property"
"napi:napi_has_property"
"napi:napi_is_array"
"napi:napi_is_arraybuffer"
"napi:napi_is_buffer"
"napi:napi_is_exception_pending"
"napi:napi_new_instance"
"napi:napi_open_callback_scope"
"napi:napi_open_escapable_handle_scope"
"napi:napi_open_handle_scope"
"napi:napi_queue_async_work"
"napi:napi_reject_deferred"
"napi:napi_remove_wrap"
"napi:napi_resolve_deferred"
"napi:napi_set_element"
"napi:napi_set_instance_data"
"napi:napi_set_named_property"
"napi:napi_throw"
"napi:napi_typeof"
"napi:napi_unwrap"
"napi:napi_wrap"
"env:__syscall_accept4"
"env:__syscall_bind"
"env:__syscall_connect"
"env:__syscall_getpeername"
"env:__syscall_getsockname"
"env:__syscall_getsockopt"
"env:__syscall_listen"
"env:__syscall_recvfrom"
"env:__syscall_rmdir"
"env:__syscall_sendto"
"env:__syscall_setsockopt"
"env:__syscall_socket"
"env:__syscall_unlinkat"
"env:emscripten_asm_const_int"
"env:emscripten_notify_memory_growth"
"env:napi_wasm_cancel"
"env:napi_wasm_schedule"
"env:getaddrinfo"
"env:getnameinfo"
"env:pthread_setschedparam"
"wasi_snapshot_preview1:clock_time_get"
"wasi_snapshot_preview1:environ_get"
"wasi_snapshot_preview1:environ_sizes_get"
"wasi_snapshot_preview1:fd_close"
"wasi_snapshot_preview1:fd_read"
"wasi_snapshot_preview1:fd_seek"
"wasi_snapshot_preview1:fd_write"
"wasi_snapshot_preview1:proc_exit"
"wasi_snapshot_preview1:random_get"
)
set(ZMQ_WASM_IMPORT_ALLOWLIST
"${CMAKE_CURRENT_BINARY_DIR}/zeromq-wasm-intentional-imports.txt")
set(ZMQ_WASM_IMPORT_CONTRACT_FILE
"${CMAKE_CURRENT_BINARY_DIR}/zeromq-wasm-import-contract.txt")
file(WRITE "${ZMQ_WASM_IMPORT_ALLOWLIST}" "")
file(WRITE "${ZMQ_WASM_IMPORT_CONTRACT_FILE}" "")
foreach(import_contract IN LISTS ZMQ_WASM_IMPORT_CONTRACT)
string(REPLACE ":" ";" import_parts "${import_contract}")
list(GET import_parts 1 import_name)
file(APPEND "${ZMQ_WASM_IMPORT_ALLOWLIST}" "${import_name}\n")
file(APPEND "${ZMQ_WASM_IMPORT_CONTRACT_FILE}" "${import_contract}\n")
endforeach()
add_link_options("SHELL:--allow-undefined-file=${ZMQ_WASM_IMPORT_ALLOWLIST}")
add_link_options("SHELL:-s DISABLE_EXCEPTION_CATCHING=1")
add_link_options("--no-entry")

# The build script stages one current addon, while previous failed builds
# can leave copies in versioned cmake-ts output directories. Remove only
# those generated addon artifacts so the wildcard copy is deterministic.
file(GLOB ZMQ_WASM_STALE_ARTIFACTS
"${CMAKE_SOURCE_DIR}/build/wasm32/unknown/node/*/addon.wasm")
if(ZMQ_WASM_STALE_ARTIFACTS)
file(REMOVE ${ZMQ_WASM_STALE_ARTIFACTS})
endif()
endif()

# Set MacOS deployment target
if(APPLE)
option(MACOSX_DEPLOYMENT_TARGET "MacOS deployment target" "10.15")
Expand Down Expand Up @@ -130,10 +324,17 @@ project_options(
ENABLE_CACHE
ENABLE_COMPILE_COMMANDS_SYMLINK
${ENABLE_SANITIZER_UNDEFINED}
ENABLE_INTERPROCEDURAL_OPTIMIZATION
)

file(GLOB_RECURSE SOURCES "./src/*.cc")
add_library(addon SHARED ${SOURCES})
file(GLOB_RECURSE SOURCES "./src/*.cc" "./src/*.cpp")

if(ZMQ_WASM)
# For WebAssembly, create an executable instead of a shared library
add_executable(addon ${SOURCES})
else()
add_library(addon SHARED ${SOURCES})
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL GNU
OR CMAKE_CXX_COMPILER_ID STREQUAL Clang
Expand All @@ -151,6 +352,10 @@ if(ZMQ_NO_SYNC_RESOLVE)
target_compile_definitions(addon PRIVATE ZMQ_NO_SYNC_RESOLVE)
endif()

if(ZMQ_WASM)
target_compile_definitions(addon PRIVATE ZMQ_WASM)
endif()

# ZeroMQ
if(ZMQ_SHARED)
find_package(PkgConfig REQUIRED)
Expand Down Expand Up @@ -180,7 +385,21 @@ if(WIN32)
endif()

# Use `.node` for the library without any "lib" prefix
set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node")
if(ZMQ_WASM)
set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".wasm")

find_program(NODE_EXECUTABLE node REQUIRED)
add_custom_command(
TARGET addon
POST_BUILD
COMMAND "${NODE_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/script/audit-wasm-imports.mjs"
"$<TARGET_FILE:addon>" "${ZMQ_WASM_IMPORT_CONTRACT_FILE}"
COMMENT "Auditing WASM imports"
VERBATIM
)
else()
set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node")
endif()

# Windows delay load node.exe
if(WIN32)
Expand Down
16 changes: 16 additions & 0 deletions cmake/vcpkg-overlays/zeromq/fix-arm.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
diff --git a/src/clock.cpp b/src/clock.cpp
index 79522ad..0667c59 100644
--- a/src/clock.cpp
+++ b/src/clock.cpp
@@ -41,8 +41,10 @@
#include <cmnintrin.h>
#else
#include <intrin.h>
-#if defined(_M_ARM) || defined(_M_ARM64)
+#if defined(_M_ARM)
#include <arm_neon.h>
+#elif defined(_M_ARM64)
+#include <arm64_neon.h>
#endif
#endif
#endif
40 changes: 40 additions & 0 deletions cmake/vcpkg-overlays/zeromq/pkgconfig.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0346227..56d34e6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -569,6 +569,23 @@ else()
check_cxx_symbol_exists(SO_BUSY_POLL sys/socket.h ZMQ_HAVE_BUSY_POLL)
endif()

+if(WITH_LIBSODIUM)
+ string(APPEND pkg_config_names_private " libsodium")
+endif()
+if(WIN32)
+ string(APPEND pkg_config_libs_private " -ladvapi32 -liphlpapi -lrpcrt4 -lws2_32")
+endif()
+string(APPEND pkg_config_libs_private " ${CMAKE_THREAD_LIBS_INIT}")
+foreach(lib IN LISTS CMAKE_CXX_IMPLICIT_LINK_LIBRARIES)
+ if(lib IN_LIST CMAKE_C_IMPLICIT_LINK_LIBRARIES)
+ continue()
+ elseif(EXISTS "${lib}")
+ string(APPEND pkg_config_libs_private " ${lib}")
+ else()
+ string(APPEND pkg_config_libs_private " -l${lib}")
+ endif()
+endforeach()
+
if(NOT MINGW)
find_library(RT_LIBRARY rt)
if(RT_LIBRARY)
diff --git a/src/libzmq.pc.in b/src/libzmq.pc.in
index 233bc3a..3c2bf0d 100644
--- a/src/libzmq.pc.in
+++ b/src/libzmq.pc.in
@@ -7,6 +7,6 @@ Name: libzmq
Description: 0MQ c++ library
Version: @VERSION@
Libs: -L${libdir} -lzmq
-Libs.private: -lstdc++ @pkg_config_libs_private@
+Libs.private: @pkg_config_libs_private@
Requires.private: @pkg_config_names_private@
Cflags: -I${includedir} @pkg_config_defines@
Loading
Loading