Running QUIC Cut 3's Windows leg exposed the same class of defect Linux
had at 5533c34: CULTMESH_QUIC_BUILD_TESTS has never had its own packaging
step on Windows, and build-quic-native.ps1's copy of msquic.dll only
lands in the final packaged output directory, never beside the test
binary in the CMake build tree.
Unlike the Linux failure, this one did not fail loudly. gamecult_mesh_quic_native.dll
imports msquic.dll by name, and Windows DLL search falls through to PATH
when the importing binary's own directory doesn't have it. This
workstation has an unrelated msquic.dll on PATH from the Windows
Performance Toolkit (529,480 bytes, unversioned) that is not the pinned
Microsoft.Native.Quic.MsQuic.OpenSSL 2.5.9 build (4,181,856 bytes). Every
asserts-build scenario ran clean against that wrong binary before this
fix, with no error to say so.
Mirrors the Linux POST_BUILD fix: CMake already receives MSQUIC_ROOT, so
the test target now copies msquic.dll from MSQUIC_ROOT/bin/x64 beside
itself instead of leaving the loader to guess.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
118 lines
6.9 KiB
CMake
118 lines
6.9 KiB
CMake
cmake_minimum_required(VERSION 3.24)
|
|
project(gamecult_mesh_quic_native LANGUAGES CXX)
|
|
|
|
# Two platforms, one source. Windows takes MsQuic from the pinned OpenSSL NuGet
|
|
# layout (MSQUIC_ROOT); Linux takes it from the extracted libmsquic deb, whose
|
|
# headers and library land in unrelated places, so they are named separately.
|
|
if(NOT WIN32 AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
|
message(FATAL_ERROR "The CultMesh native QUIC bridge supports Windows x64 and Linux x64")
|
|
endif()
|
|
|
|
add_library(gamecult_mesh_quic_native SHARED cultmesh_quic_native.cpp)
|
|
target_compile_features(gamecult_mesh_quic_native PRIVATE cxx_std_20)
|
|
# include/cultmesh_quic_native.h is the host-facing contract, not an internal
|
|
# detail: the bridge includes it, and so does any consumer that can.
|
|
target_include_directories(gamecult_mesh_quic_native PUBLIC include)
|
|
|
|
if(WIN32)
|
|
if(NOT DEFINED MSQUIC_ROOT)
|
|
message(FATAL_ERROR "MSQUIC_ROOT must point at the extracted Microsoft.Native.Quic.MsQuic.OpenSSL package build/native directory")
|
|
endif()
|
|
# Static CRT. A Node or Unity host without the Visual C++ redistributable
|
|
# installed cannot load a DLL that imports vcruntime140.dll, and the failure
|
|
# arrives as "the specified module could not be found" with nothing naming the
|
|
# missing runtime. The bridge carries its own instead.
|
|
set_property(TARGET gamecult_mesh_quic_native PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
|
target_compile_definitions(gamecult_mesh_quic_native PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
|
|
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/include")
|
|
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/lib/x64")
|
|
# crypt32 and bcrypt belong to the v1 shim alone: CryptHashCertificate2
|
|
# compares the Unity pin. Nothing in the v2 path needs them. ntdll carries
|
|
# RtlIpv4/6StringToAddressEx, which msquic_winuser.h's inline QuicAddrFromString
|
|
# calls to parse the listener host; the Linux header parses it in-tree instead.
|
|
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic crypt32 bcrypt ntdll)
|
|
# The DLL is committed beside its source, so a rebuild must reproduce it: /Brepro drops the link timestamps.
|
|
target_compile_options(gamecult_mesh_quic_native PRIVATE /Brepro)
|
|
target_link_options(gamecult_mesh_quic_native PRIVATE /Brepro)
|
|
else()
|
|
if(NOT DEFINED MSQUIC_INCLUDE_DIR OR NOT DEFINED MSQUIC_LIB_DIR)
|
|
message(FATAL_ERROR "MSQUIC_INCLUDE_DIR and MSQUIC_LIB_DIR must point at the msquic v2.5.9 headers and the extracted libmsquic library directory")
|
|
endif()
|
|
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_INCLUDE_DIR}")
|
|
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_LIB_DIR}")
|
|
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic pthread)
|
|
# Only the CULTMESH_API exports leave the .so; everything else stays internal.
|
|
target_compile_options(gamecult_mesh_quic_native PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
|
|
# libmsquic.so.2 ships beside the bridge rather than being installed
|
|
# system-wide, so the loader is pointed at the bridge's own directory.
|
|
set_target_properties(gamecult_mesh_quic_native PROPERTIES
|
|
BUILD_WITH_INSTALL_RPATH TRUE
|
|
INSTALL_RPATH "$ORIGIN")
|
|
endif()
|
|
|
|
set_target_properties(gamecult_mesh_quic_native PROPERTIES
|
|
OUTPUT_NAME "gamecult_mesh_quic_native"
|
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|
|
|
# The runtime-lifetime scenarios. Off by default: the shipped artifact is the
|
|
# library alone, and a delivery build has no business producing an executable.
|
|
# CULTMESH_QUIC_DEBUG_ASSERTS goes with them, never into a shipped binary — it
|
|
# makes the bridge state the quiesce invariant instead of leaving it to whatever
|
|
# a sanitizer happens to catch.
|
|
option(CULTMESH_QUIC_BUILD_TESTS "Build the runtime-lifetime scenario runner" OFF)
|
|
option(CULTMESH_QUIC_DEBUG_ASSERTS "Abort the bridge on a violated internal invariant" OFF)
|
|
|
|
if(CULTMESH_QUIC_DEBUG_ASSERTS)
|
|
target_compile_definitions(gamecult_mesh_quic_native PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
|
|
endif()
|
|
|
|
if(CULTMESH_QUIC_BUILD_TESTS)
|
|
add_executable(cultmesh_quic_native_tests tests/cultmesh_quic_native_tests.cpp)
|
|
target_compile_features(cultmesh_quic_native_tests PRIVATE cxx_std_20)
|
|
# The header only. The scenarios drive the bridge as a host does, so they get
|
|
# no more of it than a host has.
|
|
target_link_libraries(cultmesh_quic_native_tests PRIVATE gamecult_mesh_quic_native)
|
|
# The scenarios see the development seam (section 7 of the header) exactly when
|
|
# the library has one, so `holdclose` compiles out of the same builds the
|
|
# bridge's own assertions compile out of.
|
|
if(CULTMESH_QUIC_DEBUG_ASSERTS)
|
|
target_compile_definitions(cultmesh_quic_native_tests PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
|
|
endif()
|
|
set_target_properties(cultmesh_quic_native_tests PROPERTIES
|
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|
if(WIN32)
|
|
# gamecult_mesh_quic_native.dll imports msquic.dll, and the scenario
|
|
# runner loads it from its own directory (or PATH) at process start, not
|
|
# from MSQUIC_ROOT. build-quic-native.ps1's packaged output gets a copy
|
|
# from its own recipe; a test build has no packaging step, so without this
|
|
# the loader falls through to whatever msquic.dll happens to already be on
|
|
# PATH — silently running the scenarios against an unpinned, wrong-version
|
|
# MsQuic instead of failing to start. CMake already knows MSQUIC_ROOT, so
|
|
# it places the pinned DLL itself instead of leaving the reader to guess
|
|
# why a scenario passed against the wrong binary.
|
|
add_custom_command(TARGET cultmesh_quic_native_tests POST_BUILD
|
|
COMMAND ${CMAKE_COMMAND} -E copy
|
|
"${MSQUIC_ROOT}/bin/x64/msquic.dll"
|
|
"$<TARGET_FILE_DIR:cultmesh_quic_native_tests>/msquic.dll")
|
|
else()
|
|
target_link_libraries(cultmesh_quic_native_tests PRIVATE pthread)
|
|
# The linker resolves the bridge's own NEEDED libmsquic.so.2 while linking
|
|
# this executable, and the runtime copy only lands beside the binaries after
|
|
# the build; point it at the extracted package instead.
|
|
target_link_options(cultmesh_quic_native_tests PRIVATE "-Wl,-rpath-link,${MSQUIC_LIB_DIR}")
|
|
set_target_properties(cultmesh_quic_native_tests PROPERTIES
|
|
BUILD_WITH_INSTALL_RPATH TRUE
|
|
INSTALL_RPATH "$ORIGIN")
|
|
# Both binaries carry INSTALL_RPATH "$ORIGIN" and expect libmsquic.so.2
|
|
# beside them at load time. build-quic-native.sh's packaged output gets
|
|
# that copy from its own recipe; a test build has no packaging step, so
|
|
# the runner would otherwise die at once with "cannot open shared object
|
|
# file". CMake already knows where MSQUIC_LIB_DIR is, so it places the
|
|
# file itself instead of leaving the reader to run an extra cp by hand.
|
|
add_custom_command(TARGET cultmesh_quic_native_tests POST_BUILD
|
|
COMMAND ${CMAKE_COMMAND} -E copy
|
|
"${MSQUIC_LIB_DIR}/libmsquic.so.2"
|
|
"$<TARGET_FILE_DIR:cultmesh_quic_native_tests>/libmsquic.so.2")
|
|
endif()
|
|
endif()
|