@@ -32,6 +32,14 @@ FetchContent_Declare(CMakeExtensions
3232FetchContent_Declare (googletest
3333 URL "https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz"
3434 EXCLUDE_FROM_ALL )
35+ FetchContent_Declare (hermes
36+ GIT_REPOSITORY https://github.com/facebook/hermes.git
37+ # Pinned to the tip of the `static_h` branch as of 2026-06-03 so CI is
38+ # reproducible. Bump this SHA when you intentionally want to pick up
39+ # upstream Hermes changes — keep it on the static_h branch (Static
40+ # Hermes is the variant our NAPI integration targets).
41+ GIT_TAG 348582831f50954895da8e80cc91112d51036c69
42+ EXCLUDE_FROM_ALL )
3543FetchContent_Declare (ios -cmake
3644 GIT_REPOSITORY https://github.com/leetal/ios -cmake.git
3745 GIT_TAG 4.4.1
@@ -157,6 +165,148 @@ if(BABYLON_DEBUG_TRACE)
157165 add_definitions (-DBABYLON_DEBUG_TRACE )
158166endif ()
159167
168+ if (NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes" )
169+ # Hermes is currently an experimental engine for JsRuntimeHost and is not
170+ # supported on Apple platforms (iOS or macOS). Bail out early with a
171+ # clear error rather than letting the build fail deep inside Hermes's
172+ # Apple framework / toolchain machinery.
173+ if (APPLE OR IOS )
174+ message (FATAL_ERROR
175+ "NAPI_JAVASCRIPT_ENGINE=Hermes is not supported on Apple platforms "
176+ "(iOS or macOS). Hermes support in JsRuntimeHost is experimental "
177+ "and currently limited to Windows, Android, and Linux." )
178+ endif ()
179+
180+ # Configure Hermes static_h options BEFORE making it available so that
181+ # cache variables take effect. We disable the parts of Hermes we don't
182+ # need (the unit-test suite, debugger) to keep build time reasonable and
183+ # to avoid pulling extra targets into our build tree. Notably we leave
184+ # HERMES_ENABLE_NAPI ON (the default) — that's the whole point of
185+ # integrating Hermes here.
186+ #
187+ # HERMES_ENABLE_TOOLS must stay ON: even when HERMES_ENABLE_TEST_SUITE is
188+ # OFF, Hermes unconditionally adds external/node-api-cts and
189+ # external/node-api-tests whenever HERMES_ENABLE_NAPI is on, and those
190+ # subdirectories reference the `hermes` CLI tool target via
191+ # $<TARGET_FILE:hermes>. CMake fails to generate if the target doesn't
192+ # exist, so we let Hermes build its tools. None of them ship in our
193+ # final binaries because they're EXCLUDE_FROM_ALL via FetchContent.
194+ set (HERMES_ENABLE_TOOLS ON CACHE BOOL "" FORCE )
195+ set (HERMES_ENABLE_TEST_SUITE OFF CACHE BOOL "" FORCE )
196+ set (HERMES_ENABLE_DEBUGGER OFF CACHE BOOL "" FORCE )
197+ set (HERMES_BUILD_APPLE_FRAMEWORK OFF CACHE BOOL "" FORCE )
198+ set (HERMES_ENABLE_NAPI ON CACHE BOOL "" FORCE )
199+ if (ANDROID )
200+ # JsRuntimeHost's Android test app embeds Hermes directly, without
201+ # Hermes's React Native Android/fbjni packaging. Unicode-lite avoids
202+ # pulling ICU/fbjni into this standalone N-API build.
203+ set (HERMES_UNICODE_LITE ON CACHE BOOL "" FORCE )
204+ endif ()
205+ # Hermes defaults HERMES_SLOW_DEBUG=ON, which compiles in internal
206+ # sanity checks that fire even in optimized (RelWithDebInfo / Release)
207+ # builds — e.g. heap-state assertions in HadesGC and the finalizer
208+ # chain. These can SIGABRT during Runtime teardown on some platforms
209+ # (observed on macOS arm64 RelWithDebInfo: process exited with code
210+ # 134 right after `145 passing`, before any gtest [OK] line, with no
211+ # diagnostic in stderr). Embedders don't need them.
212+ set (HERMES_SLOW_DEBUG OFF CACHE BOOL "" FORCE )
213+ # Hermes vendors Boost.Context (external/boost/boost_1_86_0/libs/context).
214+ # The default fcontext implementation needs the platform-native assembler
215+ # for context switching. On Windows + MSBuild, MASM picks up CXX compile
216+ # flags like /Zc:__cplusplus from the surrounding scope, emits A4018
217+ # warnings for every prefix substring, eventually bails out before writing
218+ # boost_context_obj.dir\Release\make_x86_64_ms_pe_masm.obj, and the link
219+ # step fails with LNK1181. Switch to the Win32 Fiber-based implementation
220+ # (winfib) — no assembler needed, no MASM problem. Hermes only uses the
221+ # fiber API (per a comment in the bundled boost CMakeLists), so winfib is
222+ # functionally equivalent for this embedder.
223+ if (WIN32 )
224+ set (BOOST_CONTEXT_IMPLEMENTATION "winfib" CACHE STRING "" FORCE )
225+ endif ()
226+ # ------------------------------------------------------------------
227+ # Cross-compilation: bootstrap the Hermes host compilers automatically.
228+ #
229+ # `hermesc`/`shermes` are HOST tools that Hermes runs at build time (to
230+ # emit InternalBytecode and, in -emit-c mode, the native internal unit).
231+ # When cross-compiling (e.g. the Android NDK build) the in-tree Hermes
232+ # tool targets are built for the *target* and can't run on the build host,
233+ # so Hermes must be handed prebuilt HOST compilers via the standard
234+ # IMPORT_HOST_COMPILERS mechanism (it `include()`s a CMake file that
235+ # Hermes `export()`s when those tools are built).
236+ #
237+ # Rather than require a separate, manual "build host compilers" CI step
238+ # plus a -DIMPORT_HOST_COMPILERS=... hand-off, do it here: configure and
239+ # build a native (host-toolchain) copy of hermesc/shermes from THIS source
240+ # tree, then point Hermes at the ImportHostCompilers.cmake it produces.
241+ #
242+ # This runs at configure time (via execute_process) on purpose: Hermes
243+ # consumes IMPORT_HOST_COMPILERS during its own configure, so the exported
244+ # file must already exist before FetchContent_MakeAvailable(hermes) below.
245+ # An explicit -DIMPORT_HOST_COMPILERS=... still wins and skips the
246+ # bootstrap, preserving the manual/prebuilt path.
247+ if (CMAKE_CROSSCOMPILING AND NOT IMPORT_HOST_COMPILERS)
248+ # Shared, source-relative location so the host tools are built once and
249+ # reused across target ABIs (/Build is git-ignored).
250+ set (_jsrh_hermes_host_dir "${CMAKE_CURRENT_SOURCE_DIR } /Build/HermesHostTools" )
251+ set (_jsrh_hermes_host_import "${_jsrh_hermes_host_dir} /ImportHostCompilers.cmake" )
252+
253+ if (NOT EXISTS "${_jsrh_hermes_host_import} " )
254+ message (STATUS "Hermes: bootstrapping host compilers (hermesc/shermes) for cross-compilation" )
255+
256+ # Prefer Ninja when available; otherwise fall back to the host's
257+ # default generator (Unix Makefiles).
258+ find_program (_jsrh_host_ninja ninja )
259+ if (_jsrh_host_ninja)
260+ set (_jsrh_host_generator -G Ninja)
261+ else ()
262+ set (_jsrh_host_generator "" )
263+ endif ()
264+
265+ # Configure a native build from the same source tree. No toolchain
266+ # file is passed, so CMake selects the host compiler. Tests are
267+ # off and unicode-lite is on to keep this bootstrap lean.
268+ execute_process (
269+ COMMAND "${CMAKE_COMMAND } "
270+ ${_jsrh_host_generator}
271+ -S "${CMAKE_CURRENT_SOURCE_DIR } "
272+ -B "${_jsrh_hermes_host_dir} "
273+ -D CMAKE_BUILD_TYPE =Release
274+ -D NAPI_JAVASCRIPT_ENGINE=Hermes
275+ -D HERMES_UNICODE_LITE=ON
276+ -D JSRUNTIMEHOST_TESTS=OFF
277+ RESULT_VARIABLE _jsrh_host_cfg_result )
278+ if (NOT _jsrh_host_cfg_result EQUAL 0)
279+ message (FATAL_ERROR
280+ "Hermes host-compiler bootstrap: configure failed (${_jsrh_host_cfg_result} )." )
281+ endif ()
282+
283+ execute_process (
284+ COMMAND "${CMAKE_COMMAND } " --build "${_jsrh_hermes_host_dir} "
285+ --target hermesc shermes --config Release
286+ RESULT_VARIABLE _jsrh_host_build_result )
287+ if (NOT _jsrh_host_build_result EQUAL 0)
288+ message (FATAL_ERROR
289+ "Hermes host-compiler bootstrap: build failed (${_jsrh_host_build_result} )." )
290+ endif ()
291+ endif ()
292+
293+ if (NOT EXISTS "${_jsrh_hermes_host_import} " )
294+ message (FATAL_ERROR
295+ "Hermes host-compiler bootstrap did not produce ${_jsrh_hermes_host_import} ." )
296+ endif ()
297+
298+ set (IMPORT_HOST_COMPILERS "${_jsrh_hermes_host_import} " CACHE FILEPATH
299+ "Imported Hermes host compilers (hermesc/shermes) for cross-compilation" FORCE )
300+ message (STATUS "Hermes: using bootstrapped host compilers at ${IMPORT_HOST_COMPILERS} " )
301+ endif ()
302+
303+ # Hermes ships its own bundled gtest as `llvh-gtest` (a different
304+ # CMake target name from googletest's `gtest`), so the two coexist
305+ # without clashing. We never link llvh-gtest into our UnitTests
306+ # executable, so there's no duplicate-symbol issue at link time.
307+ FetchContent_MakeAvailable_With_Message (hermes )
308+ endif ()
309+
160310if (NAPI_JAVASCRIPT_ENGINE STREQUAL "V8" AND JSRUNTIMEHOST_CORE_APPRUNTIME_V8_INSPECTOR)
161311 FetchContent_MakeAvailable_With_Message (asio )
162312 add_library (asio INTERFACE )
0 commit comments