From ad3b3208f5b668681409358a31351b5f9402b59b Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Fri, 19 Jun 2026 20:03:51 -0500 Subject: [PATCH 1/2] fix(build): make gperftools opt-in so release binary is self-contained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release binaries dynamically linked the system shared libprofiler/libtcmalloc whenever gperftools was present on the build machine. The self-hosted Linux release runner has libgoogle-perftools-dev installed, so the shipped v0.6.0 binary hard-depended on libprofiler.so.0 and failed to load on any machine without gperftools: error while loading shared libraries: libprofiler.so.0: cannot open shared object file: No such file or directory gperftools is a dev-only profiling aid (--profile-cpu / --profile-memory). Gate it behind LCI_ENABLE_GPERFTOOLS (default OFF) so release builds never link it; profiling.cpp already fails fast at runtime with a rebuild hint when absent. Opting in but missing gperftools now hard-errors at configure time instead of silently skipping. Verified on a machine WITH libprofiler.so.0 installed: release `lci` NEEDED list no longer contains profiler/tcmalloc — only universal system libs. Co-Authored-By: Claude Opus 4.8 --- src/CMakeLists.txt | 50 ++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94e026c..ea5df09 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -133,35 +133,47 @@ if(WIN32) endif() # CoreServices framework no longer needed: efsw vendors its own FSEvents bindings. -# Optional: gperftools for --profile-cpu / --profile-memory wiring. -# When found, profiling.cpp uses ProfilerStart/HeapProfilerStart; when absent, -# the runtime path emits a clear error (Karpathy rule 6) instead of silent no-op. +# Optional: gperftools for --profile-cpu / --profile-memory wiring (DEV ONLY). +# +# OFF by default so release binaries stay self-contained. Auto-detecting the +# system gperftools and linking its SHARED libprofiler/libtcmalloc made the +# release binary hard-depend on libprofiler.so.0; machines without +# libgoogle-perftools installed fail at load ("error while loading shared +# libraries: libprofiler.so.0"). Enable explicitly for local profiling: +# -DLCI_ENABLE_GPERFTOOLS=ON. When disabled, profiling.cpp fails fast at +# runtime (Karpathy rule 6) with a clear rebuild hint. # # tcmalloc replaces the global allocator and is incompatible with the # sanitizer allocators (ASan/TSan/MSan); linking both aborts at startup -# ("Attempt to free invalid pointer"). Skip gperftools entirely under any -# -fsanitize build so sanitizer presets (e.g. tsan) link and run. +# ("Attempt to free invalid pointer"). Force-disabled under any -fsanitize +# build so sanitizer presets (e.g. tsan) link and run even if ON is passed. +option(LCI_ENABLE_GPERFTOOLS "Link gperftools (shared libprofiler/libtcmalloc) for --profile-cpu/--profile-memory; dev only, breaks self-contained release" OFF) string(FIND "${CMAKE_CXX_FLAGS}" "-fsanitize" LCI_SANITIZER_POS) if(LCI_SANITIZER_POS GREATER -1) set(LCI_SANITIZER_BUILD ON) else() set(LCI_SANITIZER_BUILD OFF) endif() -find_library(LCI_GPERFTOOLS_PROFILER profiler) -find_library(LCI_GPERFTOOLS_TCMALLOC tcmalloc) -find_path(LCI_GPERFTOOLS_INCLUDE gperftools/profiler.h) -if(LCI_SANITIZER_BUILD) - message(STATUS "sanitizer build - gperftools/tcmalloc disabled (allocator conflict)") -elseif(LCI_GPERFTOOLS_PROFILER AND LCI_GPERFTOOLS_TCMALLOC AND LCI_GPERFTOOLS_INCLUDE) - message(STATUS "gperftools found - enabling --profile-cpu / --profile-memory") - target_compile_definitions(lci_lib PUBLIC LCI_HAVE_GPERFTOOLS=1) - target_include_directories(lci_lib PUBLIC "${LCI_GPERFTOOLS_INCLUDE}") - target_link_libraries(lci_lib PUBLIC - "${LCI_GPERFTOOLS_PROFILER}" - "${LCI_GPERFTOOLS_TCMALLOC}" - ) +if(NOT LCI_ENABLE_GPERFTOOLS) + message(STATUS "gperftools disabled (LCI_ENABLE_GPERFTOOLS=OFF) - --profile-cpu/--profile-memory fail-fast at runtime") +elseif(LCI_SANITIZER_BUILD) + message(STATUS "sanitizer build - gperftools/tcmalloc force-disabled (allocator conflict)") else() - message(STATUS "gperftools not found - --profile-cpu/--profile-memory will fail-fast at runtime") + find_library(LCI_GPERFTOOLS_PROFILER profiler) + find_library(LCI_GPERFTOOLS_TCMALLOC tcmalloc) + find_path(LCI_GPERFTOOLS_INCLUDE gperftools/profiler.h) + if(LCI_GPERFTOOLS_PROFILER AND LCI_GPERFTOOLS_TCMALLOC AND LCI_GPERFTOOLS_INCLUDE) + message(STATUS "gperftools found - enabling --profile-cpu / --profile-memory") + target_compile_definitions(lci_lib PUBLIC LCI_HAVE_GPERFTOOLS=1) + target_include_directories(lci_lib PUBLIC "${LCI_GPERFTOOLS_INCLUDE}") + target_link_libraries(lci_lib PUBLIC + "${LCI_GPERFTOOLS_PROFILER}" + "${LCI_GPERFTOOLS_TCMALLOC}" + ) + else() + message(FATAL_ERROR "LCI_ENABLE_GPERFTOOLS=ON but gperftools not found " + "(apt: libgoogle-perftools-dev, brew: gperftools, vcpkg: gperftools)") + endif() endif() add_executable(lci From 6aa2c28c2b55651038235eca6c998df1177bfb18 Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Fri, 19 Jun 2026 20:21:10 -0500 Subject: [PATCH 2/2] fix(ci): build Windows release self-contained via x64-windows-static-md triplet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows release leg configured vcpkg with the default x64-windows triplet, which builds the manifest deps (abseil, re2, xxhash, ...) as DLLs. The CPack TGZ ships only lci.exe, so the shipped binary references absl_*.dll / re2.dll that are absent on any machine without the build tree — the Windows analogue of the libprofiler.so.0 load failure. The unit gate passed regardless because the DLLs sit next to the test exe in the build tree, so CI was green while the tarball was broken. Pin VCPKG_TARGET_TRIPLET=x64-windows-static-md on both the release and ci windows legs (kept in lockstep so the gate exercises the shipped linkage). That triplet links the vcpkg deps statically into lci.exe while keeping the dynamic CRT (/MD) the project and its FetchContent deps already use, avoiding a CRT mismatch. Verified the triplet exists at the pinned vcpkg commit (f3e1065) and resolves to CRT=dynamic, LIBRARY=static. BUILD_SHARED_LIBS=OFF only governs the FetchContent deps; vcpkg packages follow the triplet, which is why Linux (x64-linux) and macOS (arm64-osx) were already self-contained (static by default) and only Windows leaked DLLs. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 5 +++++ .github/workflows/release.yml | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bbe86d..35339f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,9 +147,14 @@ jobs: with: vcpkgGitCommitId: "f3e10653cc27d62a37a3763cd84b38bca07c6075" + # Static-md triplet: exercise the SAME self-contained linkage the release + # leg ships (vcpkg deps linked into lci.exe, dynamic CRT). See release.yml + # windows leg for why; keep the two in lockstep so this gate proves the + # shipped artifact loads standalone. - name: Configure run: | cmake --preset release ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static-md ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - name: Dump vcpkg logs on failure diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de54747..1966e5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,9 +112,18 @@ jobs: with: vcpkgGitCommitId: "f3e10653cc27d62a37a3763cd84b38bca07c6075" + # x64-windows-static-md links the vcpkg deps (abseil, re2, xxhash, ...) + # statically INTO lci.exe while keeping the dynamic CRT (/MD) that the + # project and its FetchContent deps already use. Without this the default + # x64-windows triplet builds those deps as DLLs that the CPack TGZ does + # not bundle, so the shipped binary fails to load on any machine without + # them (the libprofiler.so.0 failure's Windows analogue). Keep this in + # lockstep with the ci.yml windows leg so the unit gate exercises the same + # linkage that ships. - name: Configure run: | cmake --preset release ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static-md ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - name: Build